@michengai/dsh-automation 0.1.10 → 0.1.12

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.
@@ -13,7 +13,7 @@
13
13
  - 每次到期都创建全新 root Agent 和 Session,不继承来源对话。
14
14
  - 规则、修订号和运行历史持久化。
15
15
  - 同一条规则同时最多一个 active run;超时、重叠和 Host 中断都有明确终态。
16
- - 默认只读;可显式选择 `workspace-write`。禁止无人值守 `danger-full-access`。
16
+ - 权限预设与 Chat 保持一致,完整使用 Host 提供的列表与默认值;切换到 `danger-full-access` 时显示同款风险确认。
17
17
 
18
18
  ## 稳定边界
19
19
 
@@ -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);
@@ -415,11 +409,6 @@ function permissionLabel(option, t) {
415
409
  function shouldConfirmFullAccess(current, next) {
416
410
  return current !== next && next === "danger-full-access";
417
411
  }
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
412
 
424
413
  // src/client/menu.tsx
425
414
  var import_react = require("react");
@@ -647,9 +636,7 @@ function CreateModal({
647
636
  draft,
648
637
  editing,
649
638
  onClose,
650
- onSubmit,
651
- onAddWorkspace,
652
- pickWorkspaceDirectory
639
+ onSubmit
653
640
  }) {
654
641
  const [form, setForm] = (0, import_react2.useState)(() => ({ ...defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel, defaultPermission), ...draft }));
655
642
  const [validationError, setValidationError] = (0, import_react2.useState)();
@@ -672,8 +659,7 @@ function CreateModal({
672
659
  setConfirmingPermission(void 0);
673
660
  };
674
661
  const confirmFullAccess = () => {
675
- if (!fullAccessAcknowledged) return;
676
- if (confirmingPermission === void 0) return;
662
+ if (!fullAccessAcknowledged || confirmingPermission === void 0) return;
677
663
  update({ permission: confirmingPermission });
678
664
  cancelFullAccessConfirmation();
679
665
  };
@@ -819,30 +805,7 @@ function CreateModal({
819
805
  onClick: () => update({ workspaceId: item.id })
820
806
  },
821
807
  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
- ] })
808
+ ))
846
809
  ] }),
847
810
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuPanel, { ghost: true, up: true, label: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
848
811
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, { width: 14, height: 14 }),
@@ -1157,6 +1120,7 @@ function unwrapRpcResult(value) {
1157
1120
 
1158
1121
  // src/client/runtime.ts
1159
1122
  var CHANNEL = "/dsh-automation";
1123
+ var POLL_INTERVAL_MS = 15e3;
1160
1124
  function isTransportError(error) {
1161
1125
  const message = error instanceof Error ? error.message : String(error);
1162
1126
  return /failed to fetch|networkerror|load failed|network request failed/i.test(message);
@@ -1164,6 +1128,7 @@ function isTransportError(error) {
1164
1128
  function createAutomationRuntime(rpc) {
1165
1129
  let state = { phase: "idle" };
1166
1130
  let refreshPromise;
1131
+ let pollTimer;
1167
1132
  const listeners2 = /* @__PURE__ */ new Set();
1168
1133
  const publish = (next) => {
1169
1134
  state = next;
@@ -1173,8 +1138,20 @@ function createAutomationRuntime(rpc) {
1173
1138
  getSnapshot: () => state,
1174
1139
  subscribe: (listener) => {
1175
1140
  listeners2.add(listener);
1141
+ if (listeners2.size === 1) {
1142
+ queueMicrotask(() => {
1143
+ if (listeners2.size > 0) void refresh().catch(() => void 0);
1144
+ });
1145
+ pollTimer = setInterval(() => {
1146
+ void refresh().catch(() => void 0);
1147
+ }, POLL_INTERVAL_MS);
1148
+ }
1176
1149
  return () => {
1177
1150
  listeners2.delete(listener);
1151
+ if (listeners2.size === 0 && pollTimer !== void 0) {
1152
+ clearInterval(pollTimer);
1153
+ pollTimer = void 0;
1154
+ }
1178
1155
  };
1179
1156
  }
1180
1157
  };
@@ -1274,25 +1251,18 @@ function createAutomationRuntime(rpc) {
1274
1251
  return rest;
1275
1252
  })
1276
1253
  }));
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
1254
  }
1284
1255
  };
1285
1256
  }
1286
1257
 
1287
1258
  // src/client/AutomationView.tsx
1288
1259
  var import_jsx_runtime4 = require("react/jsx-runtime");
1289
- var POLL_INTERVAL_MS = 15e3;
1290
1260
  var EXAMPLES = [
1291
1261
  { name: "\u6BCF\u65E5\u56DE\u5F52\u68C0\u67E5", scheduleKind: "daily", time: "09:00", weekdays: [1, 2, 3, 4, 5] },
1292
1262
  { name: "\u6BCF\u5468\u4F9D\u8D56\u5DE1\u68C0", scheduleKind: "weekly", time: "10:00", weekdays: [1] },
1293
1263
  { name: "\u5DE5\u4F5C\u65E5\u65E9\u62A5", scheduleKind: "weekly", time: "08:00", weekdays: [1, 2, 3, 4, 5] }
1294
1264
  ];
1295
- function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWorkspaceDirectory }) {
1265
+ function AutomationView({ t, permissionT, modelT, runtime, closeSettings }) {
1296
1266
  const state = (0, import_react3.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
1297
1267
  const [tab, setTab] = (0, import_react3.useState)("mine");
1298
1268
  const [query, setQuery] = (0, import_react3.useState)("");
@@ -1305,15 +1275,6 @@ function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWo
1305
1275
  const [historyTask, setHistoryTask] = (0, import_react3.useState)("all");
1306
1276
  const [historyStatus, setHistoryStatus] = (0, import_react3.useState)("all");
1307
1277
  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
1278
  const snapshot = state.snapshot;
1318
1279
  const workspaces = snapshot?.workspaces ?? [];
1319
1280
  const models = snapshot?.models ?? [];
@@ -1474,8 +1435,6 @@ function AutomationView({ t, permissionT, modelT, runtime, closeSettings, pickWo
1474
1435
  skills: snapshot?.skills ?? [],
1475
1436
  permissions,
1476
1437
  defaultPermission,
1477
- onAddWorkspace: async (path) => runtime.addWorkspace(path).then((value) => value.id),
1478
- ...pickWorkspaceDirectory === void 0 ? {} : { pickWorkspaceDirectory },
1479
1438
  editing: editingId !== void 0,
1480
1439
  ...draft === void 0 ? {} : { draft },
1481
1440
  onClose: closeModal,
@@ -1637,7 +1596,6 @@ var en = {
1637
1596
  "tabs.runs": "Run history",
1638
1597
  "sort.created": "Newest first",
1639
1598
  "form.workspace": "Workspace",
1640
- "form.addWorkspace": "Add workspace...",
1641
1599
  "form.workspacePath": "Folder path",
1642
1600
  "form.workspacePathPlaceholder": "D:\\work\\project",
1643
1601
  "form.model": "Model",
@@ -1829,7 +1787,6 @@ var zh = {
1829
1787
  "tabs.runs": "\u6267\u884C\u8BB0\u5F55",
1830
1788
  "sort.created": "\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F",
1831
1789
  "form.workspace": "\u5DE5\u4F5C\u76EE\u5F55",
1832
- "form.addWorkspace": "\u6DFB\u52A0\u5DE5\u4F5C\u533A...",
1833
1790
  "form.workspacePath": "\u76EE\u5F55\u8DEF\u5F84",
1834
1791
  "form.workspacePathPlaceholder": "D:\\work\\project",
1835
1792
  "form.model": "\u6A21\u578B",
@@ -2486,15 +2443,6 @@ function NativeScheduleSessionList(props) {
2486
2443
  const [query, setQuery] = (0, import_react5.useState)("");
2487
2444
  const [sort, setSort] = (0, import_react5.useState)("time");
2488
2445
  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
2446
  const archived = (0, import_react5.useMemo)(() => new Set(archivedIds), [archivedIds]);
2499
2447
  const listedIds = useSessions ? useSessions((snap) => Array.isArray(snap?.ids) ? snap.ids : void 0) : void 0;
2500
2448
  const presentIds = (0, import_react5.useMemo)(() => {
@@ -2729,15 +2677,6 @@ function ScheduleRail({
2729
2677
  }) {
2730
2678
  const state = (0, import_react6.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
2731
2679
  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
2680
  const groups = (0, import_react6.useMemo)(() => {
2742
2681
  const snapshot = state.snapshot;
2743
2682
  if (snapshot === void 0) return [];
@@ -3068,20 +3007,30 @@ var name = "dsh-automation-client";
3068
3007
  var inject = ["slots", "locale", "connection", "sessions"];
3069
3008
  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
3009
  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
- }
3010
+ const wanted = new Set(labels().map((item) => item.trim()).filter((item) => item !== ""));
3011
+ const applyButton = (button) => {
3012
+ const text = (button.textContent ?? "").replace(/\s+/g, " ").trim();
3013
+ if (!wanted.has(text)) return;
3014
+ const svg = button.querySelector("svg");
3015
+ if (svg === null || svg.getAttribute("data-dsh-schedule-icon") === "1") return;
3016
+ svg.outerHTML = SETTINGS_CLOCK_SVG;
3017
+ };
3018
+ const scan = (root) => {
3019
+ if (root instanceof HTMLButtonElement) applyButton(root);
3020
+ for (const button of root.querySelectorAll?.("button") ?? []) applyButton(button);
3081
3021
  };
3082
- apply2();
3083
- const observer = new MutationObserver(apply2);
3084
- observer.observe(document.documentElement, { childList: true, subtree: true });
3022
+ scan(document);
3023
+ const observer = new MutationObserver((mutations) => {
3024
+ for (const mutation of mutations) {
3025
+ const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
3026
+ const owner = target?.closest("button");
3027
+ if (owner instanceof HTMLButtonElement) applyButton(owner);
3028
+ for (const node of mutation.addedNodes) {
3029
+ if (node instanceof Element) scan(node);
3030
+ }
3031
+ }
3032
+ });
3033
+ observer.observe(document.documentElement, { childList: true, characterData: true, subtree: true });
3085
3034
  return () => {
3086
3035
  observer.disconnect();
3087
3036
  };
@@ -3102,8 +3051,7 @@ function apply(ctx) {
3102
3051
  label: () => t("tab"),
3103
3052
  icon: "schedule"
3104
3053
  }, 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() } });
3054
+ return (0, import_react7.createElement)(AutomationView, { t, permissionT, modelT, runtime, ...props.close === void 0 ? {} : { closeSettings: props.close } });
3107
3055
  }));
3108
3056
  ctx.slots.inject("sidebar.schedule", () => ctx.slots.register({
3109
3057
  name: "sidebar.schedule",
@@ -3311,12 +3259,5 @@ function readSlotEntries(ctx, name2) {
3311
3259
  function slotHasEntries(ctx, name2) {
3312
3260
  return readSlotEntries(ctx, name2).length > 0;
3313
3261
  }
3314
- function readOptionalWorkspaces(ctx) {
3315
- try {
3316
- return ctx.workspaces;
3317
- } catch {
3318
- return void 0;
3319
- }
3320
- }
3321
3262
  return module.exports; } });
3322
3263
  //# sourceMappingURL=client.js.map