@michengai/dsh-automation 0.1.28 → 0.1.30

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,6 +4,17 @@
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.30 — 2026-09-04
8
+
9
+ - Kept standalone Web and Codex UI scheduled-task lists current without requiring a mounted plugin page, and refreshed the Host session index when Automation runs reference sessions that have not appeared there yet.
10
+ - Added bounded refresh-cycle backoff for delayed Host session creation, dynamically reading the current Host session store while suppressing repeated refresh warnings.
11
+ - Refined the native and Codex UI schedule sidebars with aligned folder and card gutters, more compact task cards, and accessible pause/resume switches in place of status badges.
12
+
13
+ ## 0.1.29 — 2026-09-04
14
+
15
+ - Refreshed scheduled execution records immediately when the DSH page regains focus or becomes visible, avoiding stale history when Chromium throttles background-tab timers.
16
+ - Removed page-resume listeners after the final client subscriber disconnects.
17
+
7
18
  ## 0.1.28 — 2026-09-04
8
19
 
9
20
  - Compared normalized schedules semantically so property order no longer makes an unchanged overdue one-time schedule look edited.
@@ -4,6 +4,17 @@
4
4
 
5
5
  以下记录最近发布的五个版本。Git 标签与 GitHub Release 现已和这些条目同步;历史条目继续保留原始发布提交链接。
6
6
 
7
+ ## 0.1.30 — 2026-09-04
8
+
9
+ - 独立 Web 与 Codex UI 的定时任务列表不再依赖插件页面挂载即可持续更新;Automation 运行记录引用的 Session 尚未进入宿主索引时,会主动刷新 Host 会话列表。
10
+ - 针对 Host Session 延迟创建增加有上限的完整刷新周期退避,并动态读取当前 Host Session Store,同时抑制重复刷新告警。
11
+ - 统一原生与 Codex UI 定时侧栏的文件夹和卡片边距,压缩任务卡片高度,并以可访问的暂停/恢复开关替代状态徽标。
12
+
13
+ ## 0.1.29 — 2026-09-04
14
+
15
+ - DSH 页面重新获得焦点或恢复可见时立即刷新定时任务执行记录,避免 Chromium 节流后台标签页计时器后侧栏历史停留在旧状态。
16
+ - 最后一个客户端订阅释放后同步移除页面恢复监听,避免遗留无效监听器。
17
+
7
18
  ## 0.1.28 — 2026-09-04
8
19
 
9
20
  - 改用语义方式比较规范化计划,避免属性顺序不同导致未变更的过期单次计划被误判为已编辑。
package/lib/client.js CHANGED
@@ -341,9 +341,6 @@ function RefreshIcon(props) {
341
341
  function PlayIcon(props) {
342
342
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: 16, height: 16, fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.6 3.4 11.7 8l-7.1 4.6V3.4Z", fill: "currentColor", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round" }) });
343
343
  }
344
- function PauseIcon(props) {
345
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconFrame, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 7v10M15 7v10" }) });
346
- }
347
344
  function TrashIcon(props) {
348
345
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FillIcon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M14.478 4.841 14.214 10.115c-.104 2.072-.147 2.896-.827 3.846a3.53 3.53 0 0 1-1.044.993c-.519.333-1.101.478-1.784.546-.671.067-1.509.066-2.559.066s-1.887.001-2.558-.066c-.683-.068-1.266-.213-1.784-.546a3.53 3.53 0 0 1-1.044-.993c-.681-.95-.724-1.774-.828-3.846L1.522 4.841l1.368-.068.263 5.273c.109 2.176.171 2.556.573 3.117a2.16 2.16 0 0 0 .673.64c.263.169.603.277 1.179.334.587.059 1.345.06 2.422.06s1.834-.001 2.422-.06c.575-.057.916-.165 1.179-.335.262-.168.49-.386.672-.64.402-.56.464-.94.573-3.116l.263-5.273 1.369.068ZM5.43 6.228h1.37v5.163H5.43V6.228Zm3.77 0h1.37v5.163H9.2V6.228ZM8.536.434c.644 0 1.116-.007 1.56.137.14.045.276.101.406.168.416.212.745.552 1.2 1.007l.796.795h2.876v1.37H.626V2.541h2.876l.796-.795c.456-.455.784-.795 1.2-1.007.13-.067.266-.123.405-.168C6.348.427 6.82.434 7.464.434h1.072Zm-1.072 1.37c-.732 0-.948.008-1.138.07a2.2 2.2 0 0 0-.206.085c-.156.08-.296.204-.678.583h5.117c-.382-.379-.522-.503-.679-.583a2.2 2.2 0 0 0-.205-.085c-.191-.062-.406-.07-1.138-.07H7.464Z" }) });
349
346
  }
@@ -359,9 +356,6 @@ function CalendarIcon(props) {
359
356
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3.8v3.4M16 3.8v3.4M4 9.5h16" })
360
357
  ] });
361
358
  }
362
- function CheckIcon(props) {
363
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconFrame, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m5.5 12.5 4 4 9-9" }) });
364
- }
365
359
  function MoreIcon(props) {
366
360
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(IconFrame, { ...props, children: [
367
361
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "12", r: "1.2", fill: "currentColor" }),
@@ -1375,6 +1369,8 @@ function unwrapRpcResult(value) {
1375
1369
  var CHANNEL = "/dsh-automation";
1376
1370
  var IDLE_POLL_INTERVAL_MS = 15e3;
1377
1371
  var ACTIVE_POLL_INTERVAL_MS = 2e3;
1372
+ var HOST_SESSION_RETRY_REFRESH_GAPS = [1, 2, 4];
1373
+ var HOST_SESSION_MAX_ATTEMPTS = HOST_SESSION_RETRY_REFRESH_GAPS.length + 1;
1378
1374
  function snapshotPollIntervalMs(runs) {
1379
1375
  return (runs ?? []).some((run) => run.status === "running" || run.status === "queued") ? ACTIVE_POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS;
1380
1376
  }
@@ -1382,10 +1378,92 @@ function isTransportError(error) {
1382
1378
  const message = error instanceof Error ? error.message : String(error);
1383
1379
  return /failed to fetch|networkerror|load failed|network request failed/i.test(message);
1384
1380
  }
1381
+ function installAutomationSessionSync(runtime, getSessions) {
1382
+ let stopped = false;
1383
+ let completedRefreshes = 0;
1384
+ let automationRefreshInProgress = runtime.source.getSnapshot().phase === "loading";
1385
+ let trackedSessions;
1386
+ let trackedMissingKey;
1387
+ let attempts = 0;
1388
+ let nextAttemptRefresh = 0;
1389
+ let warnedMissingKey;
1390
+ let hostRefreshPromise;
1391
+ let reconcileAfterRefresh = false;
1392
+ const resetAttempts = (missingKey) => {
1393
+ trackedMissingKey = missingKey;
1394
+ attempts = 0;
1395
+ nextAttemptRefresh = completedRefreshes;
1396
+ warnedMissingKey = void 0;
1397
+ };
1398
+ const missingSessionKey = (sessions) => {
1399
+ const snapshot = runtime.source.getSnapshot().snapshot;
1400
+ const hostSnapshot = sessions?.list?.getSnapshot();
1401
+ if (snapshot === void 0 || hostSnapshot === void 0 || sessions?.refresh === void 0) return void 0;
1402
+ const present = /* @__PURE__ */ new Set([
1403
+ ...hostSnapshot.ids ?? [],
1404
+ ...Object.keys(hostSnapshot.byId ?? {})
1405
+ ]);
1406
+ return [...new Set(snapshot.runs.map((run) => run.sessionId).filter((sessionId) => sessionId !== void 0 && sessionId !== ""))].filter((sessionId) => !present.has(sessionId)).sort().join("\0");
1407
+ };
1408
+ const reconcile = () => {
1409
+ if (stopped) return;
1410
+ const sessions = getSessions();
1411
+ if (sessions !== trackedSessions) {
1412
+ trackedSessions = sessions;
1413
+ resetAttempts();
1414
+ }
1415
+ const missingKey = missingSessionKey(sessions);
1416
+ if (missingKey === void 0) return;
1417
+ if (missingKey === "") {
1418
+ resetAttempts();
1419
+ return;
1420
+ }
1421
+ if (missingKey !== trackedMissingKey) resetAttempts(missingKey);
1422
+ if (attempts >= HOST_SESSION_MAX_ATTEMPTS || completedRefreshes < nextAttemptRefresh) return;
1423
+ if (hostRefreshPromise !== void 0) {
1424
+ reconcileAfterRefresh = true;
1425
+ return;
1426
+ }
1427
+ const attemptIndex = attempts;
1428
+ attempts += 1;
1429
+ const retryGap = HOST_SESSION_RETRY_REFRESH_GAPS[attemptIndex];
1430
+ nextAttemptRefresh = retryGap === void 0 ? Number.POSITIVE_INFINITY : completedRefreshes + retryGap;
1431
+ hostRefreshPromise = Promise.resolve().then(async () => {
1432
+ await sessions.refresh();
1433
+ }).catch((error) => {
1434
+ if (!stopped && warnedMissingKey !== missingKey) {
1435
+ warnedMissingKey = missingKey;
1436
+ console.warn("[dsh-automation] \u5237\u65B0 Host \u4F1A\u8BDD\u5217\u8868\u5931\u8D25", error);
1437
+ }
1438
+ }).finally(() => {
1439
+ hostRefreshPromise = void 0;
1440
+ if (stopped || !reconcileAfterRefresh) return;
1441
+ reconcileAfterRefresh = false;
1442
+ reconcile();
1443
+ });
1444
+ };
1445
+ const unsubscribe = runtime.source.subscribe(() => {
1446
+ const phase = runtime.source.getSnapshot().phase;
1447
+ if (phase === "loading") {
1448
+ automationRefreshInProgress = true;
1449
+ } else if (automationRefreshInProgress) {
1450
+ automationRefreshInProgress = false;
1451
+ completedRefreshes += 1;
1452
+ }
1453
+ reconcile();
1454
+ });
1455
+ reconcile();
1456
+ return () => {
1457
+ stopped = true;
1458
+ reconcileAfterRefresh = false;
1459
+ unsubscribe();
1460
+ };
1461
+ }
1385
1462
  function createAutomationRuntime(rpc) {
1386
1463
  let state = { phase: "idle" };
1387
1464
  let refreshPromise;
1388
1465
  let pollTimer;
1466
+ let removePageResumeListeners = () => void 0;
1389
1467
  const listeners2 = /* @__PURE__ */ new Set();
1390
1468
  const armPoll = () => {
1391
1469
  if (pollTimer !== void 0) clearInterval(pollTimer);
@@ -1397,6 +1475,23 @@ function createAutomationRuntime(rpc) {
1397
1475
  void refresh().catch(() => void 0);
1398
1476
  }, snapshotPollIntervalMs(state.snapshot?.runs));
1399
1477
  };
1478
+ const armPageResumeListeners = () => {
1479
+ removePageResumeListeners();
1480
+ if (listeners2.size === 0) return;
1481
+ const refreshOnResume = () => {
1482
+ void refresh().catch(() => void 0);
1483
+ };
1484
+ const refreshOnVisible = () => {
1485
+ if (document.visibilityState === "visible") refreshOnResume();
1486
+ };
1487
+ if (typeof window !== "undefined") window.addEventListener("focus", refreshOnResume);
1488
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", refreshOnVisible);
1489
+ removePageResumeListeners = () => {
1490
+ if (typeof window !== "undefined") window.removeEventListener("focus", refreshOnResume);
1491
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", refreshOnVisible);
1492
+ removePageResumeListeners = () => void 0;
1493
+ };
1494
+ };
1400
1495
  const publish = (next) => {
1401
1496
  const previousInterval = snapshotPollIntervalMs(state.snapshot?.runs);
1402
1497
  state = next;
@@ -1412,6 +1507,7 @@ function createAutomationRuntime(rpc) {
1412
1507
  if (listeners2.size > 0) void refresh().catch(() => void 0);
1413
1508
  });
1414
1509
  armPoll();
1510
+ armPageResumeListeners();
1415
1511
  }
1416
1512
  return () => {
1417
1513
  listeners2.delete(listener);
@@ -1419,6 +1515,7 @@ function createAutomationRuntime(rpc) {
1419
1515
  clearInterval(pollTimer);
1420
1516
  pollTimer = void 0;
1421
1517
  }
1518
+ if (listeners2.size === 0) removePageResumeListeners();
1422
1519
  };
1423
1520
  }
1424
1521
  };
@@ -2681,6 +2778,9 @@ function groupScheduledSessions(automations, runs) {
2681
2778
  };
2682
2779
  }).filter((group) => group.sessions.length > 0);
2683
2780
  }
2781
+ function automationToggleMutation(status) {
2782
+ return status === "active" ? "pause" : "resume";
2783
+ }
2684
2784
  function deriveTaskOverviewRows(automations, runs) {
2685
2785
  const lastByAutomation = /* @__PURE__ */ new Map();
2686
2786
  for (const run of runs) {
@@ -2958,10 +3058,12 @@ function ScheduleOverview({
2958
3058
  openSession,
2959
3059
  serverNow,
2960
3060
  archived,
2961
- presentIds
3061
+ presentIds,
3062
+ onToggleAutomation
2962
3063
  }) {
2963
3064
  const [sortKey, setSortKey] = (0, import_react7.useState)(() => readSortDefault(SORT_STORAGE2, OVERVIEW_SORT_DEFAULT_KEY)?.key ?? "planned");
2964
3065
  const [sortDirection, setSortDirection] = (0, import_react7.useState)(() => readSortDefault(SORT_STORAGE2, OVERVIEW_SORT_DEFAULT_KEY)?.direction ?? "asc");
3066
+ const [busyIds, setBusyIds] = (0, import_react7.useState)(() => /* @__PURE__ */ new Set());
2965
3067
  const now = (0, import_react7.useMemo)(() => new Date(serverNow ?? Date.now()), [serverNow]);
2966
3068
  const rows = (0, import_react7.useMemo)(() => {
2967
3069
  const sorted = sortAutomations(automations, sortKey, sortDirection);
@@ -2974,6 +3076,19 @@ function ScheduleOverview({
2974
3076
  });
2975
3077
  }, [archived, automations, presentIds, runs, sortDirection, sortKey]);
2976
3078
  const scheduleSummaries = (0, import_react7.useMemo)(() => new Map(automations.map((item) => [item.id, formatSchedule(item.schedule, t)])), [automations, t]);
3079
+ const toggleAutomation = (automationId, mutation) => {
3080
+ if (onToggleAutomation === void 0 || busyIds.has(automationId)) return;
3081
+ setBusyIds((current) => new Set(current).add(automationId));
3082
+ void Promise.resolve(onToggleAutomation(automationId, mutation)).catch((error) => {
3083
+ console.warn("[dsh-automation] \u5207\u6362\u4EFB\u52A1\u72B6\u6001\u5931\u8D25", error);
3084
+ }).finally(() => {
3085
+ setBusyIds((current) => {
3086
+ const next = new Set(current);
3087
+ next.delete(automationId);
3088
+ return next;
3089
+ });
3090
+ });
3091
+ };
2977
3092
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-st-overview", children: [
2978
3093
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-st-overview-head", children: [
2979
3094
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-st-overview-title", children: [
@@ -2998,7 +3113,19 @@ function ScheduleOverview({
2998
3113
  }
2999
3114
  )
3000
3115
  ] }),
3001
- rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-st-rail-empty", children: t("overview.empty") }) : rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(OverviewRow, { t, row, now, scheduleSummary: scheduleSummaries.get(row.id) ?? "", ...openSession === void 0 ? {} : { openSession } }, row.id))
3116
+ rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-st-rail-empty", children: t("overview.empty") }) : rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3117
+ OverviewRow,
3118
+ {
3119
+ t,
3120
+ row,
3121
+ now,
3122
+ scheduleSummary: scheduleSummaries.get(row.id) ?? "",
3123
+ toggleDisabled: onToggleAutomation === void 0 || busyIds.has(row.id),
3124
+ onToggleAutomation: toggleAutomation,
3125
+ ...openSession === void 0 ? {} : { openSession }
3126
+ },
3127
+ row.id
3128
+ ))
3002
3129
  ] });
3003
3130
  }
3004
3131
  function OverviewRow({
@@ -3006,41 +3133,57 @@ function OverviewRow({
3006
3133
  row,
3007
3134
  now,
3008
3135
  scheduleSummary,
3009
- openSession
3136
+ openSession,
3137
+ toggleDisabled,
3138
+ onToggleAutomation
3010
3139
  }) {
3011
3140
  const paused = row.status !== "active";
3012
3141
  const nextRun = row.nextRunAt === void 0 ? t("stats.noneScheduled") : formatWithin(row.nextRunAt, now, t);
3013
- const status = paused ? t("status.paused") : t("status.active");
3014
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3015
- "button",
3016
- {
3017
- type: "button",
3018
- className: `dsh-st-overview-row${paused ? " is-paused" : ""}`,
3019
- disabled: row.lastSessionId === void 0,
3020
- title: paused ? t("status.paused") : t("status.active"),
3021
- onClick: () => {
3022
- if (row.lastSessionId !== void 0) openSession?.(row.lastSessionId);
3023
- },
3024
- children: [
3025
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-copy", children: [
3026
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-status", children: [
3027
- paused ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PauseIcon, { width: 14, height: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckIcon, { width: 14, height: 14 }),
3028
- status
3142
+ const toggleLabel = paused ? t("card.resume") : t("card.pause");
3143
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `dsh-st-overview-row${paused ? " is-paused" : ""}`, children: [
3144
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3145
+ "button",
3146
+ {
3147
+ type: "button",
3148
+ className: "dsh-st-overview-open",
3149
+ disabled: row.lastSessionId === void 0,
3150
+ onClick: () => {
3151
+ if (row.lastSessionId !== void 0) openSession?.(row.lastSessionId);
3152
+ },
3153
+ children: [
3154
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-copy", children: [
3155
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-st-overview-name", children: row.name }),
3156
+ scheduleSummary !== "" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-schedule", children: [
3157
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CalendarIcon, { width: 14, height: 14 }),
3158
+ scheduleSummary
3159
+ ] })
3029
3160
  ] }),
3030
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-st-overview-name", children: row.name }),
3031
- scheduleSummary !== "" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-schedule", children: [
3032
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CalendarIcon, { width: 14, height: 14 }),
3033
- scheduleSummary
3034
- ] })
3035
- ] }),
3036
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-next", children: [
3037
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: t("stats.next") }),
3038
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: nextRun })
3039
- ] }),
3040
- row.lastSessionId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ChevronIcon, { width: 12, height: 12, className: "dsh-st-overview-chevron" })
3041
- ]
3042
- }
3043
- );
3161
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dsh-st-overview-next", children: [
3162
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: t("stats.next") }),
3163
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: nextRun })
3164
+ ] }),
3165
+ row.lastSessionId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ChevronIcon, { width: 12, height: 12, className: "dsh-st-overview-chevron" })
3166
+ ]
3167
+ }
3168
+ ),
3169
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "dsh-st-overview-toggle", title: toggleLabel, children: [
3170
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3171
+ "input",
3172
+ {
3173
+ type: "checkbox",
3174
+ role: "switch",
3175
+ checked: !paused,
3176
+ "aria-checked": !paused,
3177
+ "aria-label": toggleLabel,
3178
+ disabled: toggleDisabled,
3179
+ onChange: () => {
3180
+ onToggleAutomation?.(row.id, automationToggleMutation(row.status));
3181
+ }
3182
+ }
3183
+ ),
3184
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true" })
3185
+ ] })
3186
+ ] });
3044
3187
  }
3045
3188
 
3046
3189
  // src/client/workspace-toolbar.tsx
@@ -3222,6 +3365,7 @@ function NativeScheduleSessionList(props) {
3222
3365
  t,
3223
3366
  automations: state.snapshot.automations,
3224
3367
  runs: state.snapshot.runs,
3368
+ onToggleAutomation: (automationId, mutation) => runtime.mutateAutomation(automationId, mutation),
3225
3369
  ...openSession === void 0 ? {} : { openSession },
3226
3370
  ...state.snapshot.serverNow === void 0 ? {} : { serverNow: state.snapshot.serverNow },
3227
3371
  archived,
@@ -3564,6 +3708,7 @@ function ScheduleRail({
3564
3708
  t,
3565
3709
  automations: snapshot.automations,
3566
3710
  runs: snapshot.runs,
3711
+ onToggleAutomation: (automationId, mutation) => runtime.mutateAutomation(automationId, mutation),
3567
3712
  ...openSession === void 0 ? {} : { openSession },
3568
3713
  ...snapshot.serverNow === void 0 ? {} : { serverNow: snapshot.serverNow }
3569
3714
  }
@@ -3904,19 +4049,26 @@ var CSS_TEXT = `
3904
4049
  .dsh-st-overview-title strong{font-size:14px;font-weight:400;line-height:20px}
3905
4050
  .dsh-st-overview-title span{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.1));color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:11px;line-height:18px}
3906
4051
  .dsh-st-overview-sort{display:flex;align-items:center}
3907
- .dsh-st-overview-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto 12px;align-items:center;gap:8px;width:100%;min-height:88px;margin:0 0 8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.1));border-radius:9px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.035));color:var(--dsw-alias-label-primary,inherit);text-align:left;cursor:pointer;box-shadow:inset 0 1px rgba(255,255,255,.025);transition:background .14s ease,border-color .14s ease}
3908
- .dsh-st-overview-row:hover:not(:disabled){border-color:var(--dsw-alias-border-inverted,rgba(255,255,255,.18));background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.06))}
3909
- .dsh-st-overview-row:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c8dff);outline-offset:2px}
3910
- .dsh-st-overview-row:disabled{cursor:default}
3911
- .dsh-st-overview-copy{display:flex;flex:1;min-width:0;flex-direction:column;gap:4px}
3912
- .dsh-st-overview-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;font-weight:580}
3913
- .dsh-st-overview-status{display:inline-flex;align-self:flex-start;align-items:center;gap:4px;padding:2px 6px;border-radius:999px;background:rgba(50,205,143,.12);color:#45d483;font-size:11px;line-height:16px}
3914
- .dsh-st-overview-row.is-paused .dsh-st-overview-status{background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.08));color:var(--dsw-alias-label-secondary,#aeb3bd)}
3915
- .dsh-st-overview-schedule{display:inline-flex;align-items:center;gap:5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:12px;line-height:16px}
3916
- .dsh-st-overview-next{display:flex;flex:none;min-width:50px;flex-direction:column;align-items:flex-end;gap:2px;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:10px;line-height:14px}
4052
+ .dsh-st-overview-row{position:relative;width:100%;min-height:56px;margin:0 0 8px;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.1));border-radius:9px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.035));color:var(--dsw-alias-label-primary,inherit);box-shadow:inset 0 1px rgba(255,255,255,.025);transition:background .14s ease,border-color .14s ease;overflow:hidden}
4053
+ .dsh-st-overview-row:hover{border-color:var(--dsw-alias-border-inverted,rgba(255,255,255,.18));background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.06))}
4054
+ .dsh-st-overview-open{display:grid;grid-template-columns:minmax(0,1fr) auto 12px;grid-template-rows:18px 18px;align-items:center;gap:4px 8px;width:100%;min-height:56px;padding:8px 9px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}
4055
+ .dsh-st-overview-open:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4c8dff);outline-offset:-2px}
4056
+ .dsh-st-overview-open:disabled{cursor:default}
4057
+ .dsh-st-overview-copy{display:contents}
4058
+ .dsh-st-overview-name{box-sizing:border-box;grid-column:1/4;grid-row:1;min-width:0;padding-right:44px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:18px;font-weight:580}
4059
+ .dsh-st-overview-schedule{display:inline-flex;grid-column:1;grid-row:2;align-items:center;gap:5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:12px;line-height:16px}
4060
+ .dsh-st-overview-next{display:inline-flex;grid-column:2;grid-row:2;flex:none;min-width:0;flex-direction:row;align-items:baseline;gap:4px;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:10px;line-height:14px;white-space:nowrap}
3917
4061
  .dsh-st-overview-next strong{color:var(--dsw-alias-label-secondary,#b6bac2);font-size:12px;font-weight:600;line-height:16px;white-space:nowrap}
3918
4062
  .dsh-st-overview-row:not(.is-paused) .dsh-st-overview-next strong{color:#45d483}
3919
- .dsh-st-overview-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#8b8f98)}
4063
+ .dsh-st-overview-chevron{grid-column:3;grid-row:2;flex:none;color:var(--dsw-alias-label-tertiary,#8b8f98)}
4064
+ .dsh-st-overview-toggle{position:absolute;top:3px;right:2px;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:44px;height:28px;cursor:pointer}
4065
+ .dsh-st-overview-toggle input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
4066
+ .dsh-st-overview-toggle>span{position:relative;width:28px;height:16px;border-radius:999px;background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.14));box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,rgba(255,255,255,.12));transition:background .14s ease}
4067
+ .dsh-st-overview-toggle>span::after{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.32);content:"";transition:transform .14s ease}
4068
+ .dsh-st-overview-toggle input:checked+span{background:#32cd8f;box-shadow:none}
4069
+ .dsh-st-overview-toggle input:checked+span::after{transform:translateX(12px)}
4070
+ .dsh-st-overview-toggle input:focus-visible+span{outline:2px solid var(--dsw-alias-state-business-primary,#4c8dff);outline-offset:2px}
4071
+ .dsh-st-overview-toggle:has(input:disabled){cursor:wait;opacity:.62}
3920
4072
  .dsh-st-rail{box-sizing:border-box;height:100%;overflow:auto;padding:4px var(--dsh-sidebar-inline-padding,12px) 18px 8px;color:inherit;scrollbar-gutter:stable}
3921
4073
  .dsh-st-rail-empty{padding:16px 10px;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:12px}
3922
4074
  .dsh-st-rail-group{margin:0 0 8px}
@@ -3937,6 +4089,7 @@ var CSS_TEXT = `
3937
4089
  .dsh-st-rail-head.is-static{cursor:default;font-weight:600}
3938
4090
  .dsh-st-rail-session.is-on{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}
3939
4091
  .dsh-st-n,.dsh-st-n *{box-sizing:border-box}.dsh-st-n{box-sizing:border-box;display:flex;flex:1;min-width:0;max-width:100%;min-height:0;flex-direction:column;padding:0;padding-right:var(--dsh-sidebar-inline-padding,12px);color:var(--dsw-alias-label-primary,inherit);font:14px/20px inherit;overflow:hidden}.dsh-st-n-toolbar{box-sizing:border-box;flex:none;height:36px;margin:2px -4px 4px 0;padding-left:4px;display:flex;justify-content:flex-end;align-items:center;gap:4px;overflow:visible;position:relative;z-index:2;color:var(--dsw-alias-label-tertiary,#81858C);border-radius:12px}.dsh-st-n-head-label{white-space:nowrap;min-width:0;max-width:45%;flex:none;line-height:20px;font-size:14px;overflow:hidden;transition:max-width .18s var(--ds-ease-in-out,ease),margin-right .18s var(--ds-ease-in-out,ease),opacity .12s var(--ds-ease-in-out,ease),transform .18s var(--ds-ease-in-out,ease),visibility 0s linear}.dsh-st-n-toolbar.is-search .dsh-st-n-head-label{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transform:translate(-4px);transition-delay:0s,0s,0s,0s,.18s}.dsh-st-n-search-slot{box-sizing:border-box;min-width:28px;max-width:28px;transition:max-width .18s var(--ds-ease-in-out,ease);flex:none;align-items:center;margin-left:auto;display:flex;position:relative;z-index:2}.dsh-st-n-toolbar.is-search .dsh-st-n-search-slot{flex:1;min-width:0;max-width:100%}.dsh-st-n-search{box-sizing:border-box;cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out,ease),padding .18s var(--ds-ease-in-out,ease),border-color .18s var(--ds-ease-in-out,ease);background:transparent;border:none;border-radius:50%;flex:none;align-items:center;margin:0;padding:0;display:flex;overflow:hidden}.dsh-st-n-toolbar.is-search .dsh-st-n-search{border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.10));width:calc(100% + 4px);height:30px;border-radius:10px;margin-inline:-2px;padding:0 4px 0 0}.dsh-st-n-search-btn,.dsh-st-n-head-btn{cursor:pointer;width:28px;height:28px;min-width:28px;min-height:28px;position:relative;z-index:1;color:var(--dsw-alias-label-secondary);background:transparent;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.dsh-st-n-toolbar.is-search .dsh-st-n-search-btn{width:28px;height:30px}.dsh-st-n-search-btn:hover,.dsh-st-n-head-btn:hover,.dsh-st-n-head-btn.is-on{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06));color:var(--dsw-alias-label-primary,inherit)}.dsh-st-n-toolbar.is-search .dsh-st-n-search-btn:hover{background:transparent}.dsh-st-n-head-acts{opacity:1;visibility:visible;max-width:32px;transition:max-width .18s var(--ds-ease-in-out,ease),opacity .12s var(--ds-ease-in-out,ease),transform .18s var(--ds-ease-in-out,ease),visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:visible;position:relative}.dsh-st-n-toolbar.is-search .dsh-st-n-head-acts{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transform:translate(4px);transition-delay:0s,0s,0s,.18s}.dsh-st-n-head-filter{position:relative}.dsh-st-n-search-input{display:none;opacity:0;pointer-events:none;width:0;min-width:0;flex:none;color:var(--dsw-alias-label-primary,inherit);transition:opacity .12s var(--ds-ease-in-out,ease);background:transparent;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.dsh-st-n-toolbar.is-search .dsh-st-n-search-input{display:block;opacity:1;pointer-events:auto;margin-left:-2px;width:auto;flex:1;min-width:0}.dsh-st-n-search-input::placeholder{color:var(--dsw-alias-label-tertiary,#81858C)}.dsh-st-n-search-clear{cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:transparent;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.dsh-st-n-search-clear:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}@media (prefers-reduced-motion:reduce){.dsh-st-n-head-label,.dsh-st-n-search-slot,.dsh-st-n-search,.dsh-st-n-head-acts,.dsh-st-n-search-input{transition:none}}.dsh-st-n-filter-menu{position:absolute;right:0;top:calc(100% + 6px);z-index:30;min-width:196px;padding:8px 6px;border:1px solid var(--dsw-alias-border-inverted,rgba(255,255,255,.12));border-radius:12px;background:var(--dsw-specific-menu,#1c2128);box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.36))}.dsh-st-n-filter-label{padding:6px 10px 4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#81858C)}.dsh-st-n-filter-split{height:1px;margin:6px 8px;background:var(--dsw-alias-border-l2,rgba(255,255,255,.1))}.dsh-st-n-filter-menu button{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;min-height:36px;padding:6px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,inherit);font:14px/20px inherit;cursor:pointer;text-align:left}.dsh-st-n-filter-menu button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}.dsh-st-n-filter-tick{width:16px;height:16px;flex:none}.dsh-st-n-tree{flex:1;min-width:0;max-width:100%;min-height:0;overflow-x:hidden;overflow-y:auto;padding:0 0 16px;user-select:none}.dsh-st-n-empty{padding:14px 8px;color:var(--dsw-alias-label-tertiary,#8b8f98);font-size:12px}.dsh-st-n-group{min-width:0;max-width:100%}.dsh-st-n-row,.dsh-st-n-sess{display:flex;align-items:center;max-width:100%;border-radius:8px;padding:0 8px 0 12px;cursor:pointer;border:0;background:transparent;color:var(--dsw-alias-label-primary,inherit);text-align:left;font:14px/20px inherit}.dsh-st-n-row{height:34px;gap:6px}.dsh-st-n-sess{height:32px;gap:0;position:relative;width:100%;appearance:none}.dsh-st-n-row:hover,.dsh-st-n-sess:hover,.dsh-st-n-sess.is-on,.dsh-st-n-sess.is-menu{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}.dsh-st-n-slot{flex:none;width:16px;height:20px;display:inline-flex;align-items:center;justify-content:center}.dsh-st-n-folder{color:var(--dsw-alias-label-secondary,#9ca39f)}.dsh-st-n-lead{color:var(--dsw-alias-label-tertiary,#81858C)}.dsh-st-n-corner{color:var(--dsw-alias-label-caption,#ADB2B8);width:8px}.dsh-st-n-title{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}.dsh-st-n-sess .dsh-st-n-title{margin:0 6px 0 4px}.dsh-st-n-time{flex:none;font-size:12px;line-height:20px;color:var(--dsw-alias-label-tertiary,#81858C);white-space:nowrap}.dsh-st-n-acts{flex:none;display:none;align-items:center;gap:12px}.dsh-st-n-row:hover .dsh-st-n-acts,.dsh-st-n-sess:hover .dsh-st-n-acts,.dsh-st-n-row.is-menu .dsh-st-n-acts,.dsh-st-n-sess.is-menu .dsh-st-n-acts{display:inline-flex}.dsh-st-n-sess:hover .dsh-st-n-time,.dsh-st-n-sess.is-menu .dsh-st-n-time{display:none}.dsh-st-n-ico{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border:0;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#81858C);padding:0;cursor:pointer}.dsh-st-n-ico:hover{color:var(--dsw-alias-label-primary,inherit)}.dsh-st-n-menu.is-float{position:fixed;z-index:4000;right:auto;top:auto}.dsh-st-n-menu{position:absolute;right:8px;top:calc(100% + 4px);z-index:1100;min-width:218px;max-width:360px;box-sizing:border-box;padding:4px;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-inverted,rgba(255,255,255,.12));border-radius:12px;background:var(--dsw-specific-menu,#1c2128);box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.36))}.dsh-st-n-menu button{display:flex;align-items:center;gap:8px;width:100%;min-height:40px;padding:8px 10px;border:0;border-radius:10px;background:transparent;cursor:pointer;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary,inherit);text-align:left}.dsh-st-n-menu button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}.dsh-st-n-mi{display:inline-flex;flex:none;width:16px;height:16px;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary,#81858C)}.dsh-st-n-menu button.danger{color:var(--dsw-alias-state-error-primary,#f85149)}.dsh-st-n-menu button.danger .dsh-st-n-mi{color:inherit}.dsh-st-n-menu button.danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgba(248,81,73,.12))}.dsh-st-n-hover{position:fixed;z-index:4100;min-width:188px;max-width:280px;padding:12px 14px;border:1px solid var(--dsw-alias-border-inverted,rgba(255,255,255,.12));border-radius:12px;background:var(--dsw-specific-menu,#1c2128);box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.36));color:var(--dsw-alias-label-primary,inherit)}.dsh-st-n-hover-title{font-size:14px;line-height:20px;font-weight:500}.dsh-st-n-hover-time{margin-top:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#81858C)}.dsh-st-n-hover-state{display:flex;align-items:center;gap:6px;margin-top:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#9ca39f)}.dsh-st-n-hover-dot{width:8px;height:8px;border-radius:50%;background:#34c759;flex:none}.dsh-st-n-hover-dot.is-run{background:#4c8dff}.dsh-st-n-rename{flex:1;min-width:0;margin:0 6px 0 4px;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:var(--dsw-alias-button-elevated-fill,rgba(255,255,255,.04));color:inherit;font:inherit;padding:0 2px}
4092
+ .dsh-st-n-row{padding-left:8px}
3940
4093
  .dsh-st-n-row.is-menu{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}
3941
4094
  .dsh-st-overview-sort .dsh-st-n-head-btn.is-open{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06));color:var(--dsw-alias-label-primary,inherit)}
3942
4095
  .dsh-st-n-row[aria-expanded="true"].has-current-session .dsh-st-n-folder{color:var(--dsw-static-deepseek-450,#4c8dff)}
@@ -4082,6 +4235,7 @@ function apply(ctx) {
4082
4235
  const permissionT = ctx.locale.bind("permission.access");
4083
4236
  const modelT = ctx.locale.bind("model");
4084
4237
  const runtime = createAutomationRuntime(ctx.connection.rpc);
4238
+ ctx.effect(() => installAutomationSessionSync(runtime, () => ctx.sessions), "dsh-automation: session sync");
4085
4239
  ctx.effect(() => installSettingsNavIcon(() => [t("tab"), "Scheduled tasks", "\u5B9A\u65F6\u4EFB\u52A1"]), "dsh-automation: settings icon");
4086
4240
  ctx.slots.inject("settings.section", () => ctx.slots.register({
4087
4241
  name: "settings.section",