@michengai/dsh-automation 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,10 +4,19 @@
4
4
 
5
5
  The five most recent published versions are listed below. Git tags and GitHub Releases now mirror these entries; historical sections retain links to their original release commits.
6
6
 
7
+ ## 0.1.28 — 2026-09-04
8
+
9
+ - Compared normalized schedules semantically so property order no longer makes an unchanged overdue one-time schedule look edited.
10
+ - Formatted scheduled-session and execution-record titles in the task's IANA time zone. Host-local fallback formatting now follows the runtime time zone dynamically.
11
+ - Clarified that the unattended tool guard is layered on top of the selected permission preset, and ignored the repository-local pnpm store.
12
+
13
+ ## 0.1.27 — 2026-09-03
14
+
15
+ - Fixed the scheduled execution-record list staying empty after a run until the sidebar was switched several times. New runs now stay visible even when the Host session index has not caught up yet, the snapshot refreshes immediately when the open session is a scheduled run, and polling speeds up while a run is queued or running.
16
+
7
17
  ## 0.1.26 — 2026-09-03
8
18
 
9
- - Updated the DSH development baseline to `0.1.2-rc.1`.
10
- - Read Session events through `snapshotEvents()` on current Hosts while retaining the legacy `events` fallback for older Hosts.
19
+ - Added compatibility with DeepSeek Harness `0.1.2-rc.1`, keeping scheduled automations responsive to session events in current Hosts.
11
20
 
12
21
  ## 0.1.25 — 2026-09-03
13
22
 
@@ -4,10 +4,19 @@
4
4
 
5
5
  以下记录最近发布的五个版本。Git 标签与 GitHub Release 现已和这些条目同步;历史条目继续保留原始发布提交链接。
6
6
 
7
+ ## 0.1.28 — 2026-09-04
8
+
9
+ - 改用语义方式比较规范化计划,避免属性顺序不同导致未变更的过期单次计划被误判为已编辑。
10
+ - 定时会话与执行记录标题按任务的 IANA 时区显示;未指定时区时的宿主本地 fallback 会动态跟随运行时区。
11
+ - 明确无人值守工具守卫叠加于所选权限预设之上,并忽略仓库本地 pnpm 存储目录。
12
+
13
+ ## 0.1.27 — 2026-09-03
14
+
15
+ - 修复定时任务已经执行、但侧栏执行记录要多次切换后才出现的问题。宿主会话簿尚未收录时仍显示新记录,当前打开的若是定时会话会立即刷新快照,排队或执行中时加快轮询。
16
+
7
17
  ## 0.1.26 — 2026-09-03
8
18
 
9
- - 将 DSH 开发基线升级到 `0.1.2-rc.1`。
10
- - 当前 Host 使用 `snapshotEvents()` 读取 Session 事件,同时保留旧 Host 的 `events` 回退。
19
+ - 支持 DeepSeek Harness `0.1.2-rc.1`,确保定时自动化在新版 DSH 中继续响应会话事件。
11
20
 
12
21
  ## 0.1.25 — 2026-09-03
13
22
 
package/lib/client.js CHANGED
@@ -1373,7 +1373,11 @@ function unwrapRpcResult(value) {
1373
1373
 
1374
1374
  // src/client/runtime.ts
1375
1375
  var CHANNEL = "/dsh-automation";
1376
- var POLL_INTERVAL_MS = 15e3;
1376
+ var IDLE_POLL_INTERVAL_MS = 15e3;
1377
+ var ACTIVE_POLL_INTERVAL_MS = 2e3;
1378
+ function snapshotPollIntervalMs(runs) {
1379
+ return (runs ?? []).some((run) => run.status === "running" || run.status === "queued") ? ACTIVE_POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS;
1380
+ }
1377
1381
  function isTransportError(error) {
1378
1382
  const message = error instanceof Error ? error.message : String(error);
1379
1383
  return /failed to fetch|networkerror|load failed|network request failed/i.test(message);
@@ -1383,8 +1387,20 @@ function createAutomationRuntime(rpc) {
1383
1387
  let refreshPromise;
1384
1388
  let pollTimer;
1385
1389
  const listeners2 = /* @__PURE__ */ new Set();
1390
+ const armPoll = () => {
1391
+ if (pollTimer !== void 0) clearInterval(pollTimer);
1392
+ if (listeners2.size === 0) {
1393
+ pollTimer = void 0;
1394
+ return;
1395
+ }
1396
+ pollTimer = setInterval(() => {
1397
+ void refresh().catch(() => void 0);
1398
+ }, snapshotPollIntervalMs(state.snapshot?.runs));
1399
+ };
1386
1400
  const publish = (next) => {
1401
+ const previousInterval = snapshotPollIntervalMs(state.snapshot?.runs);
1387
1402
  state = next;
1403
+ if (listeners2.size > 0 && previousInterval !== snapshotPollIntervalMs(state.snapshot?.runs)) armPoll();
1388
1404
  for (const listener of [...listeners2]) listener();
1389
1405
  };
1390
1406
  const source = {
@@ -1395,9 +1411,7 @@ function createAutomationRuntime(rpc) {
1395
1411
  queueMicrotask(() => {
1396
1412
  if (listeners2.size > 0) void refresh().catch(() => void 0);
1397
1413
  });
1398
- pollTimer = setInterval(() => {
1399
- void refresh().catch(() => void 0);
1400
- }, POLL_INTERVAL_MS);
1414
+ armPoll();
1401
1415
  }
1402
1416
  return () => {
1403
1417
  listeners2.delete(listener);
@@ -2591,12 +2605,39 @@ async function archiveScheduledGroup(sessionIds, archiveSession) {
2591
2605
  }
2592
2606
 
2593
2607
  // src/run-title.ts
2594
- function formatRunStamp(iso) {
2608
+ var RUN_STAMP_OPTIONS = {
2609
+ year: "numeric",
2610
+ month: "2-digit",
2611
+ day: "2-digit",
2612
+ hour: "2-digit",
2613
+ minute: "2-digit",
2614
+ hourCycle: "h23"
2615
+ };
2616
+ var zonedRunStampFormatters = /* @__PURE__ */ new Map();
2617
+ function runStampFormatter(timeZone) {
2618
+ const cached = zonedRunStampFormatters.get(timeZone);
2619
+ if (cached !== void 0) return cached;
2620
+ const formatter = new Intl.DateTimeFormat("en-CA", { ...RUN_STAMP_OPTIONS, timeZone });
2621
+ zonedRunStampFormatters.set(timeZone, formatter);
2622
+ return formatter;
2623
+ }
2624
+ function formatRunStamp(iso, timeZone) {
2595
2625
  const value = new Date(iso);
2596
2626
  if (Number.isNaN(value.getTime())) return iso;
2597
- const date = `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
2598
- const time = `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
2599
- return `${date} ${time}`;
2627
+ if (timeZone === void 0) {
2628
+ const date = `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
2629
+ const time = `${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`;
2630
+ return `${date} ${time}`;
2631
+ }
2632
+ try {
2633
+ const parts = runStampFormatter(timeZone).formatToParts(value);
2634
+ const part = (type) => parts.find((item) => item.type === type)?.value;
2635
+ const [year, month, day, hour, minute] = ["year", "month", "day", "hour", "minute"].map((type) => part(type));
2636
+ if ([year, month, day, hour, minute].some((item) => item === void 0)) return iso;
2637
+ return `${year}-${month}-${day} ${hour}:${minute}`;
2638
+ } catch {
2639
+ return iso;
2640
+ }
2600
2641
  }
2601
2642
 
2602
2643
  // src/client/schedule-rail-model.ts
@@ -2604,7 +2645,11 @@ var AUTOMATION_SESSION_PREFIX = "dsh-automation-session-";
2604
2645
  var NATIVE_SIDEBAR_TAB_KEY = "dsh-automation.sidebar-tab";
2605
2646
  function groupScheduledSessions(automations, runs) {
2606
2647
  const nameById = /* @__PURE__ */ new Map();
2607
- for (const item of automations) nameById.set(item.id, item.name);
2648
+ const timeZoneById = /* @__PURE__ */ new Map();
2649
+ for (const item of automations) {
2650
+ nameById.set(item.id, item.name);
2651
+ if (item.timeZone !== void 0) timeZoneById.set(item.id, item.timeZone);
2652
+ }
2608
2653
  for (const run of runs) {
2609
2654
  const stored = (run.automationName || "").trim();
2610
2655
  if (stored !== "" && stored !== run.automationId && !nameById.has(run.automationId)) {
@@ -2631,7 +2676,7 @@ function groupScheduledSessions(automations, runs) {
2631
2676
  sessions: runs.filter((run) => run.automationId === id && run.sessionId !== void 0 && run.sessionId !== "").slice().sort((left, right) => Date.parse(right.startedAt ?? right.scheduledFor) - Date.parse(left.startedAt ?? left.scheduledFor)).map((run) => ({
2632
2677
  id: run.sessionId,
2633
2678
  running: run.status === "running" || run.status === "queued",
2634
- label: formatRunStamp(run.startedAt ?? run.scheduledFor) + " - " + name2
2679
+ label: formatRunStamp(run.startedAt ?? run.scheduledFor, timeZoneById.get(id)) + " - " + name2
2635
2680
  }))
2636
2681
  };
2637
2682
  }).filter((group) => group.sessions.length > 0);
@@ -2656,11 +2701,15 @@ function deriveTaskOverviewRows(automations, runs) {
2656
2701
  };
2657
2702
  });
2658
2703
  }
2659
- function keepScheduledSessionLink(sessionId, archived, presentIds) {
2704
+ function keepScheduledSessionLink(sessionId, archived, _presentIds) {
2660
2705
  if (sessionId === void 0 || sessionId === "") return false;
2661
2706
  if (archived.has(sessionId)) return false;
2662
- if (presentIds === void 0) return true;
2663
- return presentIds.has(sessionId);
2707
+ return true;
2708
+ }
2709
+ function scheduledSessionNeedsSnapshotRefresh(sessionId, runs) {
2710
+ if (sessionId === void 0 || sessionId === null || sessionId === "") return false;
2711
+ if (!sessionId.startsWith(AUTOMATION_SESSION_PREFIX)) return false;
2712
+ return !(runs ?? []).some((run) => run.sessionId === sessionId);
2664
2713
  }
2665
2714
  function collectScheduledSessionIds(runs) {
2666
2715
  const ids = /* @__PURE__ */ new Set();
@@ -3104,6 +3153,7 @@ function NativeScheduleSessionList(props) {
3104
3153
  const [sort, setSort] = (0, import_react9.useState)("time");
3105
3154
  const [groupMode, setGroupMode] = (0, import_react9.useState)("workspace");
3106
3155
  const [view, setView] = (0, import_react9.useState)("runs");
3156
+ const snapshotRefreshFor = (0, import_react9.useRef)(null);
3107
3157
  const archived = (0, import_react9.useMemo)(() => new Set(archivedIds), [archivedIds]);
3108
3158
  const listedIds = useSessions ? useSessions((snap) => Array.isArray(snap.ids) ? snap.ids : void 0) : void 0;
3109
3159
  const presentIds = (0, import_react9.useMemo)(() => {
@@ -3114,19 +3164,29 @@ function NativeScheduleSessionList(props) {
3114
3164
  const groups = (0, import_react9.useMemo)(() => {
3115
3165
  const snapshot = state.snapshot;
3116
3166
  if (snapshot === void 0) return [];
3167
+ const timeZoneById = new Map(snapshot.automations.map((item) => [item.id, item.timeZone]));
3117
3168
  return groupScheduledSessions(snapshot.automations, snapshot.runs).map((group) => ({
3118
3169
  ...group,
3119
3170
  sessions: group.sessions.filter((session) => keepScheduledSessionLink(session.id, archived, presentIds)).map((session) => {
3120
3171
  const run = snapshot.runs.find((item) => item.sessionId === session.id);
3121
3172
  return {
3122
3173
  ...session,
3123
- title: formatRunStamp(run && (run.startedAt || run.scheduledFor) || ""),
3174
+ title: formatRunStamp(run && (run.startedAt || run.scheduledFor) || "", timeZoneById.get(group.id)),
3124
3175
  updatedAt: run && (run.startedAt || run.scheduledFor) || "",
3125
3176
  running: session.running || sessionById[session.id]?.running === true
3126
3177
  };
3127
3178
  })
3128
3179
  })).filter((group) => group.sessions.length > 0);
3129
3180
  }, [archived, presentIds, sessionById, state.snapshot]);
3181
+ (0, import_react9.useEffect)(() => {
3182
+ if (!scheduledSessionNeedsSnapshotRefresh(selectedId, state.snapshot?.runs)) {
3183
+ snapshotRefreshFor.current = null;
3184
+ return;
3185
+ }
3186
+ if (snapshotRefreshFor.current === selectedId) return;
3187
+ snapshotRefreshFor.current = selectedId;
3188
+ void runtime.refresh().catch(() => void 0);
3189
+ }, [runtime, selectedId, state.snapshot?.runs]);
3130
3190
  const visibleGroups = (0, import_react9.useMemo)(() => applyWorkspaceBrowserQuery(groups.map((group) => ({ ...group, name: group.name })), query, sort, groupMode), [groups, query, sort, groupMode]);
3131
3191
  (0, import_react9.useEffect)(() => {
3132
3192
  if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
@@ -3581,6 +3641,16 @@ function NativeScheduleShell({
3581
3641
  const currentId = useSessions?.((state) => state?.current ?? null);
3582
3642
  const automationState = (0, import_react10.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
3583
3643
  const scheduledIds = (0, import_react10.useMemo)(() => collectScheduledSessionIds(automationState.snapshot?.runs), [automationState.snapshot]);
3644
+ const snapshotRefreshFor = (0, import_react10.useRef)(null);
3645
+ (0, import_react10.useEffect)(() => {
3646
+ if (!scheduledSessionNeedsSnapshotRefresh(currentId, automationState.snapshot?.runs)) {
3647
+ snapshotRefreshFor.current = null;
3648
+ return;
3649
+ }
3650
+ if (snapshotRefreshFor.current === currentId) return;
3651
+ snapshotRefreshFor.current = currentId ?? null;
3652
+ void runtime.refresh().catch(() => void 0);
3653
+ }, [automationState.snapshot?.runs, currentId, runtime]);
3584
3654
  const useFilteredSessions = (0, import_react10.useCallback)((selector, eq) => {
3585
3655
  if (useSessions === void 0) return selector({ ids: [], byId: {}, current: null });
3586
3656
  return useSessions((state) => selector(filterTaskSessionState(state, scheduledIds)), eq);