@michengai/dsh-automation 0.1.9 → 0.1.11

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.
@@ -17,7 +17,7 @@
17
17
  - [x] 原生侧栏定时页签:包裹官方任务树,不依赖 dsh-codex-ui
18
18
  - [x] 单测与包契约
19
19
  - [x] 定时侧栏会话菜单互斥,避免点第二条时与第一条重叠
20
- - [x] 新建弹窗工作区改为官方目录选择,点旁边不关闭
20
+ - [x] 新建弹窗只允许选择 Host 已注册工作区,新增目录统一从 Host 工作区入口完成
21
21
  - [x] 修复任务树过滤导致的 Loading plugins / 点任务卡死
22
22
  - [x] 会话创建定时任务按官方审批策略分流,并补系统提示避免走 crontab
23
23
  - [x] 工作区标题栏对齐官方位置与搜索点击
@@ -36,7 +36,7 @@
36
36
 
37
37
  ## 已完成内容
38
38
 
39
- 仓库内已落地 Host 插件、Web 客户端、原生侧栏定时页签、文档和单测。定时会话更多菜单改为列表级互斥,并用固定定位弹出,避免两条菜单叠在一起。新建弹窗添加工作区改为和 Chat 一样的目录选择,点遮罩不再关闭。
39
+ 仓库内已落地 Host 插件、Web 客户端、原生侧栏定时页签、文档和单测。定时会话更多菜单改为列表级互斥,并用固定定位弹出,避免两条菜单叠在一起。新建弹窗只消费 Host 已注册工作区,避免插件 RPC 自行扩大目录边界;点遮罩不再关闭。
40
40
 
41
41
  ## 待处理事项
42
42
 
package/lib/client.js CHANGED
@@ -79,7 +79,7 @@ function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], def
79
79
  permission: defaultPermission,
80
80
  workspaceId: workspaces[0]?.id ?? "",
81
81
  modelKey: defaultModel === void 0 || defaultModel === null ? "default" : `${defaultModel.provider}::${defaultModel.model}`,
82
- reasoningEffort: "high",
82
+ reasoningEffort: defaultModel?.reasoning?.defaultEffort ?? "none",
83
83
  skills: []
84
84
  };
85
85
  }
@@ -193,19 +193,13 @@ function groupHistory(runs, range, now, t) {
193
193
  const at = new Date(run.finishedAt ?? run.startedAt ?? run.scheduledFor);
194
194
  if (Number.isNaN(at.getTime())) continue;
195
195
  let key;
196
- let label;
197
196
  if (range === "month") {
198
197
  key = `${at.getFullYear()}-${String(at.getMonth() + 1).padStart(2, "0")}`;
199
- label = t("history.month", { month: `${at.getFullYear()}/${at.getMonth() + 1}` });
200
198
  } else if (range === "week") {
201
199
  const start = startOfWeek(at);
202
- key = start.toISOString().slice(0, 10);
203
- label = t("history.week", { date: `${start.getMonth() + 1}/${start.getDate()}` });
200
+ key = localDayKey(start);
204
201
  } else {
205
202
  key = localDayKey(at);
206
- const today = localDayKey(now);
207
- const yesterday = localDayKey(new Date(now.getTime() - 864e5));
208
- label = key === today ? t("history.today") : key === yesterday ? t("history.yesterday") : t("history.date", { date: `${at.getMonth() + 1}/${at.getDate()}` });
209
203
  }
210
204
  const existing = buckets.get(key) ?? [];
211
205
  existing.push(run);
@@ -239,7 +233,7 @@ function formFromAutomation(item, workspaces = [], defaultModel, defaultPermissi
239
233
  permission: item.permission,
240
234
  workspaceId: item.workspaceId ?? base.workspaceId,
241
235
  modelKey,
242
- reasoningEffort: item.reasoningEffort ?? "high"
236
+ reasoningEffort: item.reasoningEffort ?? "none"
243
237
  };
244
238
  switch (schedule.kind) {
245
239
  case "once":
@@ -411,16 +405,6 @@ function permissionLabel(option, t) {
411
405
  return option.name || option.value;
412
406
  }
413
407
 
414
- // src/client/create-modal-logic.ts
415
- function shouldConfirmFullAccess(current, next) {
416
- return current !== next && next === "danger-full-access";
417
- }
418
- async function adoptPickedWorkspace(input) {
419
- const path = await input.pick();
420
- if (path == null || path.trim() === "") return null;
421
- return input.add(path.trim());
422
- }
423
-
424
408
  // src/client/menu.tsx
425
409
  var import_react = require("react");
426
410
  var import_react_dom = require("react-dom");
@@ -465,6 +449,7 @@ function MenuPopup({
465
449
  up,
466
450
  end,
467
451
  className,
452
+ ariaLabel,
468
453
  children,
469
454
  onClick
470
455
  }) {
@@ -489,6 +474,8 @@ function MenuPopup({
489
474
  {
490
475
  ref: menuRef,
491
476
  className: `${className}${host !== null ? " is-float" : ""}`,
477
+ role: "menu",
478
+ "aria-label": ariaLabel,
492
479
  style: host !== null ? style : void 0,
493
480
  onMouseDown: (event) => event.stopPropagation(),
494
481
  onClick: (event) => {
@@ -632,9 +619,11 @@ var MINUTES = Array.from({ length: 60 }, (_, index) => String(index).padStart(2,
632
619
  function CreateModal({
633
620
  t,
634
621
  permissionT,
622
+ modelT,
635
623
  busy,
636
624
  workspaces,
637
625
  models,
626
+ modelFailures,
638
627
  defaultModel,
639
628
  skills,
640
629
  permissions,
@@ -642,39 +631,21 @@ function CreateModal({
642
631
  draft,
643
632
  editing,
644
633
  onClose,
645
- onSubmit,
646
- onAddWorkspace,
647
- pickWorkspaceDirectory
634
+ onSubmit
648
635
  }) {
649
636
  const [form, setForm] = (0, import_react2.useState)(() => ({ ...defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel, defaultPermission), ...draft }));
650
637
  const [validationError, setValidationError] = (0, import_react2.useState)();
651
- const [confirmingPermission, setConfirmingPermission] = (0, import_react2.useState)();
652
- const [fullAccessAcknowledged, setFullAccessAcknowledged] = (0, import_react2.useState)(false);
653
638
  const update = (patch) => {
654
639
  setForm((current) => ({ ...current, ...patch }));
655
640
  setValidationError(void 0);
656
641
  };
657
642
  const choosePermission = (permission) => {
658
- if (shouldConfirmFullAccess(form.permission, permission)) {
659
- setFullAccessAcknowledged(false);
660
- setConfirmingPermission(permission);
661
- return;
662
- }
663
643
  update({ permission });
664
644
  };
665
- const cancelFullAccessConfirmation = () => {
666
- setFullAccessAcknowledged(false);
667
- setConfirmingPermission(void 0);
668
- };
669
- const confirmFullAccess = () => {
670
- if (!fullAccessAcknowledged) return;
671
- if (confirmingPermission === void 0) return;
672
- update({ permission: confirmingPermission });
673
- cancelFullAccessConfirmation();
674
- };
675
645
  (0, import_react2.useEffect)(() => {
676
646
  const onKey = (event) => {
677
647
  if (event.key !== "Escape") return;
648
+ if (document.querySelector(".dsh-st-model-select-menu") !== null) return;
678
649
  event.preventDefault();
679
650
  event.stopPropagation();
680
651
  event.stopImmediatePropagation();
@@ -813,30 +784,7 @@ function CreateModal({
813
784
  onClick: () => update({ workspaceId: item.id })
814
785
  },
815
786
  item.id
816
- )),
817
- pickWorkspaceDirectory !== void 0 && onAddWorkspace !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
818
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-menu-split" }),
819
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
820
- MenuRow,
821
- {
822
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PlusIcon, { width: 14, height: 14 }),
823
- label: t("form.addWorkspace"),
824
- onClick: () => {
825
- void (async () => {
826
- try {
827
- const id = await adoptPickedWorkspace({
828
- pick: pickWorkspaceDirectory,
829
- add: onAddWorkspace
830
- });
831
- if (id !== null) update({ workspaceId: id });
832
- } catch (caught) {
833
- setValidationError(caught instanceof Error ? caught.message : t("error.action"));
834
- }
835
- })();
836
- }
837
- }
838
- )
839
- ] })
787
+ ))
840
788
  ] }),
841
789
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuPanel, { ghost: true, up: true, label: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
842
790
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, { width: 14, height: 14 }),
@@ -872,10 +820,12 @@ function CreateModal({
872
820
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-composer-right", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
873
821
  ModelPicker,
874
822
  {
875
- t,
823
+ modelT,
876
824
  models,
825
+ failures: modelFailures,
877
826
  modelKey: form.modelKey,
878
- onModelKey: (value) => update({ modelKey: value, reasoningEffort: "none" })
827
+ reasoningEffort: form.reasoningEffort,
828
+ onSelection: (modelKey, reasoningEffort) => update({ modelKey, reasoningEffort })
879
829
  }
880
830
  ) })
881
831
  ] })
@@ -887,84 +837,169 @@ function CreateModal({
887
837
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "dsh-st-btn dsh-st-btn--primary", disabled: busy, children: t("modal.save") })
888
838
  ] })
889
839
  ] }),
890
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-flyout-root", ref: setMenuHost }),
891
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
892
- import_dsh_client_ui_primitives.RiskConfirmation,
893
- {
894
- open: confirmingPermission !== void 0,
895
- title: permissionT("confirm.title"),
896
- description: permissionT("confirm.description"),
897
- acknowledgeLabel: permissionT("confirm.acknowledge"),
898
- cancelLabel: permissionT("confirm.cancel"),
899
- confirmLabel: permissionT("confirm.enable"),
900
- acknowledged: fullAccessAcknowledged,
901
- onAcknowledgedChange: setFullAccessAcknowledged,
902
- onCancel: cancelFullAccessConfirmation,
903
- onConfirm: confirmFullAccess
904
- }
905
- )
840
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-flyout-root", ref: setMenuHost })
906
841
  ] }) });
907
842
  }
908
843
  function ModelPicker({
909
- t,
844
+ modelT,
910
845
  models,
846
+ failures,
911
847
  modelKey,
912
- onModelKey
848
+ reasoningEffort,
849
+ onSelection
913
850
  }) {
914
851
  const menu = useMenuState();
852
+ const [pane, setPane] = (0, import_react2.useState)("root");
915
853
  const selected = models.find((item) => `${item.provider}::${item.model}` === modelKey);
916
- const trigger = selected?.label ?? t("form.modelSelect");
854
+ const reasoning = selected?.reasoning;
855
+ const effectiveEffort = reasoningEffort === "none" ? reasoning?.defaultEffort : reasoningEffort;
856
+ const effortLabel = reasoning === void 0 ? void 0 : effectiveEffort === void 0 ? modelT("effort.providerDefault") : reasoning.efforts.find((item) => item.id === effectiveEffort)?.name ?? effectiveEffort;
857
+ const trigger = selected?.label ?? modelT("trigger.fallback");
917
858
  const modelGroups = Array.from(models.reduce((groups, item) => {
918
- const group = groups.get(item.provider) ?? [];
919
- group.push(item);
859
+ const group = groups.get(item.provider) ?? { label: item.providerLabel, models: [] };
860
+ group.models.push(item);
920
861
  groups.set(item.provider, group);
921
862
  return groups;
922
863
  }, /* @__PURE__ */ new Map()));
864
+ (0, import_react2.useEffect)(() => {
865
+ if (!menu.open) return;
866
+ const onKey = (event) => {
867
+ if (event.key !== "Escape") return;
868
+ event.preventDefault();
869
+ event.stopPropagation();
870
+ event.stopImmediatePropagation();
871
+ if (pane !== "root") setPane("root");
872
+ else menu.setOpen(false);
873
+ };
874
+ window.addEventListener("keydown", onKey, true);
875
+ return () => {
876
+ window.removeEventListener("keydown", onKey, true);
877
+ };
878
+ }, [menu.open, menu.setOpen, pane]);
879
+ const selectModel = (item) => {
880
+ onSelection(
881
+ `${item.provider}::${item.model}`,
882
+ item.reasoning?.defaultEffort ?? "none"
883
+ );
884
+ menu.setOpen(false);
885
+ setPane("root");
886
+ };
887
+ const selectEffort = (effort) => {
888
+ onSelection(modelKey, effort);
889
+ menu.setOpen(false);
890
+ setPane("root");
891
+ };
923
892
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `dsh-st-model-select${menu.open ? " is-open" : ""}`, ref: menu.root, children: [
924
893
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
925
894
  "button",
926
895
  {
927
896
  type: "button",
928
897
  className: "dsh-st-model-select-trigger",
898
+ "aria-label": selected === void 0 ? modelT("trigger.selectAria") : effortLabel === void 0 ? modelT("trigger.aria", { model: selected.label }) : modelT("trigger.ariaEffort", { model: selected.label, effort: effortLabel }),
929
899
  onMouseDown: (event) => event.stopPropagation(),
930
900
  onClick: () => {
931
901
  if (menu.open) {
932
902
  menu.setOpen(false);
933
903
  return;
934
904
  }
905
+ setPane("root");
935
906
  menu.setOpen(true);
936
907
  },
937
908
  children: [
938
909
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: trigger }),
939
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("em", {})
910
+ effortLabel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-trigger-effort", children: effortLabel }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutline14, { className: `dsh-st-model-trigger-chevron${menu.open ? " is-open" : ""}` })
940
912
  ]
941
913
  }
942
914
  ),
943
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MenuPopup, { open: menu.open, anchor: menu.root, menuRef: menu.menu, up: true, end: true, className: "dsh-st-model-select-menu is-up is-end", children: modelGroups.map(([provider, group]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { role: "group", "aria-label": provider, className: "dsh-st-model-group", children: [
944
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-model-group-title", children: provider }),
945
- group.map((item) => {
946
- const value = `${item.provider}::${item.model}`;
947
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
915
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuPopup, { open: menu.open, anchor: menu.root, menuRef: menu.menu, up: true, end: true, className: "dsh-st-model-select-menu is-up is-end", ariaLabel: modelT("menu.aria"), children: [
916
+ pane === "root" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
918
+ MenuRow,
919
+ {
920
+ kv: true,
921
+ label: modelT("menu.model"),
922
+ hint: selected?.label ?? modelT("trigger.fallback"),
923
+ chevron: true,
924
+ onClick: () => setPane("model")
925
+ }
926
+ ),
927
+ reasoning !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
928
+ MenuRow,
929
+ {
930
+ kv: true,
931
+ label: modelT("menu.effort"),
932
+ hint: effortLabel ?? modelT("effort.providerDefault"),
933
+ chevron: true,
934
+ onClick: () => setPane("effort")
935
+ }
936
+ )
937
+ ] }),
938
+ pane === "model" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
939
+ failures.map((failure) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-model-warning", children: modelT("warning.groupLoad", { name: failure.providerLabel, message: failure.message }) }, failure.provider)),
940
+ modelGroups.map(([provider, group]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { role: "group", "aria-label": group.label, className: "dsh-st-model-group", children: [
941
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-model-group-title", children: group.label }),
942
+ group.models.map((item) => {
943
+ const value = `${item.provider}::${item.model}`;
944
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
945
+ "button",
946
+ {
947
+ type: "button",
948
+ role: "menuitemradio",
949
+ "aria-checked": value === modelKey,
950
+ className: "dsh-st-model-option",
951
+ title: item.label,
952
+ onClick: () => selectModel(item),
953
+ children: [
954
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "dsh-st-model-option-copy", children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-name", children: item.label }),
956
+ item.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-description", children: item.description })
957
+ ] }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-check", children: value === modelKey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives.IconCheckOutline16, {}) })
959
+ ]
960
+ },
961
+ value
962
+ );
963
+ })
964
+ ] }, provider)),
965
+ modelGroups.length === 0 && failures.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-model-empty", children: modelT("empty.models") })
966
+ ] }),
967
+ pane === "effort" && reasoning !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
968
+ reasoning.defaultEffort === void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
948
969
  "button",
949
970
  {
950
971
  type: "button",
951
972
  role: "menuitemradio",
952
- "aria-checked": value === modelKey,
973
+ "aria-checked": reasoningEffort === "none",
953
974
  className: "dsh-st-model-option",
954
- title: item.label,
955
- onClick: () => {
956
- onModelKey(value);
957
- menu.setOpen(false);
958
- },
975
+ onClick: () => selectEffort("none"),
959
976
  children: [
960
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-option-copy", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-name", children: item.label }) }),
961
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-check", children: value === modelKey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckOutlineIcon, { width: 16, height: 16 }) })
977
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-option-copy", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-name", children: modelT("effort.providerDefault") }) }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-check", children: reasoningEffort === "none" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives.IconCheckOutline16, {}) })
979
+ ]
980
+ }
981
+ ),
982
+ reasoning.efforts.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
983
+ "button",
984
+ {
985
+ type: "button",
986
+ role: "menuitemradio",
987
+ "aria-checked": effectiveEffort === item.id,
988
+ className: "dsh-st-model-option",
989
+ onClick: () => selectEffort(item.id),
990
+ children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "dsh-st-model-option-copy", children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-name", children: item.name }),
993
+ item.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-description", children: item.description })
994
+ ] }),
995
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-model-check", children: effectiveEffort === item.id && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives.IconCheckOutline16, {}) })
962
996
  ]
963
997
  },
964
- value
965
- );
966
- })
967
- ] }, provider)) })
998
+ item.id
999
+ )),
1000
+ reasoning.efforts.length === 0 && reasoning.defaultEffort !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-model-empty", children: modelT("empty.efforts") })
1001
+ ] })
1002
+ ] })
968
1003
  ] });
969
1004
  }
970
1005
  function TimeSelect({
@@ -1049,6 +1084,7 @@ function unwrapRpcResult(value) {
1049
1084
 
1050
1085
  // src/client/runtime.ts
1051
1086
  var CHANNEL = "/dsh-automation";
1087
+ var POLL_INTERVAL_MS = 15e3;
1052
1088
  function isTransportError(error) {
1053
1089
  const message = error instanceof Error ? error.message : String(error);
1054
1090
  return /failed to fetch|networkerror|load failed|network request failed/i.test(message);
@@ -1056,6 +1092,7 @@ function isTransportError(error) {
1056
1092
  function createAutomationRuntime(rpc) {
1057
1093
  let state = { phase: "idle" };
1058
1094
  let refreshPromise;
1095
+ let pollTimer;
1059
1096
  const listeners2 = /* @__PURE__ */ new Set();
1060
1097
  const publish = (next) => {
1061
1098
  state = next;
@@ -1065,8 +1102,20 @@ function createAutomationRuntime(rpc) {
1065
1102
  getSnapshot: () => state,
1066
1103
  subscribe: (listener) => {
1067
1104
  listeners2.add(listener);
1105
+ if (listeners2.size === 1) {
1106
+ queueMicrotask(() => {
1107
+ if (listeners2.size > 0) void refresh().catch(() => void 0);
1108
+ });
1109
+ pollTimer = setInterval(() => {
1110
+ void refresh().catch(() => void 0);
1111
+ }, POLL_INTERVAL_MS);
1112
+ }
1068
1113
  return () => {
1069
1114
  listeners2.delete(listener);
1115
+ if (listeners2.size === 0 && pollTimer !== void 0) {
1116
+ clearInterval(pollTimer);
1117
+ pollTimer = void 0;
1118
+ }
1070
1119
  };
1071
1120
  }
1072
1121
  };
@@ -1166,25 +1215,18 @@ function createAutomationRuntime(rpc) {
1166
1215
  return rest;
1167
1216
  })
1168
1217
  }));
1169
- },
1170
- async addWorkspace(path) {
1171
- const payload = { sessionId: "settings", path };
1172
- const value = unwrapRpcResult(await rpc.call(CHANNEL, "add-workspace", payload));
1173
- await refresh();
1174
- return value;
1175
1218
  }
1176
1219
  };
1177
1220
  }
1178
1221
 
1179
1222
  // src/client/AutomationView.tsx
1180
1223
  var import_jsx_runtime4 = require("react/jsx-runtime");
1181
- var POLL_INTERVAL_MS = 15e3;
1182
1224
  var EXAMPLES = [
1183
1225
  { name: "\u6BCF\u65E5\u56DE\u5F52\u68C0\u67E5", scheduleKind: "daily", time: "09:00", weekdays: [1, 2, 3, 4, 5] },
1184
1226
  { name: "\u6BCF\u5468\u4F9D\u8D56\u5DE1\u68C0", scheduleKind: "weekly", time: "10:00", weekdays: [1] },
1185
1227
  { name: "\u5DE5\u4F5C\u65E5\u65E9\u62A5", scheduleKind: "weekly", time: "08:00", weekdays: [1, 2, 3, 4, 5] }
1186
1228
  ];
1187
- function AutomationView({ t, permissionT, runtime, closeSettings, pickWorkspaceDirectory }) {
1229
+ function AutomationView({ t, permissionT, modelT, runtime, closeSettings }) {
1188
1230
  const state = (0, import_react3.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
1189
1231
  const [tab, setTab] = (0, import_react3.useState)("mine");
1190
1232
  const [query, setQuery] = (0, import_react3.useState)("");
@@ -1197,15 +1239,6 @@ function AutomationView({ t, permissionT, runtime, closeSettings, pickWorkspaceD
1197
1239
  const [historyTask, setHistoryTask] = (0, import_react3.useState)("all");
1198
1240
  const [historyStatus, setHistoryStatus] = (0, import_react3.useState)("all");
1199
1241
  const now = (0, import_react3.useMemo)(() => new Date(state.snapshot?.serverNow ?? Date.now()), [state.snapshot?.serverNow, state.refreshedAt]);
1200
- (0, import_react3.useEffect)(() => {
1201
- void runtime.refresh().catch(() => void 0);
1202
- const timer = window.setInterval(() => {
1203
- void runtime.refresh().catch(() => void 0);
1204
- }, POLL_INTERVAL_MS);
1205
- return () => {
1206
- window.clearInterval(timer);
1207
- };
1208
- }, [runtime]);
1209
1242
  const snapshot = state.snapshot;
1210
1243
  const workspaces = snapshot?.workspaces ?? [];
1211
1244
  const models = snapshot?.models ?? [];
@@ -1357,15 +1390,15 @@ function AutomationView({ t, permissionT, runtime, closeSettings, pickWorkspaceD
1357
1390
  {
1358
1391
  t,
1359
1392
  permissionT,
1393
+ modelT,
1360
1394
  busy,
1361
1395
  workspaces,
1362
1396
  models,
1397
+ modelFailures: snapshot?.modelFailures ?? [],
1363
1398
  defaultModel: snapshot?.defaultModel ?? null,
1364
1399
  skills: snapshot?.skills ?? [],
1365
1400
  permissions,
1366
1401
  defaultPermission,
1367
- onAddWorkspace: async (path) => runtime.addWorkspace(path).then((value) => value.id),
1368
- ...pickWorkspaceDirectory === void 0 ? {} : { pickWorkspaceDirectory },
1369
1402
  editing: editingId !== void 0,
1370
1403
  ...draft === void 0 ? {} : { draft },
1371
1404
  onClose: closeModal,
@@ -1527,7 +1560,6 @@ var en = {
1527
1560
  "tabs.runs": "Run history",
1528
1561
  "sort.created": "Newest first",
1529
1562
  "form.workspace": "Workspace",
1530
- "form.addWorkspace": "Add workspace...",
1531
1563
  "form.workspacePath": "Folder path",
1532
1564
  "form.workspacePathPlaceholder": "D:\\work\\project",
1533
1565
  "form.model": "Model",
@@ -1719,7 +1751,6 @@ var zh = {
1719
1751
  "tabs.runs": "\u6267\u884C\u8BB0\u5F55",
1720
1752
  "sort.created": "\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F",
1721
1753
  "form.workspace": "\u5DE5\u4F5C\u76EE\u5F55",
1722
- "form.addWorkspace": "\u6DFB\u52A0\u5DE5\u4F5C\u533A...",
1723
1754
  "form.workspacePath": "\u76EE\u5F55\u8DEF\u5F84",
1724
1755
  "form.workspacePathPlaceholder": "D:\\work\\project",
1725
1756
  "form.model": "\u6A21\u578B",
@@ -2376,15 +2407,6 @@ function NativeScheduleSessionList(props) {
2376
2407
  const [query, setQuery] = (0, import_react5.useState)("");
2377
2408
  const [sort, setSort] = (0, import_react5.useState)("time");
2378
2409
  const [groupMode, setGroupMode] = (0, import_react5.useState)("workspace");
2379
- (0, import_react5.useEffect)(() => {
2380
- void runtime.refresh().catch(() => void 0);
2381
- const timer = window.setInterval(() => {
2382
- void runtime.refresh().catch(() => void 0);
2383
- }, 15e3);
2384
- return () => {
2385
- window.clearInterval(timer);
2386
- };
2387
- }, [runtime]);
2388
2410
  const archived = (0, import_react5.useMemo)(() => new Set(archivedIds), [archivedIds]);
2389
2411
  const listedIds = useSessions ? useSessions((snap) => Array.isArray(snap?.ids) ? snap.ids : void 0) : void 0;
2390
2412
  const presentIds = (0, import_react5.useMemo)(() => {
@@ -2619,15 +2641,6 @@ function ScheduleRail({
2619
2641
  }) {
2620
2642
  const state = (0, import_react6.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
2621
2643
  const [folded, setFolded] = (0, import_react6.useState)({});
2622
- (0, import_react6.useEffect)(() => {
2623
- void runtime.refresh().catch(() => void 0);
2624
- const timer = window.setInterval(() => {
2625
- void runtime.refresh().catch(() => void 0);
2626
- }, 15e3);
2627
- return () => {
2628
- window.clearInterval(timer);
2629
- };
2630
- }, [runtime]);
2631
2644
  const groups = (0, import_react6.useMemo)(() => {
2632
2645
  const snapshot = state.snapshot;
2633
2646
  if (snapshot === void 0) return [];
@@ -2897,7 +2910,7 @@ var CSS_TEXT = `
2897
2910
  .dsh-st-chip-btn:hover{background:rgba(255,255,255,.06);color:var(--dsw-alias-label-primary)}
2898
2911
  .dsh-st-chip-btn.is-static{cursor:default;opacity:.78}
2899
2912
  .dsh-st-chip-btn em,.dsh-st-select.is-pill .dsh-st-select-btn em{width:6px;height:6px;margin-left:2px;opacity:.55}
2900
- .dsh-st-model-select{position:relative;z-index:1;min-width:0;flex:none}.dsh-st-model-select.is-open{z-index:30}.dsh-st-model-select-trigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:220px;height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.dsh-st-model-select-trigger:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsh-st-model-select-trigger>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsh-st-model-select-trigger em{width:6px;height:6px;margin-left:2px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg) translateY(-2px);opacity:.55;flex:none}.dsh-st-model-select-menu{z-index:30;width:min(240px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));overflow-y:auto;padding:4px;border:1px solid var(--dsw-alias-border-inverted,var(--dsw-alias-border-l2));border-radius:12px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-base));box-shadow:var(--dsw-shadow-lv3)}.dsh-st-model-select-menu.is-float{position:absolute;z-index:1200;box-sizing:border-box}.dsh-st-model-select-menu .dsh-st-menu-row{min-height:40px;padding:0 10px;border-radius:10px;font-size:14px}.dsh-st-model-select-menu .dsh-st-menu-row.is-kv .dsh-st-menu-row-side{font-size:13px;color:var(--dsw-alias-label-tertiary)}.dsh-st-model-group+.dsh-st-model-group{margin-top:4px}.dsh-st-model-group-title{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-base));color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.dsh-st-model-option{display:flex;width:100%;min-height:38px;align-items:center;gap:8px;padding:6px 8px;border:0;border-radius:10px;background:transparent;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.dsh-st-model-option:hover,.dsh-st-model-option:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}.dsh-st-model-option-copy{display:flex;min-width:0;flex:1;flex-direction:column}.dsh-st-model-name{overflow:hidden;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.dsh-st-model-check{display:grid;flex:0 0 18px;place-items:center;color:var(--dsw-alias-label-primary)}
2913
+ .dsh-st-model-select{position:relative;z-index:1;min-width:0;flex:none}.dsh-st-model-select.is-open{z-index:30}.dsh-st-model-select-trigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:260px;height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.dsh-st-model-select-trigger:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsh-st-model-select-trigger>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsh-st-model-trigger-effort{flex:none;color:var(--dsw-alias-label-tertiary);white-space:nowrap}.dsh-st-model-trigger-chevron{flex:none;transition:transform .16s ease}.dsh-st-model-trigger-chevron.is-open{transform:rotate(180deg)}.dsh-st-model-select-menu{z-index:30;width:min(240px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));overflow-y:auto;padding:4px;border:1px solid var(--dsw-alias-border-inverted,var(--dsw-alias-border-l2));border-radius:12px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-base));box-shadow:var(--dsw-shadow-lv3)}.dsh-st-model-select-menu.is-float{position:absolute;z-index:1200;box-sizing:border-box}.dsh-st-model-select-menu .dsh-st-menu-row{min-height:40px;padding:0 10px;border-radius:10px;font-size:14px}.dsh-st-model-select-menu .dsh-st-menu-row.is-kv .dsh-st-menu-row-side{font-size:13px;color:var(--dsw-alias-label-tertiary)}.dsh-st-model-group+.dsh-st-model-group{margin-top:4px}.dsh-st-model-group-title{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-base));color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.dsh-st-model-option{display:flex;width:100%;min-height:38px;align-items:center;gap:8px;padding:6px 8px;border:0;border-radius:10px;background:transparent;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.dsh-st-model-option:hover,.dsh-st-model-option:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}.dsh-st-model-option-copy{display:flex;min-width:0;flex:1;flex-direction:column}.dsh-st-model-name{overflow:hidden;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.dsh-st-model-description{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsh-st-model-check{display:grid;flex:0 0 18px;place-items:center;color:var(--dsw-alias-label-primary)}.dsh-st-model-warning{margin:4px;padding:8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger,rgba(248,81,73,.1));color:var(--dsw-alias-state-error-primary,#f85149);font-size:12px;line-height:18px}.dsh-st-model-empty{padding:14px 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}
2901
2914
  .dsh-st-suffix{color:var(--dsw-alias-label-secondary);font-size:13px}
2902
2915
  .dsh-st-inline input.is-narrow{width:72px;flex:none}
2903
2916
  .dsh-st-time{display:inline-flex;align-items:center;gap:2px;min-height:36px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:rgba(255,255,255,.04)}
@@ -2958,20 +2971,30 @@ var name = "dsh-automation-client";
2958
2971
  var inject = ["slots", "locale", "connection", "sessions"];
2959
2972
  var SETTINGS_CLOCK_SVG = '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden="true" data-dsh-schedule-icon="1"><path fill="currentColor" d="M8 1.15A6.85 6.85 0 1 0 8 14.85 6.85 6.85 0 0 0 8 1.15Zm0 1.4a5.45 5.45 0 1 1 0 10.9 5.45 5.45 0 0 1 0-10.9Z"/><path fill="currentColor" d="M8.62 4.35H7.28v4.2l3.02 1.78.67-1.13-2.35-1.39V4.35Z"/></svg>';
2960
2973
  function installSettingsNavIcon(labels) {
2961
- const apply2 = () => {
2962
- const wanted = new Set(labels().map((item) => item.trim()).filter((item) => item !== ""));
2963
- const buttons = document.querySelectorAll("button");
2964
- for (const button of buttons) {
2965
- const text = (button.textContent ?? "").replace(/\s+/g, " ").trim();
2966
- if (!wanted.has(text)) continue;
2967
- const svg = button.querySelector("svg");
2968
- if (svg === null || svg.getAttribute("data-dsh-schedule-icon") === "1") continue;
2969
- svg.outerHTML = SETTINGS_CLOCK_SVG;
2970
- }
2974
+ const wanted = new Set(labels().map((item) => item.trim()).filter((item) => item !== ""));
2975
+ const applyButton = (button) => {
2976
+ const text = (button.textContent ?? "").replace(/\s+/g, " ").trim();
2977
+ if (!wanted.has(text)) return;
2978
+ const svg = button.querySelector("svg");
2979
+ if (svg === null || svg.getAttribute("data-dsh-schedule-icon") === "1") return;
2980
+ svg.outerHTML = SETTINGS_CLOCK_SVG;
2981
+ };
2982
+ const scan = (root) => {
2983
+ if (root instanceof HTMLButtonElement) applyButton(root);
2984
+ for (const button of root.querySelectorAll?.("button") ?? []) applyButton(button);
2971
2985
  };
2972
- apply2();
2973
- const observer = new MutationObserver(apply2);
2974
- observer.observe(document.documentElement, { childList: true, subtree: true });
2986
+ scan(document);
2987
+ const observer = new MutationObserver((mutations) => {
2988
+ for (const mutation of mutations) {
2989
+ const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
2990
+ const owner = target?.closest("button");
2991
+ if (owner instanceof HTMLButtonElement) applyButton(owner);
2992
+ for (const node of mutation.addedNodes) {
2993
+ if (node instanceof Element) scan(node);
2994
+ }
2995
+ }
2996
+ });
2997
+ observer.observe(document.documentElement, { childList: true, characterData: true, subtree: true });
2975
2998
  return () => {
2976
2999
  observer.disconnect();
2977
3000
  };
@@ -2981,6 +3004,7 @@ function apply(ctx) {
2981
3004
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-automation: locale");
2982
3005
  const t = ctx.locale.bind(NS);
2983
3006
  const permissionT = ctx.locale.bind("permission.access");
3007
+ const modelT = ctx.locale.bind("model");
2984
3008
  const runtime = createAutomationRuntime(ctx.connection.rpc);
2985
3009
  ctx.effect(() => installSettingsNavIcon(() => [t("tab"), "Scheduled tasks", "\u5B9A\u65F6\u4EFB\u52A1"]), "dsh-automation: settings icon");
2986
3010
  ctx.slots.inject("settings.section", () => ctx.slots.register({
@@ -2991,8 +3015,7 @@ function apply(ctx) {
2991
3015
  label: () => t("tab"),
2992
3016
  icon: "schedule"
2993
3017
  }, function ScheduledTasksSettings(props) {
2994
- const workspacesApi = readOptionalWorkspaces(ctx);
2995
- return (0, import_react7.createElement)(AutomationView, { t, permissionT, runtime, ...props.close === void 0 ? {} : { closeSettings: props.close }, ...workspacesApi === void 0 ? {} : { pickWorkspaceDirectory: () => workspacesApi.pickDirectory() } });
3018
+ return (0, import_react7.createElement)(AutomationView, { t, permissionT, modelT, runtime, ...props.close === void 0 ? {} : { closeSettings: props.close } });
2996
3019
  }));
2997
3020
  ctx.slots.inject("sidebar.schedule", () => ctx.slots.register({
2998
3021
  name: "sidebar.schedule",
@@ -3200,12 +3223,5 @@ function readSlotEntries(ctx, name2) {
3200
3223
  function slotHasEntries(ctx, name2) {
3201
3224
  return readSlotEntries(ctx, name2).length > 0;
3202
3225
  }
3203
- function readOptionalWorkspaces(ctx) {
3204
- try {
3205
- return ctx.workspaces;
3206
- } catch {
3207
- return void 0;
3208
- }
3209
- }
3210
3226
  return module.exports; } });
3211
3227
  //# sourceMappingURL=client.js.map