@syncended/dsh-automations 0.6.0 → 0.7.1
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/README.md +2 -2
- package/docs/architecture.md +1 -1
- package/lib/client.js +769 -251
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ dsh plugin --profile web remove @syncended/dsh-automations
|
|
|
55
55
|
| Field | Meaning |
|
|
56
56
|
|---|---|
|
|
57
57
|
| Name | Human-readable job name. |
|
|
58
|
-
|
|
|
58
|
+
| Schedule | Choose Minutes, Hourly, Daily, Weekdays, or Weekly and adjust its simple controls. Custom exposes the raw five-field cron (`minute hour day-of-month month day-of-week`) with an inline field guide. |
|
|
59
59
|
| Time zone | Search by city or region in the picker, or enter `UTC` / an IANA name such as `Europe/Berlin`. Current UTC offsets are shown; DST is handled by `cron-parser`. |
|
|
60
60
|
| Workspace | Choose an existing Harness workspace or enter an absolute directory manually. Its canonical filesystem identity becomes the Session cwd and `workspace-write` root. |
|
|
61
61
|
| Prompt | The user message sent to a fresh Harness Agent. |
|
|
@@ -66,7 +66,7 @@ dsh plugin --profile web remove @syncended/dsh-automations
|
|
|
66
66
|
| Timeout | Wall-clock run limit. Cancellation is cooperative through the Agent loop. |
|
|
67
67
|
| Overlap / misfire | Admission behavior described above. |
|
|
68
68
|
|
|
69
|
-
A
|
|
69
|
+
The form groups common settings into **Task**, **Schedule**, and **Agent & access**. Job ID, timeout, overlap, and misfire stay available in the collapsed **Advanced** section. A paused job may still be started with **Run now**.
|
|
70
70
|
|
|
71
71
|
## Plugin configuration
|
|
72
72
|
|
package/docs/architecture.md
CHANGED
|
@@ -32,7 +32,7 @@ Sidebar / Center workspace / Settings UI / HTTP API
|
|
|
32
32
|
|
|
33
33
|
The same automation page is available through Settings and a dedicated center workspace. `settings.section` keeps configuration available inside Settings; `sidebar.footer.action` activates a temporary, higher-priority `conversation` entry that replaces the current center occupant. This provides a split-screen-like full-center experience while using the single-slot election directly rather than Split Screen's internal view contributions. Closing Automations disposes its entry immediately, revealing whichever center surface was previously active. A small client-only disclosure store coordinates the sidebar trigger and dynamic registration; neither surface owns scheduler state, and both read the same package HTTP API.
|
|
34
34
|
|
|
35
|
-
Interactive chrome uses the ambient `@deepseek-ai/dsh-client-ui-primitives` `Button`, `Menu`, and icon components. The Workspace selector subscribes to `ctx.workspaces.list`, projecting the Host's live workspace order while retaining manual absolute-path entry. Package CSS is limited to the automation-specific layout and composes only public DSH semantic tokens, so theme, menu, focus, and button behavior stay aligned with the host UI.
|
|
35
|
+
Interactive chrome uses the ambient `@deepseek-ai/dsh-client-ui-primitives` `Button`, `Menu`, `Pill`, and icon components. The form separates Task, Schedule, Agent/access, and Advanced concerns; common schedule pills generate canonical five-field cron while Custom keeps the raw expression and field guide available. The Workspace selector subscribes to `ctx.workspaces.list`, projecting the Host's live workspace order while retaining manual absolute-path entry. Package CSS is limited to the automation-specific layout and composes only public DSH semantic tokens, so theme, menu, focus, and button behavior stay aligned with the host UI.
|
|
36
36
|
|
|
37
37
|
### AutomationService
|
|
38
38
|
|
package/lib/client.js
CHANGED
|
@@ -17,6 +17,7 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
const {
|
|
18
18
|
Button,
|
|
19
19
|
Menu,
|
|
20
|
+
Pill,
|
|
20
21
|
RiskConfirmation,
|
|
21
22
|
IconAgentPresetOutline16,
|
|
22
23
|
IconBrowseOutline16,
|
|
@@ -44,6 +45,32 @@ window.__ModuleLoader__.load({
|
|
|
44
45
|
const MIN_TIMEOUT_MS = 1000;
|
|
45
46
|
const MAX_TIMEOUT_MS = 86400000;
|
|
46
47
|
const JOB_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
48
|
+
const SCHEDULE_MODES = [
|
|
49
|
+
{ id: "minutes", label: "Minutes" },
|
|
50
|
+
{ id: "hourly", label: "Hourly" },
|
|
51
|
+
{ id: "daily", label: "Daily" },
|
|
52
|
+
{ id: "weekdays", label: "Weekdays" },
|
|
53
|
+
{ id: "weekly", label: "Weekly" },
|
|
54
|
+
{ id: "custom", label: "Custom" },
|
|
55
|
+
];
|
|
56
|
+
const MINUTE_INTERVALS = [5, 10, 15, 20, 30];
|
|
57
|
+
const HOURLY_MINUTES = Array.from({ length: 60 }, (_, index) => index);
|
|
58
|
+
const WEEKDAYS = [
|
|
59
|
+
{ value: "1", label: "Monday" },
|
|
60
|
+
{ value: "2", label: "Tuesday" },
|
|
61
|
+
{ value: "3", label: "Wednesday" },
|
|
62
|
+
{ value: "4", label: "Thursday" },
|
|
63
|
+
{ value: "5", label: "Friday" },
|
|
64
|
+
{ value: "6", label: "Saturday" },
|
|
65
|
+
{ value: "0", label: "Sunday" },
|
|
66
|
+
];
|
|
67
|
+
const CRON_FIELD_GUIDE = [
|
|
68
|
+
{ label: "Minute", range: "0–59" },
|
|
69
|
+
{ label: "Hour", range: "0–23" },
|
|
70
|
+
{ label: "Day", range: "1–31" },
|
|
71
|
+
{ label: "Month", range: "1–12" },
|
|
72
|
+
{ label: "Weekday", range: "0–7" },
|
|
73
|
+
];
|
|
47
74
|
let formInstanceSerial = 0;
|
|
48
75
|
|
|
49
76
|
const BROWSER_TIMEZONE = (() => {
|
|
@@ -140,13 +167,13 @@ window.__ModuleLoader__.load({
|
|
|
140
167
|
error: null,
|
|
141
168
|
});
|
|
142
169
|
const OVERLAP_OPTIONS = [
|
|
143
|
-
{ value: "skip", label: "
|
|
144
|
-
{ value: "queue", label: "
|
|
145
|
-
{ value: "allow", label: "
|
|
170
|
+
{ value: "skip", label: "Skip the new run", hint: "Skip the run if a previous run is still active." },
|
|
171
|
+
{ value: "queue", label: "Queue the next run", hint: "Defer the run until the previous run finishes." },
|
|
172
|
+
{ value: "allow", label: "Allow concurrent runs", hint: "Run concurrently with any active run." },
|
|
146
173
|
];
|
|
147
174
|
const MISFIRE_OPTIONS = [
|
|
148
|
-
{ value: "skip", label: "
|
|
149
|
-
{ value: "run-once", label: "
|
|
175
|
+
{ value: "skip", label: "Skip missed runs", hint: "Drop occurrences that were missed while the scheduler was down." },
|
|
176
|
+
{ value: "run-once", label: "Run once after downtime", hint: "Run once after downtime for the latest missed occurrence." },
|
|
150
177
|
];
|
|
151
178
|
|
|
152
179
|
function errMessage(error) {
|
|
@@ -395,6 +422,31 @@ window.__ModuleLoader__.load({
|
|
|
395
422
|
return Math.round(ms / 1000) + "s";
|
|
396
423
|
}
|
|
397
424
|
|
|
425
|
+
function overlapPolicySummary(value) {
|
|
426
|
+
if (value === "queue") return "Queue overlaps";
|
|
427
|
+
if (value === "allow") return "Allow concurrent runs";
|
|
428
|
+
return "Skip overlaps";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function inferFormErrorField(message) {
|
|
432
|
+
const text = String(message || "").toLowerCase();
|
|
433
|
+
if (text.includes("job id")) return "id";
|
|
434
|
+
if (text.includes("timeout")) return "timeout";
|
|
435
|
+
if (text.includes("overlap")) return "overlap";
|
|
436
|
+
if (text.includes("misfire")) return "misfire";
|
|
437
|
+
if (text.includes("cron")) return "cron";
|
|
438
|
+
if (text.includes("timezone") || text.includes("time zone")) return "timezone";
|
|
439
|
+
if (text.includes("workspace")) return "cwd";
|
|
440
|
+
if (text.includes("prompt")) return "prompt";
|
|
441
|
+
if (text.includes("reasoning effort")) return "effort";
|
|
442
|
+
if (text.includes("agent preset")) return "preset";
|
|
443
|
+
if (text.includes("permission")) return "permission";
|
|
444
|
+
if (text.includes("model")) return "model";
|
|
445
|
+
if (text.includes("provider")) return "provider";
|
|
446
|
+
if (text.includes("name")) return "name";
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
398
450
|
async function apiFetch(path, options = {}) {
|
|
399
451
|
const headers = Object.assign(
|
|
400
452
|
{ "content-type": "application/json", "x-dsh-automation-client": "1" },
|
|
@@ -500,6 +552,304 @@ window.__ModuleLoader__.load({
|
|
|
500
552
|
};
|
|
501
553
|
}
|
|
502
554
|
|
|
555
|
+
function cronFields(value) {
|
|
556
|
+
return String(value || "").trim().split(/\s+/).filter(Boolean);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function twoDigits(value) {
|
|
560
|
+
return String(value).padStart(2, "0");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function simpleSchedule(cronValue) {
|
|
564
|
+
const cron = cronFields(cronValue).join(" ");
|
|
565
|
+
let match = cron.match(/^\*\/(5|10|15|20|30) \* \* \* \*$/);
|
|
566
|
+
if (match) return { mode: "minutes", interval: Number(match[1]), minute: 0, hour: 9, weekday: "1" };
|
|
567
|
+
match = cron.match(/^(\d{1,2}) \* \* \* \*$/);
|
|
568
|
+
if (match && Number(match[1]) <= 59) {
|
|
569
|
+
return { mode: "hourly", minute: Number(match[1]), hour: 9, weekday: "1", interval: 15 };
|
|
570
|
+
}
|
|
571
|
+
match = cron.match(/^(\d{1,2}) (\d{1,2}) \* \* 1-5$/);
|
|
572
|
+
if (match && Number(match[1]) <= 59 && Number(match[2]) <= 23) {
|
|
573
|
+
return { mode: "weekdays", minute: Number(match[1]), hour: Number(match[2]), weekday: "1", interval: 15 };
|
|
574
|
+
}
|
|
575
|
+
match = cron.match(/^(\d{1,2}) (\d{1,2}) \* \* ([0-7])$/);
|
|
576
|
+
if (match && Number(match[1]) <= 59 && Number(match[2]) <= 23) {
|
|
577
|
+
return {
|
|
578
|
+
mode: "weekly",
|
|
579
|
+
minute: Number(match[1]),
|
|
580
|
+
hour: Number(match[2]),
|
|
581
|
+
weekday: match[3] === "7" ? "0" : match[3],
|
|
582
|
+
interval: 15,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
match = cron.match(/^(\d{1,2}) (\d{1,2}) \* \* \*$/);
|
|
586
|
+
if (match && Number(match[1]) <= 59 && Number(match[2]) <= 23) {
|
|
587
|
+
return { mode: "daily", minute: Number(match[1]), hour: Number(match[2]), weekday: "1", interval: 15 };
|
|
588
|
+
}
|
|
589
|
+
return { mode: "custom", minute: 0, hour: 9, weekday: "1", interval: 15 };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function scheduleControlValues(cronValue, fallback = {}) {
|
|
593
|
+
const fields = cronFields(cronValue);
|
|
594
|
+
const minute = /^\d{1,2}$/.test(fields[0] || "") && Number(fields[0]) <= 59
|
|
595
|
+
? Number(fields[0])
|
|
596
|
+
: Number.isInteger(fallback.minute) ? fallback.minute : 0;
|
|
597
|
+
const hour = /^\d{1,2}$/.test(fields[1] || "") && Number(fields[1]) <= 23
|
|
598
|
+
? Number(fields[1])
|
|
599
|
+
: Number.isInteger(fallback.hour) ? fallback.hour : 9;
|
|
600
|
+
const normalizedWeekday = fields[4] === "7" ? "0" : fields[4];
|
|
601
|
+
const weekday = WEEKDAYS.some((day) => day.value === normalizedWeekday)
|
|
602
|
+
? normalizedWeekday
|
|
603
|
+
: WEEKDAYS.some((day) => day.value === String(fallback.weekday)) ? String(fallback.weekday) : "1";
|
|
604
|
+
const intervalMatch = /^\*\/(\d+)$/.exec(fields[0] || "");
|
|
605
|
+
const parsedInterval = intervalMatch ? Number(intervalMatch[1]) : NaN;
|
|
606
|
+
const interval = MINUTE_INTERVALS.includes(parsedInterval)
|
|
607
|
+
? parsedInterval
|
|
608
|
+
: MINUTE_INTERVALS.includes(fallback.interval) ? fallback.interval : 15;
|
|
609
|
+
return { minute, hour, weekday, interval };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function cronForSimpleSchedule(mode, values = {}) {
|
|
613
|
+
const minute = Number.isInteger(values.minute) && values.minute >= 0 && values.minute <= 59 ? values.minute : 0;
|
|
614
|
+
const hour = Number.isInteger(values.hour) && values.hour >= 0 && values.hour <= 23 ? values.hour : 9;
|
|
615
|
+
const interval = MINUTE_INTERVALS.includes(values.interval) ? values.interval : 15;
|
|
616
|
+
const weekday = WEEKDAYS.some((day) => day.value === String(values.weekday)) ? String(values.weekday) : "1";
|
|
617
|
+
if (mode === "minutes") return "*/" + interval + " * * * *";
|
|
618
|
+
if (mode === "hourly") return minute + " * * * *";
|
|
619
|
+
if (mode === "daily") return minute + " " + hour + " * * *";
|
|
620
|
+
if (mode === "weekdays") return minute + " " + hour + " * * 1-5";
|
|
621
|
+
if (mode === "weekly") return minute + " " + hour + " * * " + weekday;
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function scheduleModeTransition(mode, cron, values, customCron) {
|
|
626
|
+
if (mode === "custom") {
|
|
627
|
+
const restored = customCron === null ? cron : customCron;
|
|
628
|
+
return { cron: restored, customCron: restored };
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
cron: cronForSimpleSchedule(mode, values) || cron,
|
|
632
|
+
customCron,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function describeSimpleSchedule(schedule, timezone) {
|
|
637
|
+
const time = twoDigits(schedule.hour) + ":" + twoDigits(schedule.minute);
|
|
638
|
+
const zone = timezone.trim() || "the selected time zone";
|
|
639
|
+
if (schedule.mode === "minutes") return "Every " + schedule.interval + " minutes · " + zone;
|
|
640
|
+
if (schedule.mode === "hourly") return "Every hour at :" + twoDigits(schedule.minute) + " · " + zone;
|
|
641
|
+
if (schedule.mode === "daily") return "Every day at " + time + " · " + zone;
|
|
642
|
+
if (schedule.mode === "weekdays") return "Monday–Friday at " + time + " · " + zone;
|
|
643
|
+
if (schedule.mode === "weekly") {
|
|
644
|
+
const day = WEEKDAYS.find((entry) => entry.value === schedule.weekday)?.label || "Monday";
|
|
645
|
+
return "Every " + day + " at " + time + " · " + zone;
|
|
646
|
+
}
|
|
647
|
+
return cronFields(schedule.cron).length === 5
|
|
648
|
+
? "Custom five-field schedule · " + zone
|
|
649
|
+
: "Cron needs exactly five fields · " + zone;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function FormSection({ title, description, Icon, children, className = "" }) {
|
|
653
|
+
return h(
|
|
654
|
+
"section",
|
|
655
|
+
{ className: "dsh-auto-form-section" + (className ? " " + className : "") },
|
|
656
|
+
h(
|
|
657
|
+
"header",
|
|
658
|
+
{ className: "dsh-auto-form-section-head" },
|
|
659
|
+
Icon ? h("span", { className: "dsh-auto-form-section-icon", "aria-hidden": "true" }, h(Icon)) : null,
|
|
660
|
+
h(
|
|
661
|
+
"span",
|
|
662
|
+
{ className: "dsh-auto-form-section-copy" },
|
|
663
|
+
h("h4", { className: "dsh-auto-form-section-title" }, title),
|
|
664
|
+
description ? h("p", { className: "dsh-auto-form-section-description" }, description) : null,
|
|
665
|
+
),
|
|
666
|
+
),
|
|
667
|
+
children,
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function CronFieldGuide({ value }) {
|
|
672
|
+
const fields = cronFields(value);
|
|
673
|
+
return h(
|
|
674
|
+
React.Fragment,
|
|
675
|
+
null,
|
|
676
|
+
h(
|
|
677
|
+
"div",
|
|
678
|
+
{ className: "dsh-auto-cron-guide", "aria-label": "Cron field order" },
|
|
679
|
+
CRON_FIELD_GUIDE.map((field, index) => h(
|
|
680
|
+
"span",
|
|
681
|
+
{ key: field.label, className: "dsh-auto-cron-guide-field" },
|
|
682
|
+
h("code", { className: "dsh-auto-cron-guide-value" }, fields[index] || "—"),
|
|
683
|
+
h("span", { className: "dsh-auto-cron-guide-label" }, field.label),
|
|
684
|
+
h("span", { className: "dsh-auto-cron-guide-range" }, field.range),
|
|
685
|
+
)),
|
|
686
|
+
),
|
|
687
|
+
h("p", { className: "dsh-auto-cron-syntax" }, "* any value · , list · - range · / step"),
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function ScheduleEditor({ fieldId, cron, timezone, onCronChange, onTimezoneChange }) {
|
|
692
|
+
const detected = simpleSchedule(cron);
|
|
693
|
+
const [forceCustom, setForceCustom] = useState(false);
|
|
694
|
+
const [values, setValues] = useState(() => scheduleControlValues(cron));
|
|
695
|
+
const customCronRef = useRef(detected.mode === "custom" ? cron : null);
|
|
696
|
+
const customCronPinnedRef = useRef(detected.mode === "custom");
|
|
697
|
+
const activeMode = forceCustom || detected.mode === "custom" ? "custom" : detected.mode;
|
|
698
|
+
const applySimpleValues = (mode, nextValues) => {
|
|
699
|
+
setValues(nextValues);
|
|
700
|
+
const expression = cronForSimpleSchedule(mode, nextValues);
|
|
701
|
+
if (expression !== null) onCronChange(expression);
|
|
702
|
+
};
|
|
703
|
+
const setMode = (mode) => {
|
|
704
|
+
if (mode !== "custom" && !customCronPinnedRef.current) customCronRef.current = null;
|
|
705
|
+
const transition = scheduleModeTransition(mode, cron, values, customCronRef.current);
|
|
706
|
+
customCronRef.current = transition.customCron;
|
|
707
|
+
setForceCustom(mode === "custom");
|
|
708
|
+
if (transition.cron !== cron) onCronChange(transition.cron);
|
|
709
|
+
};
|
|
710
|
+
const setTime = (raw) => {
|
|
711
|
+
const match = /^(\d{2}):(\d{2})$/.exec(raw);
|
|
712
|
+
if (!match) return;
|
|
713
|
+
applySimpleValues(activeMode, Object.assign({}, values, {
|
|
714
|
+
hour: Number(match[1]),
|
|
715
|
+
minute: Number(match[2]),
|
|
716
|
+
}));
|
|
717
|
+
};
|
|
718
|
+
const summarySchedule = Object.assign({}, values, { mode: activeMode, cron });
|
|
719
|
+
const displayedCron = cronFields(cron).join(" ") || "—";
|
|
720
|
+
|
|
721
|
+
return h(
|
|
722
|
+
React.Fragment,
|
|
723
|
+
null,
|
|
724
|
+
h(
|
|
725
|
+
"div",
|
|
726
|
+
{ className: "dsh-auto-schedule-modes dsh-auto-field-full", role: "group", "aria-label": "Schedule frequency" },
|
|
727
|
+
SCHEDULE_MODES.map((mode) => h(
|
|
728
|
+
Pill,
|
|
729
|
+
{
|
|
730
|
+
key: mode.id,
|
|
731
|
+
type: "button",
|
|
732
|
+
active: activeMode === mode.id,
|
|
733
|
+
"aria-pressed": activeMode === mode.id,
|
|
734
|
+
onClick: () => setMode(mode.id),
|
|
735
|
+
},
|
|
736
|
+
mode.label,
|
|
737
|
+
)),
|
|
738
|
+
),
|
|
739
|
+
activeMode === "minutes"
|
|
740
|
+
? h(
|
|
741
|
+
Field,
|
|
742
|
+
{ label: "Interval", htmlFor: fieldId("interval") },
|
|
743
|
+
h(
|
|
744
|
+
"select",
|
|
745
|
+
{
|
|
746
|
+
id: fieldId("interval"),
|
|
747
|
+
className: "dsh-auto-select",
|
|
748
|
+
value: values.interval,
|
|
749
|
+
onChange: (event) => applySimpleValues("minutes", Object.assign({}, values, { interval: Number(event.target.value) })),
|
|
750
|
+
},
|
|
751
|
+
MINUTE_INTERVALS.map((minutes) => h("option", { key: minutes, value: minutes }, "Every " + minutes + " minutes")),
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
: null,
|
|
755
|
+
activeMode === "hourly"
|
|
756
|
+
? h(
|
|
757
|
+
Field,
|
|
758
|
+
{ label: "Minute past the hour", htmlFor: fieldId("hour-minute") },
|
|
759
|
+
h(
|
|
760
|
+
"select",
|
|
761
|
+
{
|
|
762
|
+
id: fieldId("hour-minute"),
|
|
763
|
+
className: "dsh-auto-select",
|
|
764
|
+
value: values.minute,
|
|
765
|
+
onChange: (event) => applySimpleValues("hourly", Object.assign({}, values, { minute: Number(event.target.value) })),
|
|
766
|
+
},
|
|
767
|
+
HOURLY_MINUTES.map((minute) => h("option", { key: minute, value: minute }, ":" + twoDigits(minute))),
|
|
768
|
+
),
|
|
769
|
+
)
|
|
770
|
+
: null,
|
|
771
|
+
["daily", "weekdays", "weekly"].includes(activeMode)
|
|
772
|
+
? h(
|
|
773
|
+
Field,
|
|
774
|
+
{ label: "Time", htmlFor: fieldId("schedule-time") },
|
|
775
|
+
h("input", {
|
|
776
|
+
id: fieldId("schedule-time"),
|
|
777
|
+
className: "dsh-auto-input",
|
|
778
|
+
type: "time",
|
|
779
|
+
value: twoDigits(values.hour) + ":" + twoDigits(values.minute),
|
|
780
|
+
onChange: (event) => setTime(event.target.value),
|
|
781
|
+
}),
|
|
782
|
+
)
|
|
783
|
+
: null,
|
|
784
|
+
activeMode === "weekly"
|
|
785
|
+
? h(
|
|
786
|
+
Field,
|
|
787
|
+
{ label: "Day", htmlFor: fieldId("schedule-day") },
|
|
788
|
+
h(
|
|
789
|
+
"select",
|
|
790
|
+
{
|
|
791
|
+
id: fieldId("schedule-day"),
|
|
792
|
+
className: "dsh-auto-select",
|
|
793
|
+
value: values.weekday,
|
|
794
|
+
onChange: (event) => applySimpleValues("weekly", Object.assign({}, values, { weekday: event.target.value })),
|
|
795
|
+
},
|
|
796
|
+
WEEKDAYS.map((day) => h("option", { key: day.value, value: day.value }, day.label)),
|
|
797
|
+
),
|
|
798
|
+
)
|
|
799
|
+
: null,
|
|
800
|
+
h(
|
|
801
|
+
Field,
|
|
802
|
+
{
|
|
803
|
+
label: "Time zone",
|
|
804
|
+
htmlFor: fieldId("timezone"),
|
|
805
|
+
required: true,
|
|
806
|
+
full: activeMode === "weekly" || activeMode === "custom",
|
|
807
|
+
hint: "Current UTC offsets and daylight-saving changes are handled by the selected IANA time zone.",
|
|
808
|
+
},
|
|
809
|
+
h(TimeZonePicker, {
|
|
810
|
+
id: fieldId("timezone"),
|
|
811
|
+
value: timezone,
|
|
812
|
+
onChange: onTimezoneChange,
|
|
813
|
+
}),
|
|
814
|
+
),
|
|
815
|
+
activeMode === "custom"
|
|
816
|
+
? h(
|
|
817
|
+
Field,
|
|
818
|
+
{
|
|
819
|
+
label: "Cron expression",
|
|
820
|
+
htmlFor: fieldId("cron"),
|
|
821
|
+
required: true,
|
|
822
|
+
full: true,
|
|
823
|
+
hint: "Five fields only. Seconds and hashed H expressions are not supported.",
|
|
824
|
+
},
|
|
825
|
+
h("input", {
|
|
826
|
+
id: fieldId("cron"),
|
|
827
|
+
className: "dsh-auto-input dsh-auto-cron-input",
|
|
828
|
+
type: "text",
|
|
829
|
+
value: cron,
|
|
830
|
+
spellCheck: false,
|
|
831
|
+
placeholder: "0 9 * * 1-5",
|
|
832
|
+
onChange: (event) => {
|
|
833
|
+
const nextCron = event.target.value;
|
|
834
|
+
customCronRef.current = nextCron;
|
|
835
|
+
customCronPinnedRef.current = true;
|
|
836
|
+
setValues(scheduleControlValues(nextCron, values));
|
|
837
|
+
setForceCustom(true);
|
|
838
|
+
onCronChange(nextCron);
|
|
839
|
+
},
|
|
840
|
+
}),
|
|
841
|
+
h(CronFieldGuide, { value: cron }),
|
|
842
|
+
)
|
|
843
|
+
: null,
|
|
844
|
+
h(
|
|
845
|
+
"div",
|
|
846
|
+
{ className: "dsh-auto-schedule-summary dsh-auto-field-full" },
|
|
847
|
+
h("span", { className: "dsh-auto-schedule-summary-copy" }, describeSimpleSchedule(summarySchedule, timezone)),
|
|
848
|
+
h("code", { className: "dsh-auto-schedule-expression", title: displayedCron }, displayedCron),
|
|
849
|
+
),
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
503
853
|
function Field(props) {
|
|
504
854
|
const { label, htmlFor, required, hint, full, children } = props;
|
|
505
855
|
return h(
|
|
@@ -1506,7 +1856,7 @@ window.__ModuleLoader__.load({
|
|
|
1506
1856
|
}
|
|
1507
1857
|
|
|
1508
1858
|
function JobForm(props) {
|
|
1509
|
-
const { meta, workspaceSnapshot, draft, editing, saving, error, onChange, onIdTouched, onSubmit, onCancel } = props;
|
|
1859
|
+
const { meta, workspaceSnapshot, draft, editing, saving, error, errorField, onChange, onIdTouched, onSubmit, onCancel } = props;
|
|
1510
1860
|
const providers = normalizedProviders(meta && Array.isArray(meta.providers) ? meta.providers : []);
|
|
1511
1861
|
const providerModels = providers.find((provider) => provider.id === draft.provider);
|
|
1512
1862
|
const models = providerModels ? providerModels.models : [];
|
|
@@ -1522,6 +1872,43 @@ window.__ModuleLoader__.load({
|
|
|
1522
1872
|
formPrefixRef.current = nextFormInstancePrefix(reactFormId);
|
|
1523
1873
|
}
|
|
1524
1874
|
const fieldId = (name) => formPrefixRef.current + "-" + name;
|
|
1875
|
+
const errorId = fieldId("form-error");
|
|
1876
|
+
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
1877
|
+
const advancedError = ["id", "timeout", "overlap", "misfire"].includes(errorField);
|
|
1878
|
+
const errorPanelReady = !advancedError || advancedOpen;
|
|
1879
|
+
useEffect(() => {
|
|
1880
|
+
if (!error || !errorField) return undefined;
|
|
1881
|
+
if (!errorPanelReady) {
|
|
1882
|
+
setAdvancedOpen(true);
|
|
1883
|
+
return undefined;
|
|
1884
|
+
}
|
|
1885
|
+
let target = null;
|
|
1886
|
+
let previousInvalid = null;
|
|
1887
|
+
let previousDescribedBy = null;
|
|
1888
|
+
const frame = requestAnimationFrame(() => {
|
|
1889
|
+
target = document.getElementById(fieldId(errorField));
|
|
1890
|
+
if (!target) return;
|
|
1891
|
+
previousInvalid = target.getAttribute("aria-invalid");
|
|
1892
|
+
previousDescribedBy = target.getAttribute("aria-describedby");
|
|
1893
|
+
target.setAttribute("aria-invalid", "true");
|
|
1894
|
+
target.setAttribute("aria-describedby", errorId);
|
|
1895
|
+
target.focus();
|
|
1896
|
+
});
|
|
1897
|
+
return () => {
|
|
1898
|
+
cancelAnimationFrame(frame);
|
|
1899
|
+
if (!target) return;
|
|
1900
|
+
if (previousInvalid === null) target.removeAttribute("aria-invalid");
|
|
1901
|
+
else target.setAttribute("aria-invalid", previousInvalid);
|
|
1902
|
+
if (previousDescribedBy === null) target.removeAttribute("aria-describedby");
|
|
1903
|
+
else target.setAttribute("aria-describedby", previousDescribedBy);
|
|
1904
|
+
};
|
|
1905
|
+
}, [error, errorField, errorPanelReady]);
|
|
1906
|
+
const advancedSummary = [
|
|
1907
|
+
creating ? (draft.id.trim() === "" ? "Automatic job ID" : "ID " + draft.id.trim()) : "ID " + draft.id,
|
|
1908
|
+
(formatTimeout(Number(draft.timeoutMs)) || draft.timeoutMs + "ms") + " timeout",
|
|
1909
|
+
overlapPolicySummary(draft.overlap),
|
|
1910
|
+
draft.misfire === "skip" ? "Skip missed runs" : "Run once after downtime",
|
|
1911
|
+
].join(" · ");
|
|
1525
1912
|
|
|
1526
1913
|
return h(
|
|
1527
1914
|
"form",
|
|
@@ -1531,262 +1918,327 @@ window.__ModuleLoader__.load({
|
|
|
1531
1918
|
noValidate: true,
|
|
1532
1919
|
"aria-label": creating ? "New automation" : "Edit automation",
|
|
1533
1920
|
},
|
|
1534
|
-
h("h3", null, creating ? "New automation" : "Edit \u201c" + editing.name + "\u201d"),
|
|
1535
1921
|
h(
|
|
1536
|
-
"
|
|
1537
|
-
{ className: "dsh-auto-
|
|
1922
|
+
"header",
|
|
1923
|
+
{ className: "dsh-auto-form-header" },
|
|
1538
1924
|
h(
|
|
1539
|
-
|
|
1540
|
-
{
|
|
1541
|
-
h("
|
|
1542
|
-
|
|
1543
|
-
className: "dsh-auto-input",
|
|
1544
|
-
type: "text",
|
|
1545
|
-
value: draft.name,
|
|
1546
|
-
autoFocus: true,
|
|
1547
|
-
placeholder: "e.g. Morning standup notes",
|
|
1548
|
-
onChange: (event) => onChange("name", event.target.value),
|
|
1549
|
-
}),
|
|
1925
|
+
"span",
|
|
1926
|
+
{ className: "dsh-auto-form-heading" },
|
|
1927
|
+
h("h3", null, creating ? "New automation" : "Edit \u201c" + editing.name + "\u201d"),
|
|
1928
|
+
h("p", null, "Configure what runs, when it runs, and which Harness agent executes it."),
|
|
1550
1929
|
),
|
|
1551
|
-
creating
|
|
1552
|
-
? h(
|
|
1553
|
-
Field,
|
|
1554
|
-
{ label: "Job id", htmlFor: fieldId("id"), hint: "Lowercase letters, digits and hyphens; auto-derived from the name. Leave blank to let the server generate one." },
|
|
1555
|
-
h("input", {
|
|
1556
|
-
id: fieldId("id"),
|
|
1557
|
-
className: "dsh-auto-input",
|
|
1558
|
-
type: "text",
|
|
1559
|
-
value: draft.id,
|
|
1560
|
-
spellCheck: false,
|
|
1561
|
-
placeholder: "auto",
|
|
1562
|
-
onChange: (event) => {
|
|
1563
|
-
onIdTouched();
|
|
1564
|
-
onChange("id", event.target.value);
|
|
1565
|
-
},
|
|
1566
|
-
}),
|
|
1567
|
-
)
|
|
1568
|
-
: h(
|
|
1569
|
-
Field,
|
|
1570
|
-
{ label: "Job id", htmlFor: fieldId("id"), hint: "Fixed after creation." },
|
|
1571
|
-
h("input", {
|
|
1572
|
-
id: fieldId("id"),
|
|
1573
|
-
className: "dsh-auto-input",
|
|
1574
|
-
type: "text",
|
|
1575
|
-
value: draft.id,
|
|
1576
|
-
disabled: true,
|
|
1577
|
-
spellCheck: false,
|
|
1578
|
-
}),
|
|
1579
|
-
),
|
|
1580
1930
|
h(
|
|
1581
|
-
|
|
1582
|
-
{
|
|
1931
|
+
"label",
|
|
1932
|
+
{ className: "dsh-auto-switch dsh-auto-form-enabled" },
|
|
1583
1933
|
h("input", {
|
|
1584
|
-
id: fieldId("
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
onChange: (event) => onChange("cron", event.target.value),
|
|
1590
|
-
}),
|
|
1591
|
-
),
|
|
1592
|
-
h(
|
|
1593
|
-
Field,
|
|
1594
|
-
{
|
|
1595
|
-
label: "Time zone",
|
|
1596
|
-
htmlFor: fieldId("timezone"),
|
|
1597
|
-
required: true,
|
|
1598
|
-
hint: "Enter an IANA time zone. Current UTC offsets may change with daylight saving time.",
|
|
1599
|
-
},
|
|
1600
|
-
h(TimeZonePicker, {
|
|
1601
|
-
id: fieldId("timezone"),
|
|
1602
|
-
value: draft.timezone,
|
|
1603
|
-
onChange: (value) => onChange("timezone", value),
|
|
1934
|
+
id: fieldId("enabled"),
|
|
1935
|
+
type: "checkbox",
|
|
1936
|
+
role: "switch",
|
|
1937
|
+
checked: draft.enabled,
|
|
1938
|
+
onChange: (event) => onChange("enabled", event.target.checked),
|
|
1604
1939
|
}),
|
|
1940
|
+
h("span", { className: "dsh-auto-switch-track" }),
|
|
1941
|
+
h("span", { className: "dsh-auto-switch-text" }, draft.enabled ? "Active" : "Paused"),
|
|
1605
1942
|
),
|
|
1943
|
+
),
|
|
1944
|
+
h(
|
|
1945
|
+
FormSection,
|
|
1946
|
+
{
|
|
1947
|
+
title: "Task",
|
|
1948
|
+
description: "Describe the work and choose where the agent should run.",
|
|
1949
|
+
Icon: IconEditOutline16,
|
|
1950
|
+
},
|
|
1606
1951
|
h(
|
|
1607
|
-
|
|
1608
|
-
{
|
|
1952
|
+
"div",
|
|
1953
|
+
{ className: "dsh-auto-formgrid" },
|
|
1609
1954
|
h(
|
|
1610
|
-
|
|
1611
|
-
{
|
|
1955
|
+
Field,
|
|
1956
|
+
{ label: "Name", htmlFor: fieldId("name"), required: true, full: true },
|
|
1612
1957
|
h("input", {
|
|
1613
|
-
id: fieldId("
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1958
|
+
id: fieldId("name"),
|
|
1959
|
+
className: "dsh-auto-input",
|
|
1960
|
+
type: "text",
|
|
1961
|
+
value: draft.name,
|
|
1962
|
+
autoFocus: true,
|
|
1963
|
+
placeholder: "e.g. Morning standup notes",
|
|
1964
|
+
onChange: (event) => onChange("name", event.target.value),
|
|
1617
1965
|
}),
|
|
1618
|
-
h("span", null, "Schedule is active"),
|
|
1619
1966
|
),
|
|
1620
|
-
),
|
|
1621
|
-
h(
|
|
1622
|
-
Field,
|
|
1623
|
-
{ label: "Timeout (ms)", htmlFor: fieldId("timeout"), required: true, hint: "Wall-clock limit for each run (1s to 24h)." },
|
|
1624
|
-
h("input", {
|
|
1625
|
-
id: fieldId("timeout"),
|
|
1626
|
-
className: "dsh-auto-input",
|
|
1627
|
-
type: "number",
|
|
1628
|
-
min: MIN_TIMEOUT_MS,
|
|
1629
|
-
max: MAX_TIMEOUT_MS,
|
|
1630
|
-
step: 1000,
|
|
1631
|
-
value: draft.timeoutMs,
|
|
1632
|
-
onChange: (event) => onChange("timeoutMs", event.target.value),
|
|
1633
|
-
}),
|
|
1634
|
-
),
|
|
1635
|
-
h(
|
|
1636
|
-
Field,
|
|
1637
|
-
{ label: "Provider", htmlFor: fieldId("provider"), hint: "Blank uses the current Harness default. You may type an unlisted provider route." },
|
|
1638
|
-
h(ProviderPicker, {
|
|
1639
|
-
id: fieldId("provider"),
|
|
1640
|
-
value: draft.provider,
|
|
1641
|
-
providers,
|
|
1642
|
-
defaultModel: meta?.defaultModel,
|
|
1643
|
-
onChange: (value) => onChange("provider", value),
|
|
1644
|
-
}),
|
|
1645
|
-
),
|
|
1646
|
-
h(
|
|
1647
|
-
Field,
|
|
1648
|
-
{
|
|
1649
|
-
label: "Model",
|
|
1650
|
-
htmlFor: fieldId("model"),
|
|
1651
|
-
required: draft.provider !== "",
|
|
1652
|
-
hint: draft.provider === ""
|
|
1653
|
-
? "Set a provider first, or leave both blank for the Harness default."
|
|
1654
|
-
: "Choose a discovered model or type an adapter-supported model id.",
|
|
1655
|
-
},
|
|
1656
|
-
h("input", {
|
|
1657
|
-
id: fieldId("model"),
|
|
1658
|
-
className: "dsh-auto-input",
|
|
1659
|
-
type: "text",
|
|
1660
|
-
list: fieldId("model-list"),
|
|
1661
|
-
value: draft.model,
|
|
1662
|
-
spellCheck: false,
|
|
1663
|
-
placeholder: draft.provider === "" ? "Harness default" : "Model id",
|
|
1664
|
-
disabled: draft.provider === "",
|
|
1665
|
-
onChange: (event) => onChange("model", event.target.value),
|
|
1666
|
-
}),
|
|
1667
1967
|
h(
|
|
1668
|
-
|
|
1669
|
-
{
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1968
|
+
Field,
|
|
1969
|
+
{
|
|
1970
|
+
label: "Prompt",
|
|
1971
|
+
htmlFor: fieldId("prompt"),
|
|
1972
|
+
required: true,
|
|
1973
|
+
full: true,
|
|
1974
|
+
hint: "Instructions sent to a fresh agent. Prompts are not copied into run-history summaries.",
|
|
1975
|
+
},
|
|
1976
|
+
h("textarea", {
|
|
1977
|
+
id: fieldId("prompt"),
|
|
1978
|
+
className: "dsh-auto-textarea dsh-auto-prompt",
|
|
1979
|
+
value: draft.prompt,
|
|
1980
|
+
placeholder: "Summarize yesterday's progress and list today's priorities\u2026",
|
|
1981
|
+
onChange: (event) => onChange("prompt", event.target.value),
|
|
1982
|
+
}),
|
|
1983
|
+
),
|
|
1984
|
+
h(
|
|
1985
|
+
Field,
|
|
1986
|
+
{
|
|
1987
|
+
label: "Workspace",
|
|
1988
|
+
htmlFor: fieldId("cwd"),
|
|
1989
|
+
required: true,
|
|
1990
|
+
full: true,
|
|
1991
|
+
hint: "Choose a Harness workspace or enter the absolute directory where the agent runs.",
|
|
1992
|
+
},
|
|
1993
|
+
h(WorkspacePicker, {
|
|
1994
|
+
id: fieldId("cwd"),
|
|
1995
|
+
value: draft.cwd,
|
|
1996
|
+
workspaceSnapshot,
|
|
1997
|
+
onChange: (value) => onChange("cwd", value),
|
|
1998
|
+
}),
|
|
1675
1999
|
),
|
|
1676
2000
|
),
|
|
2001
|
+
),
|
|
2002
|
+
h(
|
|
2003
|
+
FormSection,
|
|
2004
|
+
{
|
|
2005
|
+
title: "Schedule",
|
|
2006
|
+
description: "Choose a common pattern, or switch to Custom for a five-field cron expression.",
|
|
2007
|
+
Icon: IconGlobeOutline14,
|
|
2008
|
+
},
|
|
1677
2009
|
h(
|
|
1678
|
-
|
|
1679
|
-
{
|
|
1680
|
-
h(
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
onChange: (value) => onChange("reasoningEffort", value),
|
|
1687
|
-
}),
|
|
1688
|
-
),
|
|
1689
|
-
h(
|
|
1690
|
-
Field,
|
|
1691
|
-
{ label: "Agent preset", htmlFor: fieldId("preset"), hint: "Uses the current Harness default when not explicitly selected." },
|
|
1692
|
-
h(AgentPresetPicker, {
|
|
1693
|
-
id: fieldId("preset"),
|
|
1694
|
-
value: draft.agentPreset,
|
|
1695
|
-
presets: agentPresets,
|
|
1696
|
-
onChange: (value) => onChange("agentPreset", value),
|
|
1697
|
-
}),
|
|
1698
|
-
),
|
|
1699
|
-
h(
|
|
1700
|
-
Field,
|
|
1701
|
-
{
|
|
1702
|
-
label: "Permission preset",
|
|
1703
|
-
htmlFor: fieldId("permission"),
|
|
1704
|
-
required: true,
|
|
1705
|
-
hint: permissionPresetPresentation(draft.permissionPreset).detail,
|
|
1706
|
-
},
|
|
1707
|
-
h(PermissionPresetPicker, {
|
|
1708
|
-
id: fieldId("permission"),
|
|
1709
|
-
value: draft.permissionPreset,
|
|
1710
|
-
presets: permissionPresets,
|
|
1711
|
-
onChange: (value) => onChange("permissionPreset", value),
|
|
2010
|
+
"div",
|
|
2011
|
+
{ className: "dsh-auto-formgrid dsh-auto-schedule-grid" },
|
|
2012
|
+
h(ScheduleEditor, {
|
|
2013
|
+
fieldId,
|
|
2014
|
+
cron: draft.cron,
|
|
2015
|
+
timezone: draft.timezone,
|
|
2016
|
+
onCronChange: (value) => onChange("cron", value),
|
|
2017
|
+
onTimezoneChange: (value) => onChange("timezone", value),
|
|
1712
2018
|
}),
|
|
1713
2019
|
),
|
|
2020
|
+
),
|
|
2021
|
+
h(
|
|
2022
|
+
FormSection,
|
|
2023
|
+
{
|
|
2024
|
+
title: "Agent & access",
|
|
2025
|
+
description: "Select the model, agent behavior, and filesystem permissions for each fresh run.",
|
|
2026
|
+
Icon: IconAgentPresetOutline16,
|
|
2027
|
+
},
|
|
1714
2028
|
h(
|
|
1715
|
-
|
|
1716
|
-
{
|
|
2029
|
+
"div",
|
|
2030
|
+
{ className: "dsh-auto-formgrid" },
|
|
2031
|
+
h(
|
|
2032
|
+
Field,
|
|
2033
|
+
{ label: "Provider", htmlFor: fieldId("provider"), hint: "Blank uses the current Harness default. Custom provider routes remain supported." },
|
|
2034
|
+
h(ProviderPicker, {
|
|
2035
|
+
id: fieldId("provider"),
|
|
2036
|
+
value: draft.provider,
|
|
2037
|
+
providers,
|
|
2038
|
+
defaultModel: meta?.defaultModel,
|
|
2039
|
+
onChange: (value) => onChange("provider", value),
|
|
2040
|
+
}),
|
|
2041
|
+
),
|
|
1717
2042
|
h(
|
|
1718
|
-
|
|
2043
|
+
Field,
|
|
1719
2044
|
{
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2045
|
+
label: "Model",
|
|
2046
|
+
htmlFor: fieldId("model"),
|
|
2047
|
+
required: draft.provider !== "",
|
|
2048
|
+
hint: draft.provider === ""
|
|
2049
|
+
? "Set a provider first, or leave both blank for the Harness default."
|
|
2050
|
+
: "Choose a discovered model or type an adapter-supported model id.",
|
|
1724
2051
|
},
|
|
1725
|
-
|
|
2052
|
+
h("input", {
|
|
2053
|
+
id: fieldId("model"),
|
|
2054
|
+
className: "dsh-auto-input",
|
|
2055
|
+
type: "text",
|
|
2056
|
+
list: fieldId("model-list"),
|
|
2057
|
+
value: draft.model,
|
|
2058
|
+
spellCheck: false,
|
|
2059
|
+
placeholder: draft.provider === "" ? "Harness default" : "Model id",
|
|
2060
|
+
disabled: draft.provider === "",
|
|
2061
|
+
onChange: (event) => onChange("model", event.target.value),
|
|
2062
|
+
}),
|
|
2063
|
+
h(
|
|
2064
|
+
"datalist",
|
|
2065
|
+
{ id: fieldId("model-list") },
|
|
2066
|
+
models.map((model) => h("option", {
|
|
2067
|
+
key: model.id,
|
|
2068
|
+
value: model.id,
|
|
2069
|
+
label: model.name === model.id ? undefined : model.name,
|
|
2070
|
+
})),
|
|
2071
|
+
),
|
|
2072
|
+
),
|
|
2073
|
+
h(
|
|
2074
|
+
Field,
|
|
2075
|
+
{ label: "Reasoning effort", htmlFor: fieldId("effort"), hint: "Default follows the selected model. Custom adapter-owned IDs remain supported." },
|
|
2076
|
+
h(ReasoningEffortPicker, {
|
|
2077
|
+
id: fieldId("effort"),
|
|
2078
|
+
value: draft.reasoningEffort,
|
|
2079
|
+
provider: draft.provider,
|
|
2080
|
+
model: draft.model,
|
|
2081
|
+
defaultModel: meta?.defaultModel,
|
|
2082
|
+
onChange: (value) => onChange("reasoningEffort", value),
|
|
2083
|
+
}),
|
|
2084
|
+
),
|
|
2085
|
+
h(
|
|
2086
|
+
Field,
|
|
2087
|
+
{ label: "Agent preset", htmlFor: fieldId("preset"), hint: "Uses the current Harness default when not explicitly selected." },
|
|
2088
|
+
h(AgentPresetPicker, {
|
|
2089
|
+
id: fieldId("preset"),
|
|
2090
|
+
value: draft.agentPreset,
|
|
2091
|
+
presets: agentPresets,
|
|
2092
|
+
onChange: (value) => onChange("agentPreset", value),
|
|
2093
|
+
}),
|
|
1726
2094
|
),
|
|
1727
|
-
),
|
|
1728
|
-
h(
|
|
1729
|
-
Field,
|
|
1730
|
-
{ label: "Misfire policy", htmlFor: fieldId("misfire"), hint: "How a missed occurrence is handled after the scheduler is back." },
|
|
1731
2095
|
h(
|
|
1732
|
-
|
|
2096
|
+
Field,
|
|
1733
2097
|
{
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
2098
|
+
label: "Permission preset",
|
|
2099
|
+
htmlFor: fieldId("permission"),
|
|
2100
|
+
required: true,
|
|
2101
|
+
full: true,
|
|
2102
|
+
hint: permissionPresetPresentation(draft.permissionPreset).detail,
|
|
1738
2103
|
},
|
|
1739
|
-
|
|
2104
|
+
h(PermissionPresetPicker, {
|
|
2105
|
+
id: fieldId("permission"),
|
|
2106
|
+
value: draft.permissionPreset,
|
|
2107
|
+
presets: permissionPresets,
|
|
2108
|
+
onChange: (value) => onChange("permissionPreset", value),
|
|
2109
|
+
}),
|
|
1740
2110
|
),
|
|
1741
2111
|
),
|
|
2112
|
+
),
|
|
2113
|
+
h(
|
|
2114
|
+
"section",
|
|
2115
|
+
{ className: "dsh-auto-advanced", "data-open": advancedOpen ? "true" : undefined },
|
|
1742
2116
|
h(
|
|
1743
|
-
|
|
2117
|
+
"button",
|
|
1744
2118
|
{
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2119
|
+
type: "button",
|
|
2120
|
+
className: "dsh-auto-advanced-trigger",
|
|
2121
|
+
"aria-expanded": advancedOpen,
|
|
2122
|
+
"aria-controls": fieldId("advanced-content"),
|
|
2123
|
+
onClick: () => setAdvancedOpen((open) => !open),
|
|
1750
2124
|
},
|
|
1751
|
-
h(
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
className: "dsh-auto-textarea",
|
|
1764
|
-
value: draft.prompt,
|
|
1765
|
-
placeholder: "Summarize yesterday's progress and list today's priorities\u2026",
|
|
1766
|
-
onChange: (event) => onChange("prompt", event.target.value),
|
|
1767
|
-
}),
|
|
2125
|
+
h("span", { className: "dsh-auto-form-section-icon", "aria-hidden": "true" }, h(IconSettingsOutline16)),
|
|
2126
|
+
h(
|
|
2127
|
+
"span",
|
|
2128
|
+
{ className: "dsh-auto-advanced-copy" },
|
|
2129
|
+
h("span", { className: "dsh-auto-form-section-title" }, "Advanced"),
|
|
2130
|
+
h("span", { className: "dsh-auto-advanced-summary" }, advancedSummary),
|
|
2131
|
+
),
|
|
2132
|
+
h(
|
|
2133
|
+
"span",
|
|
2134
|
+
{ className: "dsh-auto-advanced-chevron" + (advancedOpen ? " dsh-auto-advanced-chevron-open" : ""), "aria-hidden": "true" },
|
|
2135
|
+
h(IconChevronDownOutline14),
|
|
2136
|
+
),
|
|
1768
2137
|
),
|
|
1769
|
-
),
|
|
1770
|
-
error
|
|
1771
|
-
? h("p", { className: "dsh-auto-formerror", role: "alert" }, error)
|
|
1772
|
-
: null,
|
|
1773
|
-
h(
|
|
1774
|
-
"div",
|
|
1775
|
-
{ className: "dsh-auto-formactions" },
|
|
1776
2138
|
h(
|
|
1777
|
-
|
|
2139
|
+
"div",
|
|
1778
2140
|
{
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
disabled: saving,
|
|
2141
|
+
id: fieldId("advanced-content"),
|
|
2142
|
+
className: "dsh-auto-formgrid dsh-auto-advanced-content",
|
|
2143
|
+
hidden: !advancedOpen,
|
|
1783
2144
|
},
|
|
1784
|
-
|
|
2145
|
+
creating
|
|
2146
|
+
? h(
|
|
2147
|
+
Field,
|
|
2148
|
+
{ label: "Job ID", htmlFor: fieldId("id"), hint: "Auto-derived from the name. Use lowercase letters, digits, and hyphens." },
|
|
2149
|
+
h("input", {
|
|
2150
|
+
id: fieldId("id"),
|
|
2151
|
+
className: "dsh-auto-input",
|
|
2152
|
+
type: "text",
|
|
2153
|
+
value: draft.id,
|
|
2154
|
+
spellCheck: false,
|
|
2155
|
+
placeholder: "auto",
|
|
2156
|
+
onChange: (event) => {
|
|
2157
|
+
onIdTouched();
|
|
2158
|
+
onChange("id", event.target.value);
|
|
2159
|
+
},
|
|
2160
|
+
}),
|
|
2161
|
+
)
|
|
2162
|
+
: h(
|
|
2163
|
+
Field,
|
|
2164
|
+
{ label: "Job ID", htmlFor: fieldId("id"), hint: "Fixed after creation." },
|
|
2165
|
+
h("input", {
|
|
2166
|
+
id: fieldId("id"),
|
|
2167
|
+
className: "dsh-auto-input",
|
|
2168
|
+
type: "text",
|
|
2169
|
+
value: draft.id,
|
|
2170
|
+
disabled: true,
|
|
2171
|
+
spellCheck: false,
|
|
2172
|
+
}),
|
|
2173
|
+
),
|
|
2174
|
+
h(
|
|
2175
|
+
Field,
|
|
2176
|
+
{ label: "Timeout (ms)", htmlFor: fieldId("timeout"), required: true, hint: "Wall-clock limit for each run (1s to 24h)." },
|
|
2177
|
+
h("input", {
|
|
2178
|
+
id: fieldId("timeout"),
|
|
2179
|
+
className: "dsh-auto-input",
|
|
2180
|
+
type: "number",
|
|
2181
|
+
min: MIN_TIMEOUT_MS,
|
|
2182
|
+
max: MAX_TIMEOUT_MS,
|
|
2183
|
+
step: 1000,
|
|
2184
|
+
value: draft.timeoutMs,
|
|
2185
|
+
onChange: (event) => onChange("timeoutMs", event.target.value),
|
|
2186
|
+
}),
|
|
2187
|
+
),
|
|
2188
|
+
h(
|
|
2189
|
+
Field,
|
|
2190
|
+
{ label: "Overlap policy", htmlFor: fieldId("overlap"), hint: OVERLAP_OPTIONS.find((option) => option.value === draft.overlap)?.hint },
|
|
2191
|
+
h(
|
|
2192
|
+
"select",
|
|
2193
|
+
{
|
|
2194
|
+
id: fieldId("overlap"),
|
|
2195
|
+
className: "dsh-auto-select",
|
|
2196
|
+
value: draft.overlap,
|
|
2197
|
+
onChange: (event) => onChange("overlap", event.target.value),
|
|
2198
|
+
},
|
|
2199
|
+
OVERLAP_OPTIONS.map((option) => h("option", { key: option.value, value: option.value }, option.label)),
|
|
2200
|
+
),
|
|
2201
|
+
),
|
|
2202
|
+
h(
|
|
2203
|
+
Field,
|
|
2204
|
+
{ label: "Misfire policy", htmlFor: fieldId("misfire"), hint: MISFIRE_OPTIONS.find((option) => option.value === draft.misfire)?.hint },
|
|
2205
|
+
h(
|
|
2206
|
+
"select",
|
|
2207
|
+
{
|
|
2208
|
+
id: fieldId("misfire"),
|
|
2209
|
+
className: "dsh-auto-select",
|
|
2210
|
+
value: draft.misfire,
|
|
2211
|
+
onChange: (event) => onChange("misfire", event.target.value),
|
|
2212
|
+
},
|
|
2213
|
+
MISFIRE_OPTIONS.map((option) => h("option", { key: option.value, value: option.value }, option.label)),
|
|
2214
|
+
),
|
|
2215
|
+
),
|
|
1785
2216
|
),
|
|
2217
|
+
),
|
|
2218
|
+
h(
|
|
2219
|
+
"footer",
|
|
2220
|
+
{ className: "dsh-auto-form-footer" },
|
|
2221
|
+
error
|
|
2222
|
+
? h("p", { id: errorId, className: "dsh-auto-formerror", role: "alert" }, error)
|
|
2223
|
+
: null,
|
|
1786
2224
|
h(
|
|
1787
|
-
|
|
1788
|
-
{
|
|
1789
|
-
|
|
2225
|
+
"div",
|
|
2226
|
+
{ className: "dsh-auto-formactions" },
|
|
2227
|
+
h(
|
|
2228
|
+
Button,
|
|
2229
|
+
{
|
|
2230
|
+
type: "submit",
|
|
2231
|
+
variant: "primary",
|
|
2232
|
+
icon: saving ? null : h(IconCheckOutline16),
|
|
2233
|
+
disabled: saving,
|
|
2234
|
+
},
|
|
2235
|
+
saving ? "Saving\u2026" : creating ? "Create automation" : "Save changes",
|
|
2236
|
+
),
|
|
2237
|
+
h(
|
|
2238
|
+
Button,
|
|
2239
|
+
{ type: "button", variant: "outline", onClick: onCancel, disabled: saving },
|
|
2240
|
+
"Cancel",
|
|
2241
|
+
),
|
|
1790
2242
|
),
|
|
1791
2243
|
),
|
|
1792
2244
|
);
|
|
@@ -2070,6 +2522,7 @@ window.__ModuleLoader__.load({
|
|
|
2070
2522
|
const [editing, setEditing] = useState(null);
|
|
2071
2523
|
const [draft, setDraft] = useState(() => emptyDraft(null));
|
|
2072
2524
|
const [formError, setFormError] = useState(null);
|
|
2525
|
+
const [formErrorField, setFormErrorField] = useState(null);
|
|
2073
2526
|
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
2074
2527
|
const [confirmCancelId, setConfirmCancelId] = useState(null);
|
|
2075
2528
|
|
|
@@ -2226,6 +2679,7 @@ window.__ModuleLoader__.load({
|
|
|
2226
2679
|
workspaceTouchedRef.current = false;
|
|
2227
2680
|
setDraft(Object.assign(emptyDraft(meta), { cwd: preferredWorkspacePath(workspaceSnapshot) }));
|
|
2228
2681
|
setFormError(null);
|
|
2682
|
+
setFormErrorField(null);
|
|
2229
2683
|
setFormOpen(true);
|
|
2230
2684
|
}, [meta, workspaceSnapshot]);
|
|
2231
2685
|
|
|
@@ -2235,6 +2689,7 @@ window.__ModuleLoader__.load({
|
|
|
2235
2689
|
workspaceTouchedRef.current = true;
|
|
2236
2690
|
setDraft(draftFromJob(job));
|
|
2237
2691
|
setFormError(null);
|
|
2692
|
+
setFormErrorField(null);
|
|
2238
2693
|
setFormOpen(true);
|
|
2239
2694
|
}, []);
|
|
2240
2695
|
|
|
@@ -2242,6 +2697,7 @@ window.__ModuleLoader__.load({
|
|
|
2242
2697
|
setFormOpen(false);
|
|
2243
2698
|
setEditing(null);
|
|
2244
2699
|
setFormError(null);
|
|
2700
|
+
setFormErrorField(null);
|
|
2245
2701
|
}, []);
|
|
2246
2702
|
|
|
2247
2703
|
useEffect(() => {
|
|
@@ -2254,6 +2710,8 @@ window.__ModuleLoader__.load({
|
|
|
2254
2710
|
const handleDraftChange = useCallback(
|
|
2255
2711
|
(key, value) => {
|
|
2256
2712
|
if (key === "cwd") workspaceTouchedRef.current = true;
|
|
2713
|
+
setFormError(null);
|
|
2714
|
+
setFormErrorField(null);
|
|
2257
2715
|
setDraft((previous) => {
|
|
2258
2716
|
let next = Object.assign({}, previous, { [key]: value });
|
|
2259
2717
|
if (key === "name" && editing === null && !idTouchedRef.current) {
|
|
@@ -2278,43 +2736,52 @@ window.__ModuleLoader__.load({
|
|
|
2278
2736
|
async (event) => {
|
|
2279
2737
|
event.preventDefault();
|
|
2280
2738
|
const creating = editing === null;
|
|
2739
|
+
const fail = (field, message) => {
|
|
2740
|
+
setFormErrorField(field);
|
|
2741
|
+
setFormError(message);
|
|
2742
|
+
};
|
|
2281
2743
|
|
|
2282
2744
|
const name = draft.name.trim();
|
|
2283
|
-
if (name === "") return
|
|
2745
|
+
if (name === "") return fail("name", "Give the automation a name.");
|
|
2284
2746
|
const cron = draft.cron.trim();
|
|
2285
|
-
if (cron === "") return
|
|
2747
|
+
if (cron === "") return fail("cron", "A cron expression is required, e.g. 0 9 * * 1-5.");
|
|
2748
|
+
if (cronFields(cron).length !== 5) {
|
|
2749
|
+
return fail("cron", "Cron needs exactly five fields: minute, hour, day, month, and weekday.");
|
|
2750
|
+
}
|
|
2286
2751
|
const timezone = draft.timezone.trim();
|
|
2287
|
-
if (timezone === "") return
|
|
2752
|
+
if (timezone === "") return fail("timezone", "A timezone is required.");
|
|
2288
2753
|
const cwd = draft.cwd.trim();
|
|
2289
|
-
if (cwd === "") return
|
|
2754
|
+
if (cwd === "") return fail("cwd", "A workspace is required.");
|
|
2290
2755
|
if (!looksLikeAbsolutePath(cwd)) {
|
|
2291
|
-
return
|
|
2756
|
+
return fail("cwd", "The workspace must be an absolute filesystem path.");
|
|
2292
2757
|
}
|
|
2293
2758
|
const prompt = draft.prompt.trim();
|
|
2294
|
-
if (prompt === "") return
|
|
2759
|
+
if (prompt === "") return fail("prompt", "A prompt is required.");
|
|
2295
2760
|
const timeoutMs = Number(draft.timeoutMs);
|
|
2296
2761
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < MIN_TIMEOUT_MS || timeoutMs > MAX_TIMEOUT_MS) {
|
|
2297
|
-
return
|
|
2762
|
+
return fail(
|
|
2763
|
+
"timeout",
|
|
2298
2764
|
"Timeout must be a whole number of milliseconds between " + MIN_TIMEOUT_MS + " and " + MAX_TIMEOUT_MS + ".",
|
|
2299
2765
|
);
|
|
2300
2766
|
}
|
|
2301
2767
|
const provider = draft.provider.trim();
|
|
2302
2768
|
const model = draft.model.trim();
|
|
2303
2769
|
if (provider !== "" && model === "") {
|
|
2304
|
-
return
|
|
2770
|
+
return fail("model", "Pick a model for the chosen provider (or leave both blank for the Harness default).");
|
|
2305
2771
|
}
|
|
2306
2772
|
if (provider === "" && model !== "") {
|
|
2307
|
-
return
|
|
2773
|
+
return fail("provider", "A model needs a provider (or leave both blank for the Harness default).");
|
|
2308
2774
|
}
|
|
2309
2775
|
let id = "";
|
|
2310
2776
|
if (creating) {
|
|
2311
2777
|
id = draft.id.trim().toLowerCase();
|
|
2312
2778
|
if (id !== "" && !JOB_ID_PATTERN.test(id)) {
|
|
2313
|
-
return
|
|
2779
|
+
return fail("id", "Job id must match [a-z0-9][a-z0-9-]{0,62} (lowercase letters, digits, hyphens).");
|
|
2314
2780
|
}
|
|
2315
2781
|
}
|
|
2316
2782
|
const spec = buildSpec(draft);
|
|
2317
2783
|
setFormError(null);
|
|
2784
|
+
setFormErrorField(null);
|
|
2318
2785
|
setBusyKey("save", true);
|
|
2319
2786
|
try {
|
|
2320
2787
|
if (creating) {
|
|
@@ -2332,7 +2799,9 @@ window.__ModuleLoader__.load({
|
|
|
2332
2799
|
flashMessage("success", creating ? "Automation created." : "Automation updated.");
|
|
2333
2800
|
closeForm();
|
|
2334
2801
|
} catch (error) {
|
|
2335
|
-
|
|
2802
|
+
const message = errMessage(error);
|
|
2803
|
+
setFormErrorField(inferFormErrorField(message));
|
|
2804
|
+
setFormError(message);
|
|
2336
2805
|
} finally {
|
|
2337
2806
|
setBusyKey("save", false);
|
|
2338
2807
|
}
|
|
@@ -2389,12 +2858,14 @@ window.__ModuleLoader__.load({
|
|
|
2389
2858
|
: null,
|
|
2390
2859
|
formOpen
|
|
2391
2860
|
? h(JobForm, {
|
|
2861
|
+
key: editing === null ? "create" : "edit:" + editing.id,
|
|
2392
2862
|
meta,
|
|
2393
2863
|
workspaceSnapshot,
|
|
2394
2864
|
draft,
|
|
2395
2865
|
editing,
|
|
2396
2866
|
saving: busy.save === true,
|
|
2397
2867
|
error: formError,
|
|
2868
|
+
errorField: formErrorField,
|
|
2398
2869
|
onChange: handleDraftChange,
|
|
2399
2870
|
onIdTouched: handleIdTouched,
|
|
2400
2871
|
onSubmit: handleSubmit,
|
|
@@ -2635,7 +3106,7 @@ window.__ModuleLoader__.load({
|
|
|
2635
3106
|
".dsh-auto-root *,.dsh-auto-root *::before,.dsh-auto-root *::after{box-sizing:border-box;}",
|
|
2636
3107
|
".dsh-auto-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}",
|
|
2637
3108
|
".dsh-auto-head h2{margin:0;font-size:18px;font-weight:600;line-height:1.35;}",
|
|
2638
|
-
".dsh-auto-sub{margin:3px 0 0;font-size:
|
|
3109
|
+
".dsh-auto-sub{margin:3px 0 0;font-size:13px;line-height:20px;color:var(--dsh-auto-muted);max-width:560px;}",
|
|
2639
3110
|
".dsh-auto-flash{margin:0;padding:9px 12px;border-radius:10px;font-size:13px;line-height:1.45;word-break:break-word;}",
|
|
2640
3111
|
".dsh-auto-flash-error{background:color-mix(in srgb,var(--dsh-auto-danger) 12%,transparent);color:var(--dsh-auto-danger);}",
|
|
2641
3112
|
".dsh-auto-flash-success{background:color-mix(in srgb,var(--dsh-auto-success) 12%,transparent);color:var(--dsh-auto-success);}",
|
|
@@ -2643,14 +3114,15 @@ window.__ModuleLoader__.load({
|
|
|
2643
3114
|
".dsh-auto-linkbtn{appearance:none;font:inherit;font-size:inherit;color:inherit;text-decoration:underline;cursor:pointer;background:none;border:none;padding:0;}",
|
|
2644
3115
|
".dsh-auto-danger-button{color:var(--dsh-auto-danger);}",
|
|
2645
3116
|
".dsh-auto-danger-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger,color-mix(in srgb,var(--dsh-auto-danger) 10%,transparent));}",
|
|
2646
|
-
".dsh-auto-input,.dsh-auto-select,.dsh-auto-textarea{width:100%;min-height:
|
|
3117
|
+
".dsh-auto-input,.dsh-auto-select,.dsh-auto-textarea{width:100%;min-height:36px;padding:6px 11px;border:1px solid var(--dsh-auto-border);border-radius:8px;background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);font-family:inherit;font-size:14px;line-height:22px;}",
|
|
2647
3118
|
".dsh-auto-input,.dsh-auto-textarea{appearance:none;}",
|
|
2648
3119
|
".dsh-auto-input::placeholder,.dsh-auto-textarea::placeholder{color:var(--dsh-auto-caption);}",
|
|
2649
3120
|
".dsh-auto-select option{background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);}",
|
|
2650
3121
|
".dsh-auto-input:focus-visible,.dsh-auto-select:focus-visible,.dsh-auto-textarea:focus-visible{outline:none;border-color:var(--dsh-auto-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsh-auto-accent) 18%,transparent);}",
|
|
3122
|
+
".dsh-auto-input[aria-invalid=\"true\"],.dsh-auto-select[aria-invalid=\"true\"],.dsh-auto-textarea[aria-invalid=\"true\"]{border-color:var(--dsh-auto-danger);}",
|
|
2651
3123
|
".dsh-auto-input:disabled,.dsh-auto-select:disabled,.dsh-auto-textarea:disabled{opacity:1;color:var(--dsh-auto-muted);background:var(--dsh-auto-surface-active);cursor:default;}",
|
|
2652
3124
|
".dsh-auto-picker-root{width:100%;display:flex;}",
|
|
2653
|
-
".dsh-auto-picker-trigger{appearance:none;width:100%;height:
|
|
3125
|
+
".dsh-auto-picker-trigger{appearance:none;width:100%;height:36px;padding:0 11px;border:1px solid var(--dsh-auto-border);border-radius:8px;background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);display:flex;align-items:center;gap:8px;font-family:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer;}",
|
|
2654
3126
|
".dsh-auto-picker-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsh-auto-input-bg));}",
|
|
2655
3127
|
".dsh-auto-picker-trigger:focus-visible{outline:none;border-color:var(--dsh-auto-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsh-auto-accent) 18%,transparent);}",
|
|
2656
3128
|
".dsh-auto-picker-trigger-icon{flex:none;color:var(--dsh-auto-muted);}",
|
|
@@ -2660,13 +3132,13 @@ window.__ModuleLoader__.load({
|
|
|
2660
3132
|
".dsh-auto-picker-item-copy{min-width:0;display:flex;flex-direction:column;white-space:normal;}",
|
|
2661
3133
|
".dsh-auto-picker-item-label{color:inherit;font-size:14px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
2662
3134
|
".dsh-auto-picker-item-detail{color:var(--dsh-auto-muted,var(--dsw-alias-label-tertiary,#81858c));font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
|
|
2663
|
-
".dsh-auto-combobox-control{width:100%;height:
|
|
3135
|
+
".dsh-auto-combobox-control{width:100%;height:36px;padding:0 10px;display:flex;align-items:center;gap:8px;border:1px solid var(--dsh-auto-border);border-radius:8px;background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);cursor:text;}",
|
|
2664
3136
|
".dsh-auto-combobox-control:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsh-auto-input-bg));}",
|
|
2665
3137
|
".dsh-auto-combobox-control:focus-within,.dsh-auto-combobox-control[data-open=\"true\"]{border-color:var(--dsh-auto-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsh-auto-accent) 18%,transparent);}",
|
|
2666
3138
|
".dsh-auto-combobox-control:has(.dsh-auto-combobox-input[aria-invalid=\"true\"]){border-color:var(--dsh-auto-danger);}",
|
|
2667
3139
|
".dsh-auto-combobox-control:has(.dsh-auto-combobox-input:disabled){background:var(--dsh-auto-surface-active);color:var(--dsh-auto-muted);cursor:default;}",
|
|
2668
3140
|
".dsh-auto-combobox-control-icon,.dsh-auto-combobox-chevron{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--dsh-auto-muted);pointer-events:none;}",
|
|
2669
|
-
".dsh-auto-combobox-input{appearance:none;min-width:0;height:100%;flex:1;padding:0;border:0;outline:0;background:transparent;color:inherit;font:inherit;font-size:
|
|
3141
|
+
".dsh-auto-combobox-input{appearance:none;min-width:0;height:100%;flex:1;padding:0;border:0;outline:0;background:transparent;color:inherit;font:inherit;font-size:14px;line-height:22px;}",
|
|
2670
3142
|
".dsh-auto-combobox-input::placeholder{color:var(--dsh-auto-caption);}",
|
|
2671
3143
|
".dsh-auto-combobox-input:disabled{cursor:default;}",
|
|
2672
3144
|
".dsh-auto-combobox-chevron{transition:transform .12s ease;}",
|
|
@@ -2692,18 +3164,53 @@ window.__ModuleLoader__.load({
|
|
|
2692
3164
|
".dsh-auto-permission-glyph-write{color:var(--dsh-auto-accent);}",
|
|
2693
3165
|
".dsh-auto-permission-glyph-danger{color:var(--dsh-auto-danger);}",
|
|
2694
3166
|
".dsh-auto-permission-glyph-custom{color:var(--dsh-auto-muted);}",
|
|
2695
|
-
".dsh-auto-textarea{resize:vertical;min-height:
|
|
3167
|
+
".dsh-auto-textarea{resize:vertical;min-height:120px;}",
|
|
3168
|
+
".dsh-auto-prompt{min-height:154px;line-height:22px;}",
|
|
2696
3169
|
".dsh-auto-check{display:inline-flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;min-height:34px;}",
|
|
2697
3170
|
".dsh-auto-check input{width:16px;height:16px;margin:0;accent-color:var(--dsh-auto-accent);cursor:pointer;}",
|
|
2698
|
-
".dsh-auto-form{border:
|
|
2699
|
-
".dsh-auto-form
|
|
2700
|
-
".dsh-auto-
|
|
2701
|
-
".dsh-auto-
|
|
3171
|
+
".dsh-auto-form{box-sizing:border-box;width:min(100%,780px);margin:0 auto;padding:0 0 4px;background:transparent;display:flex;flex-direction:column;gap:18px;}",
|
|
3172
|
+
".dsh-auto-form-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:2px 2px 0;}",
|
|
3173
|
+
".dsh-auto-form-heading{min-width:0;display:flex;flex-direction:column;gap:4px;}",
|
|
3174
|
+
".dsh-auto-form h3{margin:0;font-size:18px;font-weight:600;line-height:26px;}",
|
|
3175
|
+
".dsh-auto-form-heading p{margin:0;color:var(--dsh-auto-muted);font-size:13px;line-height:20px;}",
|
|
3176
|
+
".dsh-auto-form-enabled{flex:none;margin-top:1px;padding:6px 10px;border:1px solid var(--dsh-auto-border);border-radius:999px;background:var(--dsh-auto-surface);font-size:13px;line-height:20px;}",
|
|
3177
|
+
".dsh-auto-form-section,.dsh-auto-advanced{border:1px solid var(--dsh-auto-border);border-radius:14px;background:var(--dsh-auto-surface);box-shadow:0 1px 1px color-mix(in srgb,var(--dsh-auto-text) 4%,transparent);}",
|
|
3178
|
+
".dsh-auto-form-section{padding:18px;display:flex;flex-direction:column;gap:18px;}",
|
|
3179
|
+
".dsh-auto-form-section-head{display:flex;align-items:flex-start;gap:12px;}",
|
|
3180
|
+
".dsh-auto-form-section-icon{width:34px;height:34px;flex:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsh-auto-accent);background:color-mix(in srgb,var(--dsh-auto-accent) 10%,transparent);}",
|
|
3181
|
+
".dsh-auto-form-section-copy{min-width:0;display:flex;flex-direction:column;gap:2px;}",
|
|
3182
|
+
".dsh-auto-form-section-title{margin:0;color:var(--dsh-auto-text);font-size:16px;font-weight:600;line-height:22px;}",
|
|
3183
|
+
".dsh-auto-form-section-description{margin:0;color:var(--dsh-auto-muted);font-size:12px;line-height:18px;}",
|
|
3184
|
+
".dsh-auto-formgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 14px;}",
|
|
3185
|
+
".dsh-auto-field{display:flex;flex-direction:column;gap:6px;min-width:0;}",
|
|
2702
3186
|
".dsh-auto-field-full{grid-column:1/-1;}",
|
|
2703
|
-
".dsh-auto-label{font-size:
|
|
3187
|
+
".dsh-auto-label{font-size:14px;font-weight:400;line-height:22px;color:var(--dsh-auto-text-secondary);}",
|
|
2704
3188
|
".dsh-auto-required{color:var(--dsh-auto-danger);}",
|
|
2705
|
-
".dsh-auto-hint{margin:0;font-size:
|
|
2706
|
-
".dsh-auto-
|
|
3189
|
+
".dsh-auto-hint{margin:0;font-size:12px;line-height:18px;color:var(--dsh-auto-muted);}",
|
|
3190
|
+
".dsh-auto-schedule-modes{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
|
|
3191
|
+
".dsh-auto-schedule-modes button{height:32px;padding:0 11px;border-radius:16px;font-size:14px;line-height:22px;}",
|
|
3192
|
+
".dsh-auto-schedule-summary{min-height:48px;padding:11px 13px;border:1px solid color-mix(in srgb,var(--dsh-auto-accent) 20%,var(--dsh-auto-border));border-radius:10px;background:color-mix(in srgb,var(--dsh-auto-accent) 6%,var(--dsh-auto-input-bg));display:flex;align-items:center;justify-content:space-between;gap:12px;}",
|
|
3193
|
+
".dsh-auto-schedule-summary-copy{min-width:0;color:var(--dsh-auto-text-secondary);font-size:14px;font-weight:400;line-height:22px;}",
|
|
3194
|
+
".dsh-auto-schedule-expression{min-width:0;max-width:100%;flex:none;padding:3px 7px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));color:var(--dsh-auto-muted);font-size:12px;line-height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}",
|
|
3195
|
+
".dsh-auto-cron-input{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;letter-spacing:.02em;}",
|
|
3196
|
+
".dsh-auto-cron-guide{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px;margin-top:4px;}",
|
|
3197
|
+
".dsh-auto-cron-guide-field{min-width:0;padding:9px 7px;border:1px solid var(--dsh-auto-border);border-radius:9px;background:var(--dsh-auto-input-bg);text-align:center;display:flex;flex-direction:column;align-items:center;gap:2px;}",
|
|
3198
|
+
".dsh-auto-cron-guide-value{max-width:100%;overflow:hidden;text-overflow:ellipsis;color:var(--dsh-auto-text);font-size:14px;font-weight:600;line-height:20px;}",
|
|
3199
|
+
".dsh-auto-cron-guide-label{color:var(--dsh-auto-text-secondary);font-size:12px;font-weight:500;line-height:18px;}",
|
|
3200
|
+
".dsh-auto-cron-guide-range{color:var(--dsh-auto-caption);font-size:10px;line-height:14px;}",
|
|
3201
|
+
".dsh-auto-cron-syntax{margin:0;color:var(--dsh-auto-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:18px;}",
|
|
3202
|
+
".dsh-auto-advanced{overflow:hidden;}",
|
|
3203
|
+
".dsh-auto-advanced-trigger{box-sizing:border-box;width:100%;min-height:72px;padding:16px 18px;border:0;background:transparent;color:inherit;text-align:left;font:inherit;display:flex;align-items:center;gap:12px;cursor:pointer;}",
|
|
3204
|
+
".dsh-auto-advanced-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));}",
|
|
3205
|
+
".dsh-auto-advanced-trigger:focus-visible{outline:2px solid var(--dsh-auto-accent);outline-offset:-2px;border-radius:13px;}",
|
|
3206
|
+
".dsh-auto-advanced-copy{min-width:0;flex:1;display:flex;flex-direction:column;gap:2px;}",
|
|
3207
|
+
".dsh-auto-advanced-summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsh-auto-muted);font-size:12px;line-height:18px;}",
|
|
3208
|
+
".dsh-auto-advanced-chevron{flex:none;color:var(--dsh-auto-muted);transition:transform .15s ease;}",
|
|
3209
|
+
".dsh-auto-advanced-chevron-open{transform:rotate(180deg);}",
|
|
3210
|
+
".dsh-auto-advanced-content{margin:0 18px;padding:18px 0;border-top:1px solid var(--dsh-auto-border);}",
|
|
3211
|
+
".dsh-auto-advanced-content[hidden]{display:none;}",
|
|
3212
|
+
".dsh-auto-form-footer{position:sticky;z-index:4;bottom:8px;align-self:flex-end;max-width:100%;padding:10px 11px;border:1px solid var(--dsh-auto-border);border-radius:12px;background:var(--dsh-auto-surface);box-shadow:var(--dsw-shadow-lv2,0 6px 18px rgba(15,17,21,.12));display:flex;flex-direction:column;gap:8px;}",
|
|
3213
|
+
".dsh-auto-formerror{margin:0;padding:9px 11px;border-radius:8px;background:color-mix(in srgb,var(--dsh-auto-danger) 10%,transparent);color:var(--dsh-auto-danger);font-size:13px;line-height:20px;}",
|
|
2707
3214
|
".dsh-auto-formactions{display:flex;gap:8px;justify-content:flex-end;align-items:center;flex-wrap:wrap;}",
|
|
2708
3215
|
".dsh-auto-jobs{display:flex;flex-direction:column;gap:10px;}",
|
|
2709
3216
|
".dsh-auto-card{border:1px solid var(--dsh-auto-border);border-radius:12px;padding:12px 14px;background:var(--dsh-auto-surface);display:flex;flex-direction:column;gap:10px;}",
|
|
@@ -2752,8 +3259,10 @@ window.__ModuleLoader__.load({
|
|
|
2752
3259
|
"@keyframes dsh-auto-pulse{0%,100%{opacity:1}50%{opacity:.35}}",
|
|
2753
3260
|
".dsh-auto-empty,.dsh-auto-loading,.dsh-auto-error{padding:22px 16px;text-align:center;border:1px dashed var(--dsh-auto-border);border-radius:12px;font-size:13px;color:var(--dsh-auto-text-secondary);}",
|
|
2754
3261
|
".dsh-auto-error{color:var(--dsh-auto-danger);display:flex;flex-direction:column;gap:10px;align-items:center;}",
|
|
2755
|
-
"@container dsh-auto-workspace (max-width:680px){.dsh-auto-formgrid{grid-template-columns:1fr;}.dsh-auto-card-head{flex-direction:column;align-items:flex-start;}.dsh-auto-run-name{max-width:150px;}}",
|
|
2756
|
-
"@
|
|
3262
|
+
"@container dsh-auto-workspace (max-width:680px){.dsh-auto-formgrid{grid-template-columns:1fr;}.dsh-auto-form-header{align-items:center;}.dsh-auto-schedule-summary{align-items:flex-start;flex-direction:column;}.dsh-auto-card-head{flex-direction:column;align-items:flex-start;}.dsh-auto-run-name{max-width:150px;}}",
|
|
3263
|
+
"@container dsh-auto-workspace (max-width:460px){.dsh-auto-form-header{align-items:flex-start;flex-direction:column;}.dsh-auto-cron-guide{grid-template-columns:repeat(2,minmax(0,1fr));}.dsh-auto-cron-guide-field:last-child{grid-column:1/-1;}}",
|
|
3264
|
+
"@media (max-width:640px){.dsh-auto-workspace-toolbar{padding-inline:8px;}.dsh-auto-workspace-scroll{padding:16px 14px 28px;}.dsh-auto-formgrid{grid-template-columns:1fr;}.dsh-auto-form-header{align-items:center;}.dsh-auto-form-section{padding:16px;}.dsh-auto-advanced-trigger{padding:14px 15px;}.dsh-auto-advanced-content{margin-inline:15px;padding-block:16px;}.dsh-auto-schedule-summary{align-items:flex-start;flex-direction:column;}.dsh-auto-cron-guide{gap:4px;}.dsh-auto-cron-guide-field{padding-inline:4px;}.dsh-auto-card-head{flex-direction:column;align-items:flex-start;}.dsh-auto-run-name{max-width:150px;}}",
|
|
3265
|
+
"@media (max-width:480px){.dsh-auto-form-header{align-items:flex-start;flex-direction:column;}.dsh-auto-cron-guide{grid-template-columns:repeat(2,minmax(0,1fr));}.dsh-auto-cron-guide-field:last-child{grid-column:1/-1;}}",
|
|
2757
3266
|
].join("\n");
|
|
2758
3267
|
|
|
2759
3268
|
function apply(ctx) {
|
|
@@ -2831,6 +3340,7 @@ window.__ModuleLoader__.load({
|
|
|
2831
3340
|
exports.permissionPresetPresentation = permissionPresetPresentation;
|
|
2832
3341
|
exports.PermissionPresetPicker = PermissionPresetPicker;
|
|
2833
3342
|
exports.EditableCombobox = EditableCombobox;
|
|
3343
|
+
exports.ScheduleEditor = ScheduleEditor;
|
|
2834
3344
|
exports.WorkspacePicker = WorkspacePicker;
|
|
2835
3345
|
exports.ProviderPicker = ProviderPicker;
|
|
2836
3346
|
exports.ReasoningEffortPicker = ReasoningEffortPicker;
|
|
@@ -2838,6 +3348,14 @@ window.__ModuleLoader__.load({
|
|
|
2838
3348
|
exports.__testing = Object.freeze({
|
|
2839
3349
|
nextFormInstancePrefix,
|
|
2840
3350
|
canonicalTimezone,
|
|
3351
|
+
cronFields,
|
|
3352
|
+
simpleSchedule,
|
|
3353
|
+
scheduleControlValues,
|
|
3354
|
+
cronForSimpleSchedule,
|
|
3355
|
+
scheduleModeTransition,
|
|
3356
|
+
describeSimpleSchedule,
|
|
3357
|
+
inferFormErrorField,
|
|
3358
|
+
overlapPolicySummary,
|
|
2841
3359
|
comboboxSearchResults,
|
|
2842
3360
|
looksLikeAbsolutePath,
|
|
2843
3361
|
normalizedWorkspaces,
|