@michengai/dsh-automation 0.1.10 → 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
@@ -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);
@@ -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");
@@ -647,36 +631,17 @@ function CreateModal({
647
631
  draft,
648
632
  editing,
649
633
  onClose,
650
- onSubmit,
651
- onAddWorkspace,
652
- pickWorkspaceDirectory
634
+ onSubmit
653
635
  }) {
654
636
  const [form, setForm] = (0, import_react2.useState)(() => ({ ...defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel, defaultPermission), ...draft }));
655
637
  const [validationError, setValidationError] = (0, import_react2.useState)();
656
- const [confirmingPermission, setConfirmingPermission] = (0, import_react2.useState)();
657
- const [fullAccessAcknowledged, setFullAccessAcknowledged] = (0, import_react2.useState)(false);
658
638
  const update = (patch) => {
659
639
  setForm((current) => ({ ...current, ...patch }));
660
640
  setValidationError(void 0);
661
641
  };
662
642
  const choosePermission = (permission) => {
663
- if (shouldConfirmFullAccess(form.permission, permission)) {
664
- setFullAccessAcknowledged(false);
665
- setConfirmingPermission(permission);
666
- return;
667
- }
668
643
  update({ permission });
669
644
  };
670
- const cancelFullAccessConfirmation = () => {
671
- setFullAccessAcknowledged(false);
672
- setConfirmingPermission(void 0);
673
- };
674
- const confirmFullAccess = () => {
675
- if (!fullAccessAcknowledged) return;
676
- if (confirmingPermission === void 0) return;
677
- update({ permission: confirmingPermission });
678
- cancelFullAccessConfirmation();
679
- };
680
645
  (0, import_react2.useEffect)(() => {
681
646
  const onKey = (event) => {
682
647
  if (event.key !== "Escape") return;
@@ -819,30 +784,7 @@ function CreateModal({
819
784
  onClick: () => update({ workspaceId: item.id })
820
785
  },
821
786
  item.id
822
- )),
823
- pickWorkspaceDirectory !== void 0 && onAddWorkspace !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
824
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-menu-split" }),
825
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
826
- MenuRow,
827
- {
828
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PlusIcon, { width: 14, height: 14 }),
829
- label: t("form.addWorkspace"),
830
- onClick: () => {
831
- void (async () => {
832
- try {
833
- const id = await adoptPickedWorkspace({
834
- pick: pickWorkspaceDirectory,
835
- add: onAddWorkspace
836
- });
837
- if (id !== null) update({ workspaceId: id });
838
- } catch (caught) {
839
- setValidationError(caught instanceof Error ? caught.message : t("error.action"));
840
- }
841
- })();
842
- }
843
- }
844
- )
845
- ] })
787
+ ))
846
788
  ] }),
847
789
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuPanel, { ghost: true, up: true, label: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
848
790
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, { width: 14, height: 14 }),
@@ -895,22 +837,7 @@ function CreateModal({
895
837
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "dsh-st-btn dsh-st-btn--primary", disabled: busy, children: t("modal.save") })
896
838
  ] })
897
839
  ] }),
898
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-flyout-root", ref: setMenuHost }),
899
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
900
- import_dsh_client_ui_primitives.RiskConfirmation,
901
- {
902
- open: confirmingPermission !== void 0,
903
- title: permissionT("confirm.title"),
904
- description: permissionT("confirm.description"),
905
- acknowledgeLabel: permissionT("confirm.acknowledge"),
906
- cancelLabel: permissionT("confirm.cancel"),
907
- confirmLabel: permissionT("confirm.enable"),
908
- acknowledged: fullAccessAcknowledged,
909
- onAcknowledgedChange: setFullAccessAcknowledged,
910
- onCancel: cancelFullAccessConfirmation,
911
- onConfirm: confirmFullAccess
912
- }
913
- )
840
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-flyout-root", ref: setMenuHost })
914
841
  ] }) });
915
842
  }
916
843
  function ModelPicker({
@@ -1157,6 +1084,7 @@ function unwrapRpcResult(value) {
1157
1084
 
1158
1085
  // src/client/runtime.ts
1159
1086
  var CHANNEL = "/dsh-automation";
1087
+ var POLL_INTERVAL_MS = 15e3;
1160
1088
  function isTransportError(error) {
1161
1089
  const message = error instanceof Error ? error.message : String(error);
1162
1090
  return /failed to fetch|networkerror|load failed|network request failed/i.test(message);
@@ -1164,6 +1092,7 @@ function isTransportError(error) {
1164
1092
  function createAutomationRuntime(rpc) {
1165
1093
  let state = { phase: "idle" };
1166
1094
  let refreshPromise;
1095
+ let pollTimer;
1167
1096
  const listeners2 = /* @__PURE__ */ new Set();
1168
1097
  const publish = (next) => {
1169
1098
  state = next;
@@ -1173,8 +1102,20 @@ function createAutomationRuntime(rpc) {
1173
1102
  getSnapshot: () => state,
1174
1103
  subscribe: (listener) => {
1175
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
+ }
1176
1113
  return () => {
1177
1114
  listeners2.delete(listener);
1115
+ if (listeners2.size === 0 && pollTimer !== void 0) {
1116
+ clearInterval(pollTimer);
1117
+ pollTimer = void 0;
1118
+ }
1178
1119
  };
1179
1120
  }
1180
1121
  };
@@ -1274,25 +1215,18 @@ function createAutomationRuntime(rpc) {
1274
1215
  return rest;
1275
1216
  })
1276
1217
  }));
1277
- },
1278
- async addWorkspace(path) {
1279
- const payload = { sessionId: "settings", path };
1280
- const value = unwrapRpcResult(await rpc.call(CHANNEL, "add-workspace", payload));
1281
- await refresh();
1282
- return value;
1283
1218
  }
1284
1219
  };
1285
1220
  }
1286
1221
 
1287
1222
  // src/client/AutomationView.tsx
1288
1223
  var import_jsx_runtime4 = require("react/jsx-runtime");
1289
- var POLL_INTERVAL_MS = 15e3;
1290
1224
  var EXAMPLES = [
1291
1225
  { name: "\u6BCF\u65E5\u56DE\u5F52\u68C0\u67E5", scheduleKind: "daily", time: "09:00", weekdays: [1, 2, 3, 4, 5] },
1292
1226
  { name: "\u6BCF\u5468\u4F9D\u8D56\u5DE1\u68C0", scheduleKind: "weekly", time: "10:00", weekdays: [1] },
1293
1227
  { name: "\u5DE5\u4F5C\u65E5\u65E9\u62A5", scheduleKind: "weekly", time: "08:00", weekdays: [1, 2, 3, 4, 5] }
1294
1228
  ];
1295
- function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWorkspaceDirectory }) {
1229
+ function AutomationView({ t, permissionT, modelT, runtime, closeSettings }) {
1296
1230
  const state = (0, import_react3.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
1297
1231
  const [tab, setTab] = (0, import_react3.useState)("mine");
1298
1232
  const [query, setQuery] = (0, import_react3.useState)("");
@@ -1305,15 +1239,6 @@ function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWo
1305
1239
  const [historyTask, setHistoryTask] = (0, import_react3.useState)("all");
1306
1240
  const [historyStatus, setHistoryStatus] = (0, import_react3.useState)("all");
1307
1241
  const now = (0, import_react3.useMemo)(() => new Date(state.snapshot?.serverNow ?? Date.now()), [state.snapshot?.serverNow, state.refreshedAt]);
1308
- (0, import_react3.useEffect)(() => {
1309
- void runtime.refresh().catch(() => void 0);
1310
- const timer = window.setInterval(() => {
1311
- void runtime.refresh().catch(() => void 0);
1312
- }, POLL_INTERVAL_MS);
1313
- return () => {
1314
- window.clearInterval(timer);
1315
- };
1316
- }, [runtime]);
1317
1242
  const snapshot = state.snapshot;
1318
1243
  const workspaces = snapshot?.workspaces ?? [];
1319
1244
  const models = snapshot?.models ?? [];
@@ -1474,8 +1399,6 @@ function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWo
1474
1399
  skills: snapshot?.skills ?? [],
1475
1400
  permissions,
1476
1401
  defaultPermission,
1477
- onAddWorkspace: async (path) => runtime.addWorkspace(path).then((value) => value.id),
1478
- ...pickWorkspaceDirectory === void 0 ? {} : { pickWorkspaceDirectory },
1479
1402
  editing: editingId !== void 0,
1480
1403
  ...draft === void 0 ? {} : { draft },
1481
1404
  onClose: closeModal,
@@ -1637,7 +1560,6 @@ var en = {
1637
1560
  "tabs.runs": "Run history",
1638
1561
  "sort.created": "Newest first",
1639
1562
  "form.workspace": "Workspace",
1640
- "form.addWorkspace": "Add workspace...",
1641
1563
  "form.workspacePath": "Folder path",
1642
1564
  "form.workspacePathPlaceholder": "D:\\work\\project",
1643
1565
  "form.model": "Model",
@@ -1829,7 +1751,6 @@ var zh = {
1829
1751
  "tabs.runs": "\u6267\u884C\u8BB0\u5F55",
1830
1752
  "sort.created": "\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F",
1831
1753
  "form.workspace": "\u5DE5\u4F5C\u76EE\u5F55",
1832
- "form.addWorkspace": "\u6DFB\u52A0\u5DE5\u4F5C\u533A...",
1833
1754
  "form.workspacePath": "\u76EE\u5F55\u8DEF\u5F84",
1834
1755
  "form.workspacePathPlaceholder": "D:\\work\\project",
1835
1756
  "form.model": "\u6A21\u578B",
@@ -2486,15 +2407,6 @@ function NativeScheduleSessionList(props) {
2486
2407
  const [query, setQuery] = (0, import_react5.useState)("");
2487
2408
  const [sort, setSort] = (0, import_react5.useState)("time");
2488
2409
  const [groupMode, setGroupMode] = (0, import_react5.useState)("workspace");
2489
- (0, import_react5.useEffect)(() => {
2490
- void runtime.refresh().catch(() => void 0);
2491
- const timer = window.setInterval(() => {
2492
- void runtime.refresh().catch(() => void 0);
2493
- }, 15e3);
2494
- return () => {
2495
- window.clearInterval(timer);
2496
- };
2497
- }, [runtime]);
2498
2410
  const archived = (0, import_react5.useMemo)(() => new Set(archivedIds), [archivedIds]);
2499
2411
  const listedIds = useSessions ? useSessions((snap) => Array.isArray(snap?.ids) ? snap.ids : void 0) : void 0;
2500
2412
  const presentIds = (0, import_react5.useMemo)(() => {
@@ -2729,15 +2641,6 @@ function ScheduleRail({
2729
2641
  }) {
2730
2642
  const state = (0, import_react6.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
2731
2643
  const [folded, setFolded] = (0, import_react6.useState)({});
2732
- (0, import_react6.useEffect)(() => {
2733
- void runtime.refresh().catch(() => void 0);
2734
- const timer = window.setInterval(() => {
2735
- void runtime.refresh().catch(() => void 0);
2736
- }, 15e3);
2737
- return () => {
2738
- window.clearInterval(timer);
2739
- };
2740
- }, [runtime]);
2741
2644
  const groups = (0, import_react6.useMemo)(() => {
2742
2645
  const snapshot = state.snapshot;
2743
2646
  if (snapshot === void 0) return [];
@@ -3068,20 +2971,30 @@ var name = "dsh-automation-client";
3068
2971
  var inject = ["slots", "locale", "connection", "sessions"];
3069
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>';
3070
2973
  function installSettingsNavIcon(labels) {
3071
- const apply2 = () => {
3072
- const wanted = new Set(labels().map((item) => item.trim()).filter((item) => item !== ""));
3073
- const buttons = document.querySelectorAll("button");
3074
- for (const button of buttons) {
3075
- const text = (button.textContent ?? "").replace(/\s+/g, " ").trim();
3076
- if (!wanted.has(text)) continue;
3077
- const svg = button.querySelector("svg");
3078
- if (svg === null || svg.getAttribute("data-dsh-schedule-icon") === "1") continue;
3079
- svg.outerHTML = SETTINGS_CLOCK_SVG;
3080
- }
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);
3081
2985
  };
3082
- apply2();
3083
- const observer = new MutationObserver(apply2);
3084
- 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 });
3085
2998
  return () => {
3086
2999
  observer.disconnect();
3087
3000
  };
@@ -3102,8 +3015,7 @@ function apply(ctx) {
3102
3015
  label: () => t("tab"),
3103
3016
  icon: "schedule"
3104
3017
  }, function ScheduledTasksSettings(props) {
3105
- const workspacesApi = readOptionalWorkspaces(ctx);
3106
- return (0, import_react7.createElement)(AutomationView, { t, permissionT, modelT, 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 } });
3107
3019
  }));
3108
3020
  ctx.slots.inject("sidebar.schedule", () => ctx.slots.register({
3109
3021
  name: "sidebar.schedule",
@@ -3311,12 +3223,5 @@ function readSlotEntries(ctx, name2) {
3311
3223
  function slotHasEntries(ctx, name2) {
3312
3224
  return readSlotEntries(ctx, name2).length > 0;
3313
3225
  }
3314
- function readOptionalWorkspaces(ctx) {
3315
- try {
3316
- return ctx.workspaces;
3317
- } catch {
3318
- return void 0;
3319
- }
3320
- }
3321
3226
  return module.exports; } });
3322
3227
  //# sourceMappingURL=client.js.map