@syncended/dsh-automations 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ A DeepSeek Harness plugin for durable, configurable cron jobs. Each occurrence s
8
8
 
9
9
  - Standard five-field cron expressions with `UTC` or IANA timezones.
10
10
  - Per-job project directory, provider/model, reasoning effort, agent preset, permission preset, and timeout.
11
- - Enable/disable, Run now, edit, delete, cancel, and recent run history from the main **Automations** sidebar action or **Settings → Automations**.
11
+ - Enable/disable, Run now, edit, delete, cancel, and recent run history from a full center workspace opened by the main **Automations** sidebar action, or from **Settings → Automations**.
12
12
  - Overlap policies:
13
13
  - `skip` — record and skip an occurrence while the job has queued/running work.
14
14
  - `queue` — serialize occurrences for the same job.
@@ -42,7 +42,7 @@ pnpm build
42
42
  dsh plugin --profile web add .
43
43
  ```
44
44
 
45
- The package declares a DSH bundle, so `dsh plugin` appends it to the Web profile automatically. Restart the running Web Harness after the initial install, then refresh the page. Open **Automations** from the main sidebar, or use **Settings → Automations**.
45
+ The package declares a DSH bundle, so `dsh plugin` appends it to the Web profile automatically. Restart the running Web Harness after the initial install, then refresh the page. Open the full **Automations** workspace from the main sidebar, or use **Settings → Automations**.
46
46
 
47
47
  To remove it:
48
48
 
@@ -15,7 +15,7 @@ Non-goals for the first release:
15
15
  ## Components
16
16
 
17
17
  ```text
18
- Sidebar / Settings UI / HTTP API
18
+ Sidebar / Center workspace / Settings UI / HTTP API
19
19
 
20
20
 
21
21
  AutomationService ─── ProjectPolicy
@@ -30,7 +30,7 @@ Sidebar / Settings UI / HTTP API
30
30
 
31
31
  ### Browser surfaces
32
32
 
33
- The same automation page is available through two additive DSH extension points: `settings.section` keeps the configuration page inside Settings, while `sidebar.footer.action` opens a frame-wide `shell.overlay`. A small client-only disclosure store coordinates the sidebar trigger and overlay; neither surface owns scheduler state, and both read the same package HTTP API.
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
35
  Interactive chrome uses the ambient `@deepseek-ai/dsh-client-ui-primitives` `Button`, `Menu`, and icon components. 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
 
package/lib/client.js CHANGED
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({
21
21
  IconBrowseOutline16,
22
22
  IconCheckOutline16,
23
23
  IconChevronDownOutline14,
24
- IconCloseOutline16,
24
+ IconChevronLeftOutline14,
25
25
  IconEditOutline16,
26
26
  IconPlayOutline16,
27
27
  IconPlusOutline16,
@@ -327,12 +327,12 @@ window.__ModuleLoader__.load({
327
327
 
328
328
  function focusAfterPickerTab(trigger, backwards) {
329
329
  requestAnimationFrame(() => {
330
- const panel = trigger?.closest('[role="dialog"]');
331
- if (!panel) {
330
+ const focusBoundary = trigger?.closest(".dsh-auto-form") || trigger?.closest('[role="dialog"]');
331
+ if (!focusBoundary) {
332
332
  trigger?.focus();
333
333
  return;
334
334
  }
335
- const focusables = modalFocusables(panel);
335
+ const focusables = visibleFocusables(focusBoundary);
336
336
  const index = focusables.indexOf(trigger);
337
337
  const target = index < 0 ? trigger : focusables[index + (backwards ? -1 : 1)];
338
338
  (target || trigger)?.focus();
@@ -406,10 +406,14 @@ window.__ModuleLoader__.load({
406
406
  });
407
407
  });
408
408
  const onKeyDown = (event) => {
409
- if (event.key !== "Tab") return;
410
409
  const dialog = riskDialog();
411
410
  if (!dialog) return;
412
- const focusables = modalFocusables(dialog);
411
+ if (event.key === "Escape" && dialog.contains(event.target)) {
412
+ event.__dshAutomationsNested = true;
413
+ return;
414
+ }
415
+ if (event.key !== "Tab") return;
416
+ const focusables = visibleFocusables(dialog);
413
417
  if (focusables.length === 0) return;
414
418
  const index = focusables.indexOf(event.target);
415
419
  const next = event.shiftKey
@@ -607,6 +611,8 @@ window.__ModuleLoader__.load({
607
611
  : ["workspace-write"];
608
612
  const agentPresets = meta && Array.isArray(meta.agentPresets) ? meta.agentPresets : [];
609
613
  const creating = editing === null;
614
+ const formPrefix = "dsh-auto-form-" + String(useId()).replace(/[^A-Za-z0-9_-]/g, "");
615
+ const fieldId = (name) => formPrefix + "-" + name;
610
616
 
611
617
  return h(
612
618
  "form",
@@ -622,9 +628,9 @@ window.__ModuleLoader__.load({
622
628
  { className: "dsh-auto-formgrid" },
623
629
  h(
624
630
  Field,
625
- { label: "Name", htmlFor: "dsh-auto-f-name", required: true },
631
+ { label: "Name", htmlFor: fieldId("name"), required: true },
626
632
  h("input", {
627
- id: "dsh-auto-f-name",
633
+ id: fieldId("name"),
628
634
  className: "dsh-auto-input",
629
635
  type: "text",
630
636
  value: draft.name,
@@ -636,9 +642,9 @@ window.__ModuleLoader__.load({
636
642
  creating
637
643
  ? h(
638
644
  Field,
639
- { label: "Job id", htmlFor: "dsh-auto-f-id", hint: "Lowercase letters, digits and hyphens; auto-derived from the name. Leave blank to let the server generate one." },
645
+ { 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." },
640
646
  h("input", {
641
- id: "dsh-auto-f-id",
647
+ id: fieldId("id"),
642
648
  className: "dsh-auto-input",
643
649
  type: "text",
644
650
  value: draft.id,
@@ -652,9 +658,9 @@ window.__ModuleLoader__.load({
652
658
  )
653
659
  : h(
654
660
  Field,
655
- { label: "Job id", htmlFor: "dsh-auto-f-id", hint: "Fixed after creation." },
661
+ { label: "Job id", htmlFor: fieldId("id"), hint: "Fixed after creation." },
656
662
  h("input", {
657
- id: "dsh-auto-f-id",
663
+ id: fieldId("id"),
658
664
  className: "dsh-auto-input",
659
665
  type: "text",
660
666
  value: draft.id,
@@ -664,9 +670,9 @@ window.__ModuleLoader__.load({
664
670
  ),
665
671
  h(
666
672
  Field,
667
- { label: "Cron", htmlFor: "dsh-auto-f-cron", required: true, hint: "Five-field cron (minute hour day-of-month month day-of-week)." },
673
+ { label: "Cron", htmlFor: fieldId("cron"), required: true, hint: "Five-field cron (minute hour day-of-month month day-of-week)." },
668
674
  h("input", {
669
- id: "dsh-auto-f-cron",
675
+ id: fieldId("cron"),
670
676
  className: "dsh-auto-input",
671
677
  type: "text",
672
678
  value: draft.cron,
@@ -676,30 +682,30 @@ window.__ModuleLoader__.load({
676
682
  ),
677
683
  h(
678
684
  Field,
679
- { label: "Timezone", htmlFor: "dsh-auto-f-timezone", required: true },
685
+ { label: "Timezone", htmlFor: fieldId("timezone"), required: true },
680
686
  h("input", {
681
- id: "dsh-auto-f-timezone",
687
+ id: fieldId("timezone"),
682
688
  className: "dsh-auto-input",
683
689
  type: "text",
684
- list: "dsh-auto-tz-list",
690
+ list: fieldId("timezone-list"),
685
691
  value: draft.timezone,
686
692
  spellCheck: false,
687
693
  onChange: (event) => onChange("timezone", event.target.value),
688
694
  }),
689
695
  h(
690
696
  "datalist",
691
- { id: "dsh-auto-tz-list" },
697
+ { id: fieldId("timezone-list") },
692
698
  TIMEZONES.map((zone) => h("option", { key: zone, value: zone })),
693
699
  ),
694
700
  ),
695
701
  h(
696
702
  Field,
697
- { label: "Enabled", htmlFor: "dsh-auto-f-enabled" },
703
+ { label: "Enabled", htmlFor: fieldId("enabled") },
698
704
  h(
699
705
  "label",
700
706
  { className: "dsh-auto-check" },
701
707
  h("input", {
702
- id: "dsh-auto-f-enabled",
708
+ id: fieldId("enabled"),
703
709
  type: "checkbox",
704
710
  checked: draft.enabled,
705
711
  onChange: (event) => onChange("enabled", event.target.checked),
@@ -709,9 +715,9 @@ window.__ModuleLoader__.load({
709
715
  ),
710
716
  h(
711
717
  Field,
712
- { label: "Timeout (ms)", htmlFor: "dsh-auto-f-timeout", required: true, hint: "Wall-clock limit for each run (1s to 24h)." },
718
+ { label: "Timeout (ms)", htmlFor: fieldId("timeout"), required: true, hint: "Wall-clock limit for each run (1s to 24h)." },
713
719
  h("input", {
714
- id: "dsh-auto-f-timeout",
720
+ id: fieldId("timeout"),
715
721
  className: "dsh-auto-input",
716
722
  type: "number",
717
723
  min: MIN_TIMEOUT_MS,
@@ -723,12 +729,12 @@ window.__ModuleLoader__.load({
723
729
  ),
724
730
  h(
725
731
  Field,
726
- { label: "Provider", htmlFor: "dsh-auto-f-provider", hint: "Blank uses the current Harness default. You may type an unlisted provider route." },
732
+ { label: "Provider", htmlFor: fieldId("provider"), hint: "Blank uses the current Harness default. You may type an unlisted provider route." },
727
733
  h("input", {
728
- id: "dsh-auto-f-provider",
734
+ id: fieldId("provider"),
729
735
  className: "dsh-auto-input",
730
736
  type: "text",
731
- list: "dsh-auto-provider-list",
737
+ list: fieldId("provider-list"),
732
738
  value: draft.provider,
733
739
  spellCheck: false,
734
740
  placeholder: "Harness default",
@@ -736,7 +742,7 @@ window.__ModuleLoader__.load({
736
742
  }),
737
743
  h(
738
744
  "datalist",
739
- { id: "dsh-auto-provider-list" },
745
+ { id: fieldId("provider-list") },
740
746
  providers.map((provider) => h("option", { key: provider.id, value: provider.id })),
741
747
  ),
742
748
  ),
@@ -744,17 +750,17 @@ window.__ModuleLoader__.load({
744
750
  Field,
745
751
  {
746
752
  label: "Model",
747
- htmlFor: "dsh-auto-f-model",
753
+ htmlFor: fieldId("model"),
748
754
  required: draft.provider !== "",
749
755
  hint: draft.provider === ""
750
756
  ? "Set a provider first, or leave both blank for the Harness default."
751
757
  : "Choose a discovered model or type an adapter-supported model id.",
752
758
  },
753
759
  h("input", {
754
- id: "dsh-auto-f-model",
760
+ id: fieldId("model"),
755
761
  className: "dsh-auto-input",
756
762
  type: "text",
757
- list: "dsh-auto-model-list",
763
+ list: fieldId("model-list"),
758
764
  value: draft.model,
759
765
  spellCheck: false,
760
766
  placeholder: draft.provider === "" ? "Harness default" : "Model id",
@@ -763,18 +769,18 @@ window.__ModuleLoader__.load({
763
769
  }),
764
770
  h(
765
771
  "datalist",
766
- { id: "dsh-auto-model-list" },
772
+ { id: fieldId("model-list") },
767
773
  models.map((model) => h("option", { key: model, value: model })),
768
774
  ),
769
775
  ),
770
776
  h(
771
777
  Field,
772
- { label: "Reasoning effort", htmlFor: "dsh-auto-f-effort", hint: "Blank uses the provider default." },
778
+ { label: "Reasoning effort", htmlFor: fieldId("effort"), hint: "Blank uses the provider default." },
773
779
  h("input", {
774
- id: "dsh-auto-f-effort",
780
+ id: fieldId("effort"),
775
781
  className: "dsh-auto-input",
776
782
  type: "text",
777
- list: "dsh-auto-effort-list",
783
+ list: fieldId("effort-list"),
778
784
  value: draft.reasoningEffort,
779
785
  spellCheck: false,
780
786
  placeholder: "e.g. medium",
@@ -782,15 +788,15 @@ window.__ModuleLoader__.load({
782
788
  }),
783
789
  h(
784
790
  "datalist",
785
- { id: "dsh-auto-effort-list" },
791
+ { id: fieldId("effort-list") },
786
792
  REASONING_EFFORTS.map((effort) => h("option", { key: effort, value: effort })),
787
793
  ),
788
794
  ),
789
795
  h(
790
796
  Field,
791
- { label: "Agent preset", htmlFor: "dsh-auto-f-preset", hint: "Uses the current Harness default when not explicitly selected." },
797
+ { label: "Agent preset", htmlFor: fieldId("preset"), hint: "Uses the current Harness default when not explicitly selected." },
792
798
  h(AgentPresetPicker, {
793
- id: "dsh-auto-f-preset",
799
+ id: fieldId("preset"),
794
800
  value: draft.agentPreset,
795
801
  presets: agentPresets,
796
802
  onChange: (value) => onChange("agentPreset", value),
@@ -800,12 +806,12 @@ window.__ModuleLoader__.load({
800
806
  Field,
801
807
  {
802
808
  label: "Permission preset",
803
- htmlFor: "dsh-auto-f-permission",
809
+ htmlFor: fieldId("permission"),
804
810
  required: true,
805
811
  hint: permissionPresetPresentation(draft.permissionPreset).detail,
806
812
  },
807
813
  h(PermissionPresetPicker, {
808
- id: "dsh-auto-f-permission",
814
+ id: fieldId("permission"),
809
815
  value: draft.permissionPreset,
810
816
  presets: permissionPresets,
811
817
  onChange: (value) => onChange("permissionPreset", value),
@@ -813,11 +819,11 @@ window.__ModuleLoader__.load({
813
819
  ),
814
820
  h(
815
821
  Field,
816
- { label: "Overlap policy", htmlFor: "dsh-auto-f-overlap", full: true, hint: "What happens when a scheduled run fires while another run for the same job is still active." },
822
+ { label: "Overlap policy", htmlFor: fieldId("overlap"), full: true, hint: "What happens when a scheduled run fires while another run for the same job is still active." },
817
823
  h(
818
824
  "select",
819
825
  {
820
- id: "dsh-auto-f-overlap",
826
+ id: fieldId("overlap"),
821
827
  className: "dsh-auto-select",
822
828
  value: draft.overlap,
823
829
  onChange: (event) => onChange("overlap", event.target.value),
@@ -827,11 +833,11 @@ window.__ModuleLoader__.load({
827
833
  ),
828
834
  h(
829
835
  Field,
830
- { label: "Misfire policy", htmlFor: "dsh-auto-f-misfire", hint: "How a missed occurrence is handled after the scheduler is back." },
836
+ { label: "Misfire policy", htmlFor: fieldId("misfire"), hint: "How a missed occurrence is handled after the scheduler is back." },
831
837
  h(
832
838
  "select",
833
839
  {
834
- id: "dsh-auto-f-misfire",
840
+ id: fieldId("misfire"),
835
841
  className: "dsh-auto-select",
836
842
  value: draft.misfire,
837
843
  onChange: (event) => onChange("misfire", event.target.value),
@@ -841,9 +847,9 @@ window.__ModuleLoader__.load({
841
847
  ),
842
848
  h(
843
849
  Field,
844
- { label: "Working directory", htmlFor: "dsh-auto-f-cwd", required: true, full: true, hint: "Absolute project directory the agent runs in." },
850
+ { label: "Working directory", htmlFor: fieldId("cwd"), required: true, full: true, hint: "Absolute project directory the agent runs in." },
845
851
  h("input", {
846
- id: "dsh-auto-f-cwd",
852
+ id: fieldId("cwd"),
847
853
  className: "dsh-auto-input",
848
854
  type: "text",
849
855
  value: draft.cwd,
@@ -854,9 +860,9 @@ window.__ModuleLoader__.load({
854
860
  ),
855
861
  h(
856
862
  Field,
857
- { label: "Prompt", htmlFor: "dsh-auto-f-prompt", required: true, full: true, hint: "Instructions for the agent run. Prompts are never shown in run history." },
863
+ { label: "Prompt", htmlFor: fieldId("prompt"), required: true, full: true, hint: "Instructions for the agent run. Prompts are never shown in run history." },
858
864
  h("textarea", {
859
- id: "dsh-auto-f-prompt",
865
+ id: fieldId("prompt"),
860
866
  className: "dsh-auto-textarea",
861
867
  value: draft.prompt,
862
868
  placeholder: "Summarize yesterday's progress and list today's priorities\u2026",
@@ -1141,7 +1147,7 @@ window.__ModuleLoader__.load({
1141
1147
  );
1142
1148
  }
1143
1149
 
1144
- function AutomationsSection() {
1150
+ function AutomationsSection({ centerMode = false } = {}) {
1145
1151
  const [meta, setMeta] = useState(null);
1146
1152
  const [snapshot, setSnapshot] = useState(null);
1147
1153
  const [loading, setLoading] = useState(true);
@@ -1418,14 +1424,17 @@ window.__ModuleLoader__.load({
1418
1424
 
1419
1425
  return h(
1420
1426
  "section",
1421
- { className: "dsh-auto-root", "aria-busy": loading ? "true" : null },
1427
+ {
1428
+ className: "dsh-auto-root" + (centerMode ? " dsh-auto-root-workspace" : ""),
1429
+ "aria-busy": loading ? "true" : null,
1430
+ },
1422
1431
  h(
1423
1432
  "div",
1424
1433
  { className: "dsh-auto-head" },
1425
1434
  h(
1426
1435
  "div",
1427
1436
  null,
1428
- h("h2", null, "Automations"),
1437
+ h("h2", { className: centerMode ? "dsh-auto-sr-only" : undefined }, "Automations"),
1429
1438
  h(
1430
1439
  "p",
1431
1440
  { className: "dsh-auto-sub" },
@@ -1547,7 +1556,7 @@ window.__ModuleLoader__.load({
1547
1556
  };
1548
1557
  }
1549
1558
 
1550
- const MODAL_FOCUS_SELECTOR = [
1559
+ const FOCUSABLE_SELECTOR = [
1551
1560
  "a[href]",
1552
1561
  "button:not([disabled])",
1553
1562
  "input:not([disabled])",
@@ -1557,45 +1566,15 @@ window.__ModuleLoader__.load({
1557
1566
  "[tabindex]:not([tabindex=\"-1\"])",
1558
1567
  ].join(",");
1559
1568
 
1560
- function modalFocusables(panel) {
1569
+ function visibleFocusables(panel) {
1561
1570
  if (!panel) return [];
1562
- return Array.from(panel.querySelectorAll(MODAL_FOCUS_SELECTOR)).filter((element) =>
1571
+ return Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)).filter((element) =>
1563
1572
  !element.hasAttribute("hidden") &&
1564
1573
  element.getAttribute("aria-hidden") !== "true" &&
1565
1574
  element.getClientRects().length > 0,
1566
1575
  );
1567
1576
  }
1568
1577
 
1569
- function trapModalTab(event, panel) {
1570
- if (
1571
- event.key !== "Tab" ||
1572
- event.defaultPrevented ||
1573
- event.altKey ||
1574
- event.ctrlKey ||
1575
- event.metaKey
1576
- ) return;
1577
- const focusables = modalFocusables(panel);
1578
- if (focusables.length === 0) {
1579
- event.preventDefault();
1580
- panel?.focus();
1581
- return;
1582
- }
1583
- const first = focusables[0];
1584
- const last = focusables[focusables.length - 1];
1585
- const active = panel.ownerDocument.activeElement;
1586
- if (event.shiftKey) {
1587
- if (active === first || !panel.contains(active)) {
1588
- event.preventDefault();
1589
- last.focus();
1590
- }
1591
- return;
1592
- }
1593
- if (active === last || !panel.contains(active)) {
1594
- event.preventDefault();
1595
- first.focus();
1596
- }
1597
- }
1598
-
1599
1578
  function AutomationGlyph({ size = 18 }) {
1600
1579
  return h(
1601
1580
  "svg",
@@ -1644,10 +1623,9 @@ window.__ModuleLoader__.load({
1644
1623
  ref: triggerRef,
1645
1624
  type: "button",
1646
1625
  className: "dsh-auto-sidebar-trigger",
1647
- title: wide ? undefined : "Automations",
1648
- "aria-label": "Automations",
1649
- "aria-haspopup": "dialog",
1650
- "aria-expanded": open,
1626
+ title: wide ? undefined : open ? "Exit Automations" : "Automations",
1627
+ "aria-label": open ? "Exit Automations" : "Open Automations",
1628
+ "aria-pressed": open,
1651
1629
  "data-active": open ? "true" : undefined,
1652
1630
  "data-dsh-automations-trigger": "true",
1653
1631
  onClick: disclosure.toggle,
@@ -1658,67 +1636,55 @@ window.__ModuleLoader__.load({
1658
1636
  );
1659
1637
  }
1660
1638
 
1661
- function AutomationsOverlay({ disclosure }) {
1662
- const open = useSyncExternalStore(
1663
- disclosure.subscribe,
1664
- disclosure.getSnapshot,
1665
- disclosure.getSnapshot,
1666
- );
1667
- const closeRef = useRef(null);
1668
- const panelRef = useRef(null);
1639
+ function AutomationsWorkspace({ disclosure }) {
1640
+ const workspaceRef = useRef(null);
1669
1641
  const titleId = useId();
1670
1642
 
1671
1643
  useEffect(() => {
1672
- if (!open) return undefined;
1673
- const frame = window.requestAnimationFrame(() => closeRef.current?.focus());
1674
- return () => window.cancelAnimationFrame(frame);
1675
- }, [open]);
1644
+ const frame = window.requestAnimationFrame(() =>
1645
+ workspaceRef.current?.querySelector('[data-dsh-automations-exit="true"]')?.focus(),
1646
+ );
1647
+ const onKeyDown = (event) => {
1648
+ if (event.key !== "Escape" || event.defaultPrevented || event.__dshAutomationsNested) return;
1649
+ if (event.target?.closest?.('[role="dialog"],[role="menu"]')) return;
1650
+ if (document.querySelector('.dsh-auto-picker-trigger[aria-expanded="true"]')) return;
1651
+ event.preventDefault();
1652
+ disclosure.close();
1653
+ };
1654
+ window.addEventListener("keydown", onKeyDown);
1655
+ return () => {
1656
+ window.cancelAnimationFrame(frame);
1657
+ window.removeEventListener("keydown", onKeyDown);
1658
+ };
1659
+ }, [disclosure]);
1676
1660
 
1677
- if (!open) return null;
1678
1661
  return h(
1679
- "div",
1680
- { className: "dsh-auto-overlay", "data-dsh-automations-overlay": "true" },
1681
- h("div", { className: "dsh-auto-overlay-mask", "aria-hidden": "true", onClick: disclosure.close }),
1662
+ "section",
1663
+ {
1664
+ ref: workspaceRef,
1665
+ className: "dsh-auto-workspace",
1666
+ "data-dsh-automations-workspace": "true",
1667
+ "aria-labelledby": titleId,
1668
+ },
1682
1669
  h(
1683
- "section",
1684
- {
1685
- ref: panelRef,
1686
- className: "dsh-auto-overlay-panel",
1687
- role: "dialog",
1688
- tabIndex: -1,
1689
- "aria-modal": "true",
1690
- "aria-labelledby": titleId,
1691
- onKeyDownCapture: (event) => {
1692
- const panel = panelRef.current;
1693
- if (panel && event.target && !panel.contains(event.target)) return;
1694
- if (event.key === "Escape") {
1695
- if (panelRef.current?.querySelector('.dsh-auto-picker-trigger[aria-expanded="true"]')) return;
1696
- event.preventDefault();
1697
- disclosure.close();
1698
- return;
1699
- }
1700
- trapModalTab(event, panelRef.current);
1701
- },
1702
- },
1670
+ "header",
1671
+ { className: "dsh-auto-workspace-toolbar" },
1672
+ h("span", { className: "dsh-auto-workspace-icon", "aria-hidden": "true" }, h(AutomationGlyph, { size: 16 })),
1673
+ h("strong", { id: titleId, className: "dsh-auto-workspace-title" }, "Automations"),
1703
1674
  h(
1704
- "header",
1705
- { className: "dsh-auto-overlay-header" },
1706
- h("span", { id: titleId, className: "dsh-auto-sr-only" }, "Automations"),
1707
- h(
1708
- "button",
1709
- {
1710
- ref: closeRef,
1711
- type: "button",
1712
- className: "dsh-auto-overlay-close",
1713
- "aria-label": "Close Automations",
1714
- title: "Close",
1715
- onClick: disclosure.close,
1716
- },
1717
- h(IconCloseOutline16),
1718
- ),
1675
+ Button,
1676
+ {
1677
+ type: "button",
1678
+ variant: "toolbar",
1679
+ size: "sm",
1680
+ icon: h(IconChevronLeftOutline14),
1681
+ onClick: disclosure.close,
1682
+ "data-dsh-automations-exit": "true",
1683
+ },
1684
+ "Exit Automations",
1719
1685
  ),
1720
- h("div", { className: "dsh-auto-overlay-body" }, h(AutomationsSection)),
1721
1686
  ),
1687
+ h("div", { className: "dsh-auto-workspace-scroll" }, h(AutomationsSection, { centerMode: true })),
1722
1688
  );
1723
1689
  }
1724
1690
 
@@ -1731,17 +1697,15 @@ window.__ModuleLoader__.load({
1731
1697
  ".dsh-auto-sidebar-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1732
1698
  ".dsh-auto-sidebar-action-rail{width:36px;height:36px;margin:0;}",
1733
1699
  ".dsh-auto-sidebar-action-rail .dsh-auto-sidebar-trigger{width:36px;height:36px;margin:0;padding:0;justify-content:center;gap:0;border-radius:50%;}",
1734
- ".dsh-auto-overlay{z-index:1000;pointer-events:auto;position:fixed;inset:0;display:flex;align-items:center;justify-content:center;}",
1735
- ".dsh-auto-overlay-mask{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.46));backdrop-filter:var(--dsw-mask-blur,blur(2px));}",
1736
- ".dsh-auto-overlay-panel{box-sizing:border-box;z-index:1;position:relative;width:900px;max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px));border:1px solid var(--dsw-alias-border-inverted,var(--dsw-alias-border-l2));border-radius:24px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:var(--dsw-shadow-lv3,0 18px 60px rgba(0,0,0,.3));color:var(--dsw-alias-label-primary,#0f1115);display:flex;flex-direction:column;overflow:hidden;}",
1737
- ".dsh-auto-overlay-header{box-sizing:border-box;height:52px;flex:none;display:flex;align-items:center;justify-content:flex-end;padding:10px 16px;}",
1738
- ".dsh-auto-overlay-close{appearance:none;width:28px;height:28px;padding:0;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#4f5661);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;}",
1739
- ".dsh-auto-overlay-close:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));color:var(--dsw-alias-label-primary,#0f1115);}",
1740
- ".dsh-auto-overlay-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:1px;}",
1741
- ".dsh-auto-overlay-body{box-sizing:border-box;min-height:0;flex:1;padding:0 28px 28px;overflow-y:auto;overscroll-behavior:contain;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);}",
1742
- ".dsh-auto-overlay-body>.dsh-auto-root{width:100%;margin:0 auto;padding-top:0;}",
1700
+ ".dsh-auto-workspace{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;color:var(--dsw-alias-label-primary,#0f1115);background:var(--dsw-alias-bg-base,#fff);font-family:inherit;}",
1701
+ ".dsh-auto-workspace-toolbar{box-sizing:border-box;width:100%;height:44px;flex:none;display:flex;align-items:center;gap:8px;padding:6px 10px 6px 12px;border-bottom:1px solid var(--dsw-alias-border-l2,rgba(15,17,21,.14));background:var(--dsw-alias-bg-base,#fff);}",
1702
+ ".dsh-auto-workspace-icon{width:28px;height:28px;flex:none;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-state-business-primary,#4176e6);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4176e6) 10%,transparent);}",
1703
+ ".dsh-auto-workspace-title{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:20px;}",
1704
+ ".dsh-auto-workspace-scroll{container:dsh-auto-workspace / inline-size;box-sizing:border-box;min-height:0;flex:1;padding:24px clamp(20px,4vw,48px) 40px;overflow-y:auto;overscroll-behavior:contain;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);}",
1705
+ ".dsh-auto-workspace-scroll>.dsh-auto-root{width:100%;margin:0 auto;padding-top:0;}",
1743
1706
  ".dsh-auto-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important;}",
1744
1707
  ".dsh-auto-root{--dsh-auto-border:var(--dsw-alias-border-l2,rgba(15,17,21,.14));--dsh-auto-surface:var(--dsw-alias-bg-layer-3,#fff);--dsh-auto-surface-active:var(--dsw-alias-bg-layer-2,#fff);--dsh-auto-input-bg:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1,#fff));--dsh-auto-text:var(--dsw-alias-label-primary,#0f1115);--dsh-auto-text-secondary:var(--dsw-alias-label-secondary,#4f5661);--dsh-auto-muted:var(--dsw-alias-label-tertiary,#81858c);--dsh-auto-caption:var(--dsw-alias-label-caption,#adb2b8);--dsh-auto-accent:var(--dsw-alias-state-business-primary,#4176e6);--dsh-auto-danger:var(--dsw-alias-state-error-primary,#ec1313);--dsh-auto-success:var(--dsw-alias-state-success-primary,#22c55e);--dsh-auto-warning:var(--dsw-alias-state-warn-primary,#f59e0b);box-sizing:border-box;max-width:820px;padding-top:12px;display:flex;flex-direction:column;gap:16px;font-family:inherit;color:var(--dsh-auto-text);}",
1708
+ ".dsh-auto-root-workspace{max-width:960px;}",
1745
1709
  "body:not([data-ds-dark-theme]) .dsh-auto-root{color-scheme:light;}",
1746
1710
  "body[data-ds-dark-theme] .dsh-auto-root{color-scheme:dark;}",
1747
1711
  ".dsh-auto-root *,.dsh-auto-root *::before,.dsh-auto-root *::after{box-sizing:border-box;}",
@@ -1837,13 +1801,34 @@ window.__ModuleLoader__.load({
1837
1801
  "@keyframes dsh-auto-pulse{0%,100%{opacity:1}50%{opacity:.35}}",
1838
1802
  ".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);}",
1839
1803
  ".dsh-auto-error{color:var(--dsh-auto-danger);display:flex;flex-direction:column;gap:10px;align-items:center;}",
1840
- "@media (max-width:640px){.dsh-auto-overlay-panel{max-width:calc(100vw - 16px);height:calc(100vh - 16px);border-radius:16px;}.dsh-auto-overlay-header{height:46px;padding:8px 10px;}.dsh-auto-overlay-body{padding:0 16px 16px;}.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;}}",
1804
+ "@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;}}",
1805
+ "@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-card-head{flex-direction:column;align-items:flex-start;}.dsh-auto-run-name{max-width:150px;}}",
1841
1806
  ].join("\n");
1842
1807
 
1843
1808
  function apply(ctx) {
1844
1809
  const disclosure = createDisclosureStore();
1845
- // All package-owned surfaces share one style element and one disclosure
1846
- // store; both are released with the client-plugin fiber.
1810
+ let centerDeclared = false;
1811
+ let disposeCenter = null;
1812
+ const unmountCenter = () => {
1813
+ if (!disposeCenter) return;
1814
+ const dispose = disposeCenter;
1815
+ disposeCenter = null;
1816
+ dispose();
1817
+ };
1818
+ const mountCenter = () => {
1819
+ if (!centerDeclared || !disclosure.getSnapshot() || disposeCenter) return;
1820
+ try {
1821
+ disposeCenter = ctx.slots.register({
1822
+ name: "conversation",
1823
+ priority: -200,
1824
+ inject: () => ({ disclosure }),
1825
+ }, AutomationsWorkspace);
1826
+ } catch (error) {
1827
+ console.error("dsh automations: could not mount center workspace", error);
1828
+ disclosure.close();
1829
+ }
1830
+ };
1831
+
1847
1832
  ctx.effect(() => {
1848
1833
  const tag = document.createElement("style");
1849
1834
  tag.setAttribute("data-plugin", "@syncended/dsh-automations");
@@ -1853,7 +1838,28 @@ window.__ModuleLoader__.load({
1853
1838
  tag.remove();
1854
1839
  };
1855
1840
  }, "@syncended/dsh-automations: client styles");
1856
- ctx.effect(() => () => disclosure.dispose(), "@syncended/dsh-automations: disclosure store");
1841
+ ctx.effect(
1842
+ () => ctx.slots.inject("conversation", () => {
1843
+ centerDeclared = true;
1844
+ mountCenter();
1845
+ return () => {
1846
+ centerDeclared = false;
1847
+ unmountCenter();
1848
+ };
1849
+ }),
1850
+ "@syncended/dsh-automations: center workspace",
1851
+ );
1852
+ ctx.effect(() => {
1853
+ const unsubscribe = disclosure.subscribe(() => {
1854
+ if (disclosure.getSnapshot()) mountCenter();
1855
+ else unmountCenter();
1856
+ });
1857
+ return () => {
1858
+ unsubscribe();
1859
+ unmountCenter();
1860
+ disclosure.dispose();
1861
+ };
1862
+ }, "@syncended/dsh-automations: workspace state");
1857
1863
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1858
1864
  name: "settings.section",
1859
1865
  id: "automations",
@@ -1867,13 +1873,6 @@ window.__ModuleLoader__.load({
1867
1873
  label: "Automations",
1868
1874
  inject: () => ({ disclosure }),
1869
1875
  }, AutomationsSidebarAction));
1870
- ctx.slots.inject("shell.overlay", () => ctx.slots.register({
1871
- name: "shell.overlay",
1872
- id: "automations",
1873
- order: 50,
1874
- label: "Automations",
1875
- inject: () => ({ disclosure }),
1876
- }, AutomationsOverlay));
1877
1876
  }
1878
1877
 
1879
1878
  exports.agentPresetDisplayLabel = agentPresetDisplayLabel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncended/dsh-automations",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Durable cron automations for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,7 @@
30
30
  "inject": [
31
31
  "@deepseek-ai/dsh-client-runtime",
32
32
  "@deepseek-ai/dsh-client-ui-layout",
33
+ "@deepseek-ai/dsh-client-ui-conversation",
33
34
  "@deepseek-ai/dsh-client-ui-sidebar",
34
35
  "@deepseek-ai/dsh-client-ui-settings"
35
36
  ],
@@ -82,6 +83,7 @@
82
83
  "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2",
83
84
  "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2",
84
85
  "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
86
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
85
87
  "@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.2",
86
88
  "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
87
89
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.1-rc.2",
@@ -97,6 +99,7 @@
97
99
  "@deepseek-ai/dsh-agent-default-model": "0.1.1-rc.2",
98
100
  "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
99
101
  "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
102
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.1-rc.2",
100
103
  "@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2",
101
104
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
102
105
  "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",