@syncended/dsh-automations 0.2.1 → 0.2.2

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.
@@ -32,6 +32,8 @@ Sidebar / Settings UI / HTTP API
32
32
 
33
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.
34
34
 
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
+
35
37
  ### AutomationService
36
38
 
37
39
  Cordis service `ctx.automations`. It owns input validation, canonical project authorization, metadata discovery, the Web route, and the public executor-registration seam.
package/lib/client.js CHANGED
@@ -13,6 +13,23 @@ window.__ModuleLoader__.load({
13
13
  const React = require("react");
14
14
  const h = React.createElement;
15
15
  const { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } = React;
16
+ const {
17
+ Button,
18
+ Menu,
19
+ RiskConfirmation,
20
+ IconAgentPresetOutline16,
21
+ IconBrowseOutline16,
22
+ IconCheckOutline16,
23
+ IconChevronDownOutline14,
24
+ IconCloseOutline16,
25
+ IconEditOutline16,
26
+ IconPlayOutline16,
27
+ IconPlusOutline16,
28
+ IconRefreshOutline16,
29
+ IconSettingsOutline16,
30
+ IconTrashOutline16,
31
+ IconWarningOutline16,
32
+ } = require("@deepseek-ai/dsh-client-ui-primitives");
16
33
  const inject = ["slots"];
17
34
 
18
35
  const API_PREFIX = "/api/automations";
@@ -73,6 +90,26 @@ window.__ModuleLoader__.load({
73
90
  minimal: "Minimal mode",
74
91
  cordis: "Creator mode",
75
92
  };
93
+ const PERMISSION_PRESET_PRESENTATION = {
94
+ "read-only": {
95
+ label: "Read Only",
96
+ detail: "Read files without modifying the workspace.",
97
+ icon: IconBrowseOutline16,
98
+ tone: "read",
99
+ },
100
+ "workspace-write": {
101
+ label: "Workspace Write",
102
+ detail: "Write inside the workspace; wider retries require approval.",
103
+ icon: IconEditOutline16,
104
+ tone: "write",
105
+ },
106
+ "danger-full-access": {
107
+ label: "Full access",
108
+ detail: "Full file access without approval prompts.",
109
+ icon: IconWarningOutline16,
110
+ tone: "danger",
111
+ },
112
+ };
76
113
 
77
114
  function humanizePresetId(id) {
78
115
  return id
@@ -91,6 +128,24 @@ window.__ModuleLoader__.load({
91
128
  return readableId === id ? id : readableId + " (" + id + ")";
92
129
  }
93
130
 
131
+ function permissionPresetPresentation(value) {
132
+ const known = PERMISSION_PRESET_PRESENTATION[value];
133
+ if (known) return known;
134
+ return {
135
+ label: humanizePresetId(value) || value || "Unknown permission",
136
+ detail: "Host-provided permission preset.",
137
+ icon: IconSettingsOutline16,
138
+ tone: "custom",
139
+ };
140
+ }
141
+
142
+ function PermissionPresetIcon({ value, className }) {
143
+ const presentation = permissionPresetPresentation(value);
144
+ return h(presentation.icon, {
145
+ className: (className ? className + " " : "") + "dsh-auto-permission-glyph dsh-auto-permission-glyph-" + presentation.tone,
146
+ });
147
+ }
148
+
94
149
  function browserTimezone() {
95
150
  return BROWSER_TIMEZONE;
96
151
  }
@@ -258,6 +313,285 @@ window.__ModuleLoader__.load({
258
313
  );
259
314
  }
260
315
 
316
+ function pickerMenuFor(ownerId) {
317
+ return Array.from(document.querySelectorAll('[role="menu"]')).find((menu) =>
318
+ Array.from(menu.querySelectorAll("[data-dsh-auto-picker-owner]")).some((item) =>
319
+ item.getAttribute("data-dsh-auto-picker-owner") === ownerId,
320
+ ),
321
+ ) || null;
322
+ }
323
+
324
+ function focusPickerTrigger(triggerRef) {
325
+ requestAnimationFrame(() => triggerRef.current?.focus());
326
+ }
327
+
328
+ function focusAfterPickerTab(trigger, backwards) {
329
+ requestAnimationFrame(() => {
330
+ const panel = trigger?.closest('[role="dialog"]');
331
+ if (!panel) {
332
+ trigger?.focus();
333
+ return;
334
+ }
335
+ const focusables = modalFocusables(panel);
336
+ const index = focusables.indexOf(trigger);
337
+ const target = index < 0 ? trigger : focusables[index + (backwards ? -1 : 1)];
338
+ (target || trigger)?.focus();
339
+ });
340
+ }
341
+
342
+ function usePickerMenuNavigation({ open, setOpen, ownerId, selectedId, triggerRef }) {
343
+ useEffect(() => {
344
+ if (!open) return;
345
+ const focusSelected = () => {
346
+ const menu = pickerMenuFor(ownerId);
347
+ if (!menu) return;
348
+ const buttons = Array.from(menu.querySelectorAll('button[role="menuitem"]:not(:disabled)'));
349
+ const selected = buttons.find((button) =>
350
+ button.querySelector("[data-dsh-auto-picker-item]")?.getAttribute("data-dsh-auto-picker-item") === selectedId,
351
+ );
352
+ (selected || buttons[0])?.focus();
353
+ };
354
+ const frame = requestAnimationFrame(focusSelected);
355
+ const onKeyDown = (event) => {
356
+ const menu = pickerMenuFor(ownerId);
357
+ if (!menu || !(event.target instanceof Node) || !menu.contains(event.target)) return;
358
+ const buttons = Array.from(menu.querySelectorAll('button[role="menuitem"]:not(:disabled)'));
359
+ const index = buttons.indexOf(event.target.closest('button[role="menuitem"]'));
360
+ let target;
361
+ if (event.key === "ArrowDown") target = buttons[(Math.max(index, -1) + 1) % buttons.length];
362
+ else if (event.key === "ArrowUp") target = buttons[(index <= 0 ? buttons.length : index) - 1];
363
+ else if (event.key === "Home") target = buttons[0];
364
+ else if (event.key === "End") target = buttons[buttons.length - 1];
365
+ else if (event.key === "Escape") {
366
+ event.preventDefault();
367
+ event.stopImmediatePropagation();
368
+ setOpen(false);
369
+ focusPickerTrigger(triggerRef);
370
+ return;
371
+ } else if (event.key === "Tab") {
372
+ event.preventDefault();
373
+ event.stopImmediatePropagation();
374
+ setOpen(false);
375
+ focusAfterPickerTab(triggerRef.current, event.shiftKey);
376
+ return;
377
+ } else return;
378
+ if (!target) return;
379
+ event.preventDefault();
380
+ target.focus();
381
+ };
382
+ document.addEventListener("keydown", onKeyDown, true);
383
+ return () => {
384
+ cancelAnimationFrame(frame);
385
+ document.removeEventListener("keydown", onKeyDown, true);
386
+ };
387
+ }, [open, ownerId, selectedId, setOpen, triggerRef]);
388
+ }
389
+
390
+ function openPickerFromKeyboard(event, setOpen) {
391
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
392
+ event.preventDefault();
393
+ setOpen(true);
394
+ }
395
+
396
+ function useRiskConfirmationFocus(open) {
397
+ useEffect(() => {
398
+ if (!open) return;
399
+ const riskDialog = () => Array.from(document.querySelectorAll('[role="dialog"]')).find((dialog) =>
400
+ dialog.getAttribute("aria-label") === "Enable Full access?",
401
+ ) || null;
402
+ let innerFrame;
403
+ const frame = requestAnimationFrame(() => {
404
+ innerFrame = requestAnimationFrame(() => {
405
+ riskDialog()?.querySelector('input[type="checkbox"]')?.focus();
406
+ });
407
+ });
408
+ const onKeyDown = (event) => {
409
+ if (event.key !== "Tab") return;
410
+ const dialog = riskDialog();
411
+ if (!dialog) return;
412
+ const focusables = modalFocusables(dialog);
413
+ if (focusables.length === 0) return;
414
+ const index = focusables.indexOf(event.target);
415
+ const next = event.shiftKey
416
+ ? focusables[index <= 0 ? focusables.length - 1 : index - 1]
417
+ : focusables[index < 0 || index === focusables.length - 1 ? 0 : index + 1];
418
+ event.preventDefault();
419
+ event.stopImmediatePropagation();
420
+ next.focus();
421
+ };
422
+ document.addEventListener("keydown", onKeyDown, true);
423
+ return () => {
424
+ cancelAnimationFrame(frame);
425
+ if (innerFrame !== undefined) cancelAnimationFrame(innerFrame);
426
+ document.removeEventListener("keydown", onKeyDown, true);
427
+ };
428
+ }, [open]);
429
+ }
430
+
431
+ function PickerItemCopy({ label, detail, ownerId, itemId }) {
432
+ return h(
433
+ "span",
434
+ {
435
+ className: "dsh-auto-picker-item-copy",
436
+ "data-dsh-auto-picker-owner": ownerId,
437
+ "data-dsh-auto-picker-item": itemId,
438
+ },
439
+ h("span", { className: "dsh-auto-picker-item-label" }, label),
440
+ detail ? h("span", { className: "dsh-auto-picker-item-detail" }, detail) : null,
441
+ );
442
+ }
443
+
444
+ function AgentPresetPicker({ id, value, presets, onChange }) {
445
+ const [open, setOpen] = useState(false);
446
+ const ownerId = useId();
447
+ const triggerRef = useRef(null);
448
+ usePickerMenuNavigation({ open, setOpen, ownerId, selectedId: value, triggerRef });
449
+ const selected = presets.find((preset) => preset.id === value);
450
+ const label = value === ""
451
+ ? "Harness default"
452
+ : agentPresetDisplayLabel(selected || { id: value, name: value });
453
+ const items = [
454
+ {
455
+ id: "",
456
+ label: h(PickerItemCopy, {
457
+ label: "Harness default",
458
+ detail: "Use the current Harness agent preset.",
459
+ ownerId,
460
+ itemId: "",
461
+ }),
462
+ icon: h(IconAgentPresetOutline16),
463
+ },
464
+ ...presets.map((preset) => ({
465
+ id: preset.id,
466
+ label: h(PickerItemCopy, {
467
+ label: agentPresetDisplayLabel(preset),
468
+ detail: preset.broken ? "Failed to load: " + preset.broken : null,
469
+ ownerId,
470
+ itemId: preset.id,
471
+ }),
472
+ icon: h(IconAgentPresetOutline16),
473
+ disabled: Boolean(preset.broken),
474
+ })),
475
+ ];
476
+ return h(Menu, {
477
+ open,
478
+ onClose: () => setOpen(false),
479
+ items,
480
+ selectedId: value,
481
+ onSelect: (next) => {
482
+ setOpen(false);
483
+ onChange(next);
484
+ focusPickerTrigger(triggerRef);
485
+ },
486
+ side: "top",
487
+ portal: true,
488
+ className: "dsh-auto-picker-root",
489
+ anchor: h(
490
+ "button",
491
+ {
492
+ ref: triggerRef,
493
+ id,
494
+ type: "button",
495
+ className: "dsh-auto-picker-trigger",
496
+ "aria-haspopup": "menu",
497
+ "aria-expanded": open,
498
+ onKeyDown: (event) => openPickerFromKeyboard(event, setOpen),
499
+ onClick: () => setOpen((current) => !current),
500
+ },
501
+ h(IconAgentPresetOutline16, { className: "dsh-auto-picker-trigger-icon" }),
502
+ h("span", { className: "dsh-auto-picker-trigger-label" }, label),
503
+ h(IconChevronDownOutline14, { className: "dsh-auto-picker-chevron" + (open ? " dsh-auto-picker-chevron-open" : "") }),
504
+ ),
505
+ });
506
+ }
507
+
508
+ function PermissionPresetPicker({ id, value, presets, onChange }) {
509
+ const [open, setOpen] = useState(false);
510
+ const [confirmingFullAccess, setConfirmingFullAccess] = useState(false);
511
+ const [acknowledged, setAcknowledged] = useState(false);
512
+ const ownerId = useId();
513
+ const triggerRef = useRef(null);
514
+ usePickerMenuNavigation({ open, setOpen, ownerId, selectedId: value, triggerRef });
515
+ useRiskConfirmationFocus(confirmingFullAccess);
516
+ const selected = permissionPresetPresentation(value);
517
+ const items = presets.map((preset) => {
518
+ const presentation = permissionPresetPresentation(preset);
519
+ return {
520
+ id: preset,
521
+ label: h(PickerItemCopy, {
522
+ label: presentation.label,
523
+ detail: presentation.detail,
524
+ ownerId,
525
+ itemId: preset,
526
+ }),
527
+ icon: h(PermissionPresetIcon, { value: preset }),
528
+ danger: preset === "danger-full-access",
529
+ };
530
+ });
531
+ const closeConfirmation = () => {
532
+ setConfirmingFullAccess(false);
533
+ setAcknowledged(false);
534
+ focusPickerTrigger(triggerRef);
535
+ };
536
+ return h(
537
+ React.Fragment,
538
+ null,
539
+ h(Menu, {
540
+ open,
541
+ onClose: () => setOpen(false),
542
+ items,
543
+ selectedId: value,
544
+ onSelect: (next) => {
545
+ setOpen(false);
546
+ if (next === "danger-full-access" && value !== "danger-full-access") {
547
+ setAcknowledged(false);
548
+ setConfirmingFullAccess(true);
549
+ return;
550
+ }
551
+ onChange(next);
552
+ focusPickerTrigger(triggerRef);
553
+ },
554
+ side: "top",
555
+ portal: true,
556
+ className: "dsh-auto-picker-root",
557
+ anchor: h(
558
+ "button",
559
+ {
560
+ ref: triggerRef,
561
+ id,
562
+ type: "button",
563
+ className: "dsh-auto-picker-trigger",
564
+ "data-permission-preset": value,
565
+ "aria-haspopup": "menu",
566
+ "aria-expanded": open,
567
+ onKeyDown: (event) => openPickerFromKeyboard(event, setOpen),
568
+ onClick: () => setOpen((current) => !current),
569
+ },
570
+ h(PermissionPresetIcon, { value, className: "dsh-auto-picker-trigger-icon" }),
571
+ h("span", { className: "dsh-auto-picker-trigger-label" }, selected.label),
572
+ h(IconChevronDownOutline14, { className: "dsh-auto-picker-chevron" + (open ? " dsh-auto-picker-chevron-open" : "") }),
573
+ ),
574
+ }),
575
+ h(RiskConfirmation, {
576
+ open: confirmingFullAccess,
577
+ title: "Enable Full access?",
578
+ description: "Full access lets scheduled agents perform sensitive actions, file changes, and external commands without approval prompts. Only use it for automations you trust.",
579
+ acknowledgeLabel: "I understand the risks and want to continue",
580
+ cancelLabel: "Cancel",
581
+ confirmLabel: "Enable Full access",
582
+ acknowledged,
583
+ onAcknowledgedChange: setAcknowledged,
584
+ onCancel: closeConfirmation,
585
+ onConfirm: () => {
586
+ setConfirmingFullAccess(false);
587
+ setAcknowledged(false);
588
+ onChange("danger-full-access");
589
+ focusPickerTrigger(triggerRef);
590
+ },
591
+ }),
592
+ );
593
+ }
594
+
261
595
  function StatusPill(props) {
262
596
  return h("span", { className: "dsh-auto-status dsh-auto-status-" + props.status }, props.status);
263
597
  }
@@ -454,38 +788,28 @@ window.__ModuleLoader__.load({
454
788
  ),
455
789
  h(
456
790
  Field,
457
- { label: "Agent preset", htmlFor: "dsh-auto-f-preset", hint: "Blank uses the current Harness agent-preset default." },
458
- h(
459
- "select",
460
- {
461
- id: "dsh-auto-f-preset",
462
- className: "dsh-auto-select",
463
- value: draft.agentPreset,
464
- onChange: (event) => onChange("agentPreset", event.target.value),
465
- },
466
- h("option", { value: "" }, "Harness default"),
467
- agentPresets.map((preset) =>
468
- h(
469
- "option",
470
- { key: preset.id, value: preset.id },
471
- agentPresetDisplayLabel(preset) + (preset.broken ? " (broken: " + preset.broken + ")" : ""),
472
- ),
473
- ),
474
- ),
791
+ { label: "Agent preset", htmlFor: "dsh-auto-f-preset", hint: "Uses the current Harness default when not explicitly selected." },
792
+ h(AgentPresetPicker, {
793
+ id: "dsh-auto-f-preset",
794
+ value: draft.agentPreset,
795
+ presets: agentPresets,
796
+ onChange: (value) => onChange("agentPreset", value),
797
+ }),
475
798
  ),
476
799
  h(
477
800
  Field,
478
- { label: "Permission preset", htmlFor: "dsh-auto-f-permission", required: true },
479
- h(
480
- "select",
481
- {
482
- id: "dsh-auto-f-permission",
483
- className: "dsh-auto-select",
484
- value: draft.permissionPreset,
485
- onChange: (event) => onChange("permissionPreset", event.target.value),
486
- },
487
- permissionPresets.map((preset) => h("option", { key: preset, value: preset }, preset)),
488
- ),
801
+ {
802
+ label: "Permission preset",
803
+ htmlFor: "dsh-auto-f-permission",
804
+ required: true,
805
+ hint: permissionPresetPresentation(draft.permissionPreset).detail,
806
+ },
807
+ h(PermissionPresetPicker, {
808
+ id: "dsh-auto-f-permission",
809
+ value: draft.permissionPreset,
810
+ presets: permissionPresets,
811
+ onChange: (value) => onChange("permissionPreset", value),
812
+ }),
489
813
  ),
490
814
  h(
491
815
  Field,
@@ -547,13 +871,18 @@ window.__ModuleLoader__.load({
547
871
  "div",
548
872
  { className: "dsh-auto-formactions" },
549
873
  h(
550
- "button",
551
- { type: "submit", className: "dsh-auto-btn dsh-auto-btn-primary", disabled: saving },
874
+ Button,
875
+ {
876
+ type: "submit",
877
+ variant: "primary",
878
+ icon: saving ? null : h(IconCheckOutline16),
879
+ disabled: saving,
880
+ },
552
881
  saving ? "Saving\u2026" : creating ? "Create automation" : "Save changes",
553
882
  ),
554
883
  h(
555
- "button",
556
- { type: "button", className: "dsh-auto-btn", onClick: onCancel, disabled: saving },
884
+ Button,
885
+ { type: "button", variant: "outline", onClick: onCancel, disabled: saving },
557
886
  "Cancel",
558
887
  ),
559
888
  ),
@@ -589,6 +918,7 @@ window.__ModuleLoader__.load({
589
918
  name: job.execution.agentPreset,
590
919
  })
591
920
  : "";
921
+ const permission = permissionPresetPresentation(job.execution.permissionPreset);
592
922
 
593
923
  return h(
594
924
  "article",
@@ -634,7 +964,12 @@ window.__ModuleLoader__.load({
634
964
  presetLabel !== ""
635
965
  ? h("span", { title: "Agent preset" }, presetLabel)
636
966
  : null,
637
- h("span", { title: "Permission preset" }, job.execution.permissionPreset),
967
+ h(
968
+ "span",
969
+ { className: "dsh-auto-card-permission", title: "Permission preset: " + job.execution.permissionPreset },
970
+ h(PermissionPresetIcon, { value: job.execution.permissionPreset }),
971
+ permission.label,
972
+ ),
638
973
  h("span", { title: "Timeout" }, formatTimeout(job.execution.timeoutMs)),
639
974
  h("span", { className: "dsh-auto-muted", title: "Version" }, "v" + job.version),
640
975
  ),
@@ -642,15 +977,24 @@ window.__ModuleLoader__.load({
642
977
  "div",
643
978
  { className: "dsh-auto-card-actions" },
644
979
  h(
645
- "button",
646
- { type: "button", className: "dsh-auto-btn", onClick: onEdit, disabled: busyForJob || running },
980
+ Button,
981
+ {
982
+ type: "button",
983
+ variant: "ghost",
984
+ size: "sm",
985
+ icon: h(IconEditOutline16),
986
+ onClick: onEdit,
987
+ disabled: busyForJob || running,
988
+ },
647
989
  "Edit",
648
990
  ),
649
991
  h(
650
- "button",
992
+ Button,
651
993
  {
652
994
  type: "button",
653
- className: "dsh-auto-btn dsh-auto-btn-primary",
995
+ variant: "primary",
996
+ size: "sm",
997
+ icon: running ? null : h(IconPlayOutline16),
654
998
  onClick: onRun,
655
999
  disabled: busyForJob,
656
1000
  },
@@ -661,26 +1005,32 @@ window.__ModuleLoader__.load({
661
1005
  "span",
662
1006
  { className: "dsh-auto-confirm" },
663
1007
  h(
664
- "button",
1008
+ Button,
665
1009
  {
666
1010
  type: "button",
667
- className: "dsh-auto-btn dsh-auto-btn-danger",
1011
+ variant: "ghost",
1012
+ size: "sm",
1013
+ className: "dsh-auto-danger-button",
1014
+ icon: deleting ? null : h(IconTrashOutline16),
668
1015
  onClick: onDeleteConfirm,
669
1016
  disabled: deleting,
670
1017
  },
671
1018
  deleting ? "Deleting\u2026" : "Confirm delete",
672
1019
  ),
673
1020
  h(
674
- "button",
675
- { type: "button", className: "dsh-auto-btn", onClick: onDeleteCancel, disabled: deleting },
1021
+ Button,
1022
+ { type: "button", variant: "outline", size: "sm", onClick: onDeleteCancel, disabled: deleting },
676
1023
  "Keep",
677
1024
  ),
678
1025
  )
679
1026
  : h(
680
- "button",
1027
+ Button,
681
1028
  {
682
1029
  type: "button",
683
- className: "dsh-auto-btn dsh-auto-btn-danger-ghost",
1030
+ variant: "ghost",
1031
+ size: "sm",
1032
+ className: "dsh-auto-danger-button",
1033
+ icon: h(IconTrashOutline16),
684
1034
  onClick: onDelete,
685
1035
  disabled: busyForJob,
686
1036
  },
@@ -750,26 +1100,29 @@ window.__ModuleLoader__.load({
750
1100
  "span",
751
1101
  { className: "dsh-auto-confirm" },
752
1102
  h(
753
- "button",
1103
+ Button,
754
1104
  {
755
1105
  type: "button",
756
- className: "dsh-auto-btn dsh-auto-btn-danger",
1106
+ variant: "ghost",
1107
+ size: "sm",
1108
+ className: "dsh-auto-danger-button",
757
1109
  onClick: () => onCancelConfirm(run),
758
1110
  disabled: cancelling,
759
1111
  },
760
1112
  cancelling ? "Cancelling\u2026" : "Confirm",
761
1113
  ),
762
1114
  h(
763
- "button",
764
- { type: "button", className: "dsh-auto-btn", onClick: onCancelReset, disabled: cancelling },
1115
+ Button,
1116
+ { type: "button", variant: "outline", size: "sm", onClick: onCancelReset, disabled: cancelling },
765
1117
  "Back",
766
1118
  ),
767
1119
  )
768
1120
  : h(
769
- "button",
1121
+ Button,
770
1122
  {
771
1123
  type: "button",
772
- className: "dsh-auto-btn dsh-auto-btn-ghost",
1124
+ variant: "ghost",
1125
+ size: "sm",
773
1126
  onClick: () => onCancel(run),
774
1127
  },
775
1128
  "Cancel",
@@ -1083,8 +1436,8 @@ window.__ModuleLoader__.load({
1083
1436
  ),
1084
1437
  !formOpen
1085
1438
  ? h(
1086
- "button",
1087
- { type: "button", className: "dsh-auto-btn dsh-auto-btn-primary", onClick: openCreate },
1439
+ Button,
1440
+ { type: "button", variant: "primary", icon: h(IconPlusOutline16), onClick: openCreate },
1088
1441
  "New automation",
1089
1442
  )
1090
1443
  : null,
@@ -1121,8 +1474,8 @@ window.__ModuleLoader__.load({
1121
1474
  { className: "dsh-auto-error", role: "alert" },
1122
1475
  h("p", null, "Could not load automations: ", loadError),
1123
1476
  h(
1124
- "button",
1125
- { type: "button", className: "dsh-auto-btn dsh-auto-btn-primary", onClick: () => loadSnapshot(true) },
1477
+ Button,
1478
+ { type: "button", variant: "primary", size: "sm", icon: h(IconRefreshOutline16), onClick: () => loadSnapshot(true) },
1126
1479
  "Retry",
1127
1480
  ),
1128
1481
  )
@@ -1265,24 +1618,6 @@ window.__ModuleLoader__.load({
1265
1618
  );
1266
1619
  }
1267
1620
 
1268
- function CloseGlyph() {
1269
- return h(
1270
- "svg",
1271
- {
1272
- width: "16",
1273
- height: "16",
1274
- viewBox: "0 0 16 16",
1275
- fill: "none",
1276
- stroke: "currentColor",
1277
- strokeWidth: "1.5",
1278
- strokeLinecap: "round",
1279
- "aria-hidden": "true",
1280
- focusable: "false",
1281
- },
1282
- h("path", { d: "M3.5 3.5l9 9M12.5 3.5l-9 9" }),
1283
- );
1284
- }
1285
-
1286
1621
  function AutomationsSidebarAction({ wide, disclosure }) {
1287
1622
  const open = useSyncExternalStore(
1288
1623
  disclosure.subscribe,
@@ -1354,7 +1689,10 @@ window.__ModuleLoader__.load({
1354
1689
  "aria-modal": "true",
1355
1690
  "aria-labelledby": titleId,
1356
1691
  onKeyDownCapture: (event) => {
1692
+ const panel = panelRef.current;
1693
+ if (panel && event.target && !panel.contains(event.target)) return;
1357
1694
  if (event.key === "Escape") {
1695
+ if (panelRef.current?.querySelector('.dsh-auto-picker-trigger[aria-expanded="true"]')) return;
1358
1696
  event.preventDefault();
1359
1697
  disclosure.close();
1360
1698
  return;
@@ -1376,7 +1714,7 @@ window.__ModuleLoader__.load({
1376
1714
  title: "Close",
1377
1715
  onClick: disclosure.close,
1378
1716
  },
1379
- h(CloseGlyph),
1717
+ h(IconCloseOutline16),
1380
1718
  ),
1381
1719
  ),
1382
1720
  h("div", { className: "dsh-auto-overlay-body" }, h(AutomationsSection)),
@@ -1386,7 +1724,7 @@ window.__ModuleLoader__.load({
1386
1724
 
1387
1725
  const STYLE_CSS = [
1388
1726
  ".dsh-auto-sidebar-action{box-sizing:border-box;width:100%;height:42px;flex:none;display:flex;align-items:center;margin:4px 0 0;}",
1389
- ".dsh-auto-sidebar-trigger{box-sizing:border-box;appearance:none;width:calc(100% + 4px);height:42px;margin:0 -2px;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#0f1115);display:flex;align-items:center;gap:8px;overflow:hidden;font:14px/22px inherit;cursor:pointer;}",
1727
+ ".dsh-auto-sidebar-trigger{box-sizing:border-box;appearance:none;width:calc(100% + 4px);height:42px;margin:0 -2px;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#0f1115);display:flex;align-items:center;gap:8px;overflow:hidden;font-family:inherit;font-size:14px;line-height:22px;cursor:pointer;}",
1390
1728
  ".dsh-auto-sidebar-trigger:hover,.dsh-auto-sidebar-trigger[data-active=\"true\"]{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));}",
1391
1729
  ".dsh-auto-sidebar-trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px;}",
1392
1730
  ".dsh-auto-sidebar-trigger svg{flex:none;}",
@@ -1395,15 +1733,15 @@ window.__ModuleLoader__.load({
1395
1733
  ".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%;}",
1396
1734
  ".dsh-auto-overlay{z-index:1000;pointer-events:auto;position:fixed;inset:0;display:flex;align-items:center;justify-content:center;}",
1397
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));}",
1398
- ".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:0;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;}",
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;}",
1399
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;}",
1400
- ".dsh-auto-overlay-close{appearance:none;width:30px;height:30px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#4f5661);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;}",
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;}",
1401
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);}",
1402
1740
  ".dsh-auto-overlay-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:1px;}",
1403
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);}",
1404
1742
  ".dsh-auto-overlay-body>.dsh-auto-root{width:100%;margin:0 auto;padding-top:0;}",
1405
1743
  ".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;}",
1406
- ".dsh-auto-root{--dsh-auto-border:var(--dsw-alias-border-l2,rgba(15,17,21,.14));--dsh-auto-border-strong:var(--dsw-alias-border-l3,rgba(15,17,21,.2));--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-primary-fill:var(--dsw-alias-button-primary-fill,#0f1115);--dsh-auto-primary-hover:var(--dsw-alias-button-primary-hover,#34415b);--dsh-auto-on-primary:var(--dsw-alias-label-primary-foreground,#fff);--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);}",
1744
+ ".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);}",
1407
1745
  "body:not([data-ds-dark-theme]) .dsh-auto-root{color-scheme:light;}",
1408
1746
  "body[data-ds-dark-theme] .dsh-auto-root{color-scheme:dark;}",
1409
1747
  ".dsh-auto-root *,.dsh-auto-root *::before,.dsh-auto-root *::after{box-sizing:border-box;}",
@@ -1415,21 +1753,30 @@ window.__ModuleLoader__.load({
1415
1753
  ".dsh-auto-flash-success{background:color-mix(in srgb,var(--dsh-auto-success) 12%,transparent);color:var(--dsh-auto-success);}",
1416
1754
  ".dsh-auto-flash-warn{background:color-mix(in srgb,var(--dsh-auto-warning) 12%,transparent);color:var(--dsh-auto-warning);}",
1417
1755
  ".dsh-auto-linkbtn{appearance:none;font:inherit;font-size:inherit;color:inherit;text-decoration:underline;cursor:pointer;background:none;border:none;padding:0;}",
1418
- ".dsh-auto-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:5px 12px;border:1px solid var(--dsh-auto-border);border-radius:16px;background:transparent;color:var(--dsh-auto-text);font-family:inherit;font-size:13px;line-height:1.2;cursor:pointer;transition:background .15s ease,color .15s ease,opacity .15s ease;white-space:nowrap;}",
1419
- ".dsh-auto-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.08)));}",
1420
- ".dsh-auto-btn:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsh-auto-border-strong);}",
1421
- ".dsh-auto-btn:disabled{opacity:.4;cursor:default;}",
1422
- ".dsh-auto-btn-primary{background:var(--dsh-auto-primary-fill);border-color:transparent;color:var(--dsh-auto-on-primary);}",
1423
- ".dsh-auto-btn-primary:hover:not(:disabled){background:var(--dsh-auto-primary-hover);}",
1424
- ".dsh-auto-btn-danger,.dsh-auto-btn-danger-ghost{background:transparent;border-color:transparent;color:var(--dsh-auto-danger);}",
1425
- ".dsh-auto-btn-danger:hover:not(:disabled),.dsh-auto-btn-danger-ghost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger,color-mix(in srgb,var(--dsh-auto-danger) 10%,transparent));}",
1426
- ".dsh-auto-btn-ghost{border-color:transparent;color:var(--dsh-auto-text-secondary);}",
1756
+ ".dsh-auto-danger-button{color:var(--dsh-auto-danger);}",
1757
+ ".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));}",
1427
1758
  ".dsh-auto-input,.dsh-auto-select,.dsh-auto-textarea{width:100%;min-height:34px;padding:6px 10px;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:13px;line-height:1.5;}",
1428
1759
  ".dsh-auto-input,.dsh-auto-textarea{appearance:none;}",
1429
1760
  ".dsh-auto-input::placeholder,.dsh-auto-textarea::placeholder{color:var(--dsh-auto-caption);}",
1430
1761
  ".dsh-auto-select option{background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);}",
1431
1762
  ".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);}",
1432
1763
  ".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;}",
1764
+ ".dsh-auto-picker-root{width:100%;display:flex;}",
1765
+ ".dsh-auto-picker-trigger{appearance:none;width:100%;height:34px;padding:0 9px;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:13px;line-height:20px;text-align:left;cursor:pointer;}",
1766
+ ".dsh-auto-picker-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsh-auto-input-bg));}",
1767
+ ".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);}",
1768
+ ".dsh-auto-picker-trigger-icon{flex:none;color:var(--dsh-auto-muted);}",
1769
+ ".dsh-auto-picker-trigger-label{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1770
+ ".dsh-auto-picker-chevron{flex:none;color:var(--dsh-auto-muted);transition:transform .12s ease;}",
1771
+ ".dsh-auto-picker-chevron-open{transform:rotate(180deg);}",
1772
+ ".dsh-auto-picker-item-copy{min-width:0;display:flex;flex-direction:column;white-space:normal;}",
1773
+ ".dsh-auto-picker-item-label{color:inherit;font-size:14px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1774
+ ".dsh-auto-picker-item-detail{color:var(--dsh-auto-muted);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1775
+ ".dsh-auto-permission-glyph{flex:none;}",
1776
+ ".dsh-auto-permission-glyph-read{color:var(--dsh-auto-muted);}",
1777
+ ".dsh-auto-permission-glyph-write{color:var(--dsh-auto-accent);}",
1778
+ ".dsh-auto-permission-glyph-danger{color:var(--dsh-auto-danger);}",
1779
+ ".dsh-auto-permission-glyph-custom{color:var(--dsh-auto-muted);}",
1433
1780
  ".dsh-auto-textarea{resize:vertical;min-height:110px;}",
1434
1781
  ".dsh-auto-check{display:inline-flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;min-height:34px;}",
1435
1782
  ".dsh-auto-check input{width:16px;height:16px;margin:0;accent-color:var(--dsh-auto-accent);cursor:pointer;}",
@@ -1452,6 +1799,8 @@ window.__ModuleLoader__.load({
1452
1799
  ".dsh-auto-card-id{font-size:11px;color:var(--dsh-auto-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}",
1453
1800
  ".dsh-auto-card-meta{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:12px;color:var(--dsh-auto-text-secondary);line-height:1.5;}",
1454
1801
  ".dsh-auto-card-meta span{white-space:nowrap;}",
1802
+ ".dsh-auto-card-permission{display:inline-flex;align-items:center;gap:4px;}",
1803
+ ".dsh-auto-card-permission svg{width:14px;height:14px;}",
1455
1804
  ".dsh-auto-next{font-variant-numeric:tabular-nums;}",
1456
1805
  ".dsh-auto-muted{color:var(--dsh-auto-muted);}",
1457
1806
  ".dsh-auto-card-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}",
@@ -1528,6 +1877,8 @@ window.__ModuleLoader__.load({
1528
1877
  }
1529
1878
 
1530
1879
  exports.agentPresetDisplayLabel = agentPresetDisplayLabel;
1880
+ exports.permissionPresetPresentation = permissionPresetPresentation;
1881
+ exports.PermissionPresetPicker = PermissionPresetPicker;
1531
1882
  exports.inject = inject;
1532
1883
  exports.apply = apply;
1533
1884
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncended/dsh-automations",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Durable cron automations for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -98,6 +98,7 @@
98
98
  "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
99
99
  "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
100
100
  "@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2",
101
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
101
102
  "@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
102
103
  "@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2",
103
104
  "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",