@michengai/dsh-automation 0.1.39 → 0.1.41

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
@@ -2,7 +2,17 @@
2
2
 
3
3
  [简体中文](CHANGELOG.zh-CN.md)
4
4
 
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.
5
+ Upcoming changes and 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
+
7
+ ## 0.1.41 - 2026-09-14
8
+
9
+ - Fix Scheduled and execution-history navigation disappearing when used with the archive and IM plugins.
10
+
11
+ ## 0.1.40 - 2026-09-12
12
+
13
+ - Configure concurrency per automation to allow overlapping executions of the same task. The default is `1`, preserving existing task behavior. When full, scheduled triggers are skipped and manual starts are rejected; lowering the limit does not cancel existing runs.
14
+ - Reduce the minimum interval from 5 minutes to 1 minute.
15
+ - Agent create/update tools accept `max_concurrent_runs`, and Web RPC accepts per-task `maxConcurrentRuns`; these are separate from the retired plugin-level global concurrency setting.
6
16
 
7
17
  ## 0.1.39 - 2026-09-12
8
18
 
@@ -2,7 +2,17 @@
2
2
 
3
3
  [English](CHANGELOG.md)
4
4
 
5
- 以下记录最近发布的五个版本。Git 标签与 GitHub Release 现已和这些条目同步;历史条目继续保留原始发布提交链接。
5
+ 以下记录待发布变更及近期已发布版本。Git 标签与 GitHub Release 现已和这些条目同步;历史条目继续保留原始发布提交链接。
6
+
7
+ ## 0.1.41 - 2026-09-14
8
+
9
+ - 修复与归档、IM 插件组合使用时,侧栏定时入口及执行记录入口可能消失的问题。
10
+
11
+ ## 0.1.40 - 2026-09-12
12
+
13
+ - 支持为单个自动化设置并发数量,让同一任务的多次执行可以同时进行。默认 `1`,旧任务保持原有行为;满额时跳过定时触发并拒绝手动启动,调低上限不取消已有运行。
14
+ - 间隔计划的最短间隔由 5 分钟缩短为 1 分钟。
15
+ - Agent 创建、更新工具支持 `max_concurrent_runs`,Web RPC 支持任务级 `maxConcurrentRuns`;它们与已停用的插件级全局并发配置无关。
6
16
 
7
17
  ## 0.1.39 - 2026-09-12
8
18
 
package/README.md CHANGED
@@ -55,18 +55,21 @@ Run history stays in Settings and can be filtered by day, week, month, task, or
55
55
 
56
56
  ## DSH product ecosystem
57
57
 
58
- For a ready-to-use workbench, download [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases). If you already use [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), install any of these eight plugins individually. The desktop app includes all eight.
58
+ For a desktop workbench, download [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases). Existing [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) installations can add plugins as needed by following each project's README. Below are 11 first-party plugins; consult the corresponding desktop release notes and bundled catalog for what that version includes.
59
59
 
60
60
  | Plugin | What you can do |
61
61
  | --- | --- |
62
62
  | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | Organize projects and conversations, search tasks, and navigate chat turns |
63
- | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | Send tasks and receive replies through your usual messenger |
64
- | [Automation](https://github.com/MichengAI/dsh-automation) | Schedule tasks and review each run |
63
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | Choose and summon specialists for your task |
65
64
  | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | Find, enable, create, and import local skills |
66
65
  | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | Search, restore, or clean up archived conversations |
67
- | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | Choose and summon specialists for your task |
66
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | Send tasks and receive replies through messaging platforms |
67
+ | [Automation](https://github.com/MichengAI/dsh-automation) | Schedule tasks and review each run |
68
68
  | [BTW](https://github.com/MichengAI/dsh-btw) | Ask side questions without interrupting the main task |
69
- | [Simplify](https://github.com/MichengAI/dsh-simplify) | Use /simplify to improve code within your Git changes |
69
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | Use `/simplify` to improve code within your Git changes |
70
+ | [PUA](https://github.com/MichengAI/dsh-pua) | Guide the Agent to try new approaches after failures, investigate causes, and verify results before completion |
71
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | Use `/review` to request an independent Agent code review and receive the report in the current conversation |
72
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | View conversation notifications and respond to tool approvals and questions through a desktop pet |
70
73
 
71
74
  ## Prerequisites
72
75
 
@@ -130,8 +133,9 @@ Each dispatched run uses the saved prompt, workspace, model, and permission boun
130
133
  | Approval | Chat create follows the session policy. Full access (`never`) proceeds; Workspace Write / Read Only (`ask`) shows the official card. Unattended runs stay fail-closed `never`. |
131
134
  | Retry | No automatic retry after a started run. |
132
135
  | Host restart | Leftover `queued` / `running` records become `failed(host_interrupted)`. |
133
- | Concurrency | Different automations have no plugin-level global concurrency cap, including those in the same directory. The legacy `maxConcurrentRuns` setting no longer takes effect. |
134
- | Overlap | One active run per rule. A colliding occurrence is recorded as `skipped(overlap)`. |
136
+ | Concurrency | Different automations have no plugin-level global concurrency cap, including those in the same directory. The legacy plugin-level global concurrency setting (`maxConcurrentRuns` in plugin config) no longer takes effect; it is separate from the per-task field below. |
137
+ | Minimum interval | Since `0.1.40`, interval schedules accept whole minutes starting at `1`. Missed triggers are not replayed individually; only the latest due occurrence is considered within the recovery window. |
138
+ | Per-task concurrency | Available since `0.1.40`. Set Concurrency in the create/edit dialog (positive integer, default `1`). Queued and running executions share this limit. When full, scheduled triggers become `skipped(overlap)` and manual runs return a limit error. Lowering the limit does not cancel existing runs. Agent create/update tools accept `max_concurrent_runs`; Web RPC accepts `maxConcurrentRuns`. Existing tasks default to `1`. |
135
139
 
136
140
  A schedule stores future intent. It is not a cached permission grant.
137
141
 
package/README.zh-CN.md CHANGED
@@ -55,18 +55,21 @@
55
55
 
56
56
  ## DSH 产品生态
57
57
 
58
- 想直接使用完整工作台,可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases);已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境,可按需独立安装以下 8 个自研插件。桌面端已随附这些插件。
58
+ 想使用桌面工作台,可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases);已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境,可按各项目 README 按需安装。以下列出 11 个自研插件;桌面端实际随附范围以对应版本的发行说明和内置清单为准。
59
59
 
60
60
  | 插件 | 你可以用它做什么 |
61
61
  | --- | --- |
62
62
  | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | 整理项目与会话、搜索任务、跳转对话轮次 |
63
- | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从微信、飞书、钉钉等消息平台下任务、收回复 |
64
- | [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务,查看每次运行的结果 |
63
+ | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
65
64
  | [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | 统一查找、启停、创建和导入本机技能 |
66
65
  | [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | 搜索、恢复或清理已归档会话 |
67
- | [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
66
+ | [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从消息平台下任务、收回复 |
67
+ | [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务,查看每次运行的结果 |
68
68
  | [BTW](https://github.com/MichengAI/dsh-btw) | 在当前上下文中临时旁问,不打断主任务 |
69
- | [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 /simplify 整理 Git 改动范围内的代码 |
69
+ | [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 `/simplify` 整理 Git 改动范围内的代码 |
70
+ | [PUA](https://github.com/MichengAI/dsh-pua) | 引导 Agent 在失败时换方法、查原因,并在完成前验证结果 |
71
+ | [Code Review](https://github.com/MichengAI/dsh-code-review) | 用 `/review` 发起独立 Agent 代码审查,在当前会话接收报告 |
72
+ | [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | 通过桌面宠物查看会话提醒、处理工具审批和问题回答 |
70
73
 
71
74
  ## 前置条件
72
75
 
@@ -130,8 +133,9 @@ dsh --profile web --dump-config
130
133
  | 审批 | 对话创建跟随当前会话策略。Full access(`never`)直接创建;Workspace Write / Read Only(`ask`)走官方授权卡。无人值守运行仍是 fail-closed 的 `never`。 |
131
134
  | 重试 | 已经开始的运行不会自动重试。 |
132
135
  | Host 重启 | 遗留的 `queued` / `running` 会变成 `failed(host_interrupted)`。 |
133
- | 并发 | 不同自动化任务不设插件级全局并发上限,同一目录下也可同时运行。旧配置 `maxConcurrentRuns` 不再生效。 |
134
- | 重叠 | 同一规则同时最多一个 active run。冲突 occurrence 记为 `skipped(overlap)`。 |
136
+ | 并发 | 不同自动化任务不设插件级全局并发上限,同一目录下也可同时运行。旧插件级全局并发配置(插件 config 中的 `maxConcurrentRuns`)不再生效,与下方任务级字段不同。 |
137
+ | 最短间隔 | 自 `0.1.40` 起,间隔计划支持最短 `1` 分钟,仅接受整数分钟。恢复时仅处理补跑窗口内最近的到期点,不逐次补齐漏掉的触发。 |
138
+ | 单任务并发 | 自 `0.1.40` 起支持。在任务的新建/编辑页设置“并发数量”(正整数,默认 `1`)。同一任务的排队及运行记录共用此上限;满额时定时触发记为 `skipped(overlap)`,手动运行返回上限提示。调低上限不取消已有运行。Agent 创建/更新工具使用 `max_concurrent_runs`,Web RPC 使用 `maxConcurrentRuns`;旧任务默认 `1`。 |
135
139
 
136
140
  计划只表达未来意图,不是缓存下来的授权。
137
141
 
@@ -185,6 +189,8 @@ pnpm build
185
189
 
186
190
  `pnpm check` 会连续执行类型检查、单元测试、真实宿主兼容测试和构建。`pnpm test:host` 不加载宿主运行时桩,使用官方 AgentLoop、Session V3 和权限服务,模型响应由本地固定适配器提供,不调用外部模型。浏览器交互和真实模型调用需另行验收。
187
191
 
192
+ 跨插件侧栏回归可运行 `node --import tsx scripts/verify-sidebar-composition.mjs <dsh-im-connect 检出目录>`。它执行两仓库的接入逻辑和页签注册表,覆盖归档、IM、定时及 Codex UI 的注册顺序、延迟通知与卸载接管;插槽和计时器使用模拟环境,不替代 Desktop 渲染验收,也不安装插件。
193
+
188
194
  `pnpm test:matrix` 先构建发布包,再联网为上述四个版本分别建立临时 npm 环境,执行严格 peer 安装、整棵官方依赖树版本检查、发布入口加载、真实 AgentLoop 执行和服务回归。四版均通过;服务回归中的持久化枚举使用内存夹具,覆盖新旧冷会话格式,不等同于真实 Profile 的磁盘迁移验收。脚本输出证据目录,保留各版安装锁文件、测试日志和 `results.json`。矩阵不修改开发依赖,也不连接用户 Profile。
189
195
 
190
196
  ## 许可证
package/lib/client.js CHANGED
@@ -74,6 +74,7 @@ function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], def
74
74
  scheduleKind: "daily",
75
75
  onceAt: localDateTimeValue(now),
76
76
  everyMinutes: "60",
77
+ maxConcurrentRuns: "1",
77
78
  intervalAnchor: "",
78
79
  time: "09:00",
79
80
  weekdays: [1, 2, 3, 4, 5],
@@ -89,6 +90,8 @@ function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], def
89
90
  };
90
91
  }
91
92
  function buildCreateInput(form, workspaces, models, now = /* @__PURE__ */ new Date(), options = {}) {
93
+ const maxConcurrentRuns = Number(form.maxConcurrentRuns);
94
+ if (!Number.isSafeInteger(maxConcurrentRuns) || maxConcurrentRuns < 1) throw new AutomationFormError("form.error.maxConcurrentRuns");
92
95
  const name2 = form.name.trim();
93
96
  const prompt = form.prompt.trim();
94
97
  if (name2 === "") throw new AutomationFormError("form.error.name");
@@ -107,7 +110,7 @@ function buildCreateInput(form, workspaces, models, now = /* @__PURE__ */ new Da
107
110
  }
108
111
  case "interval": {
109
112
  const everyMinutes = Number(form.everyMinutes);
110
- if (!Number.isInteger(everyMinutes) || everyMinutes < 5 || everyMinutes > 43200) {
113
+ if (!Number.isInteger(everyMinutes) || everyMinutes < 1 || everyMinutes > 43200) {
111
114
  throw new AutomationFormError("form.error.interval");
112
115
  }
113
116
  schedule = {
@@ -151,6 +154,7 @@ function buildCreateInput(form, workspaces, models, now = /* @__PURE__ */ new Da
151
154
  schedule,
152
155
  timeZone: form.timeZone,
153
156
  permission: form.permission,
157
+ maxConcurrentRuns,
154
158
  workspaceId: workspace.id,
155
159
  cwd: workspace.path,
156
160
  ...selected === void 0 ? { provider: null, model: null } : { provider: selected.provider, model: selected.model },
@@ -306,6 +310,7 @@ function formFromAutomation(item, workspaces = [], defaultModel, defaultPermissi
306
310
  name: item.name,
307
311
  prompt: item.prompt,
308
312
  permission: item.permission,
313
+ maxConcurrentRuns: String(item.maxConcurrentRuns ?? 1),
309
314
  workspaceId: item.workspaceId ?? base.workspaceId,
310
315
  modelKey,
311
316
  reasoningEffort: item.reasoningEffort ?? "none",
@@ -1141,49 +1146,55 @@ function CreateModal({
1141
1146
  t("form.name"),
1142
1147
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { value: form.name, placeholder: t("form.namePlaceholder"), onChange: (event) => update({ name: event.target.value }) })
1143
1148
  ] }),
1144
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-st-field", children: [
1145
- t("form.planTime"),
1146
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-st-inline", children: [
1147
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1148
- MenuSelect,
1149
- {
1150
- value: form.scheduleKind,
1151
- options: KINDS.map((kind) => ({ value: kind, label: t(`form.${kind}`) })),
1152
- onChange: (value) => update({ scheduleKind: value })
1153
- }
1154
- ),
1155
- form.scheduleKind === "once" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1156
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "date", min: today, value: datePart, onChange: (event) => update({ onceAt: clampOnceAt(`${event.target.value}T${timePart}`) }) }),
1157
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: timePart, ...minOnceTime === void 0 ? {} : { minTime: minOnceTime }, onChange: (value) => update({ onceAt: clampOnceAt(`${datePart}T${value}`) }) })
1158
- ] }),
1159
- form.scheduleKind === "interval" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1160
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { className: "is-narrow", type: "number", min: 5, value: form.everyMinutes, onChange: (event) => update({ everyMinutes: event.target.value }) }),
1161
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.minutesShort") })
1162
- ] }),
1163
- form.scheduleKind === "hourly" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1164
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MenuSelect, { value: form.hourlyMinute, options: MINUTES.map((item) => ({ value: item, label: item })), onChange: (value) => update({ hourlyMinute: value }) }),
1165
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.minutesShort") })
1166
- ] }),
1167
- (form.scheduleKind === "daily" || form.scheduleKind === "weekly") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) }),
1168
- form.scheduleKind === "monthly" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1149
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-st-plan-row", children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-st-field", children: [
1151
+ t("form.planTime"),
1152
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-st-inline", children: [
1169
1153
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1170
1154
  MenuSelect,
1171
1155
  {
1172
- value: form.monthDay,
1173
- options: Array.from({ length: 31 }, (_, index) => {
1174
- const day = String(index + 1);
1175
- return { value: day, label: t("form.monthDay", { day }) };
1176
- }),
1177
- onChange: (value) => update({ monthDay: value })
1156
+ value: form.scheduleKind,
1157
+ options: KINDS.map((kind) => ({ value: kind, label: t(`form.${kind}`) })),
1158
+ onChange: (value) => update({ scheduleKind: value })
1178
1159
  }
1179
1160
  ),
1180
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) })
1181
- ] }),
1182
- form.scheduleKind === "custom" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1183
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { className: "is-narrow", type: "number", min: 1, value: form.customDays, onChange: (event) => update({ customDays: event.target.value }) }),
1184
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.daysShort") }),
1185
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) })
1161
+ form.scheduleKind === "once" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1162
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "date", min: today, value: datePart, onChange: (event) => update({ onceAt: clampOnceAt(`${event.target.value}T${timePart}`) }) }),
1163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: timePart, ...minOnceTime === void 0 ? {} : { minTime: minOnceTime }, onChange: (value) => update({ onceAt: clampOnceAt(`${datePart}T${value}`) }) })
1164
+ ] }),
1165
+ form.scheduleKind === "interval" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1166
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { className: "is-narrow", type: "number", min: 1, value: form.everyMinutes, onChange: (event) => update({ everyMinutes: event.target.value }) }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.minutesShort") })
1168
+ ] }),
1169
+ form.scheduleKind === "hourly" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1170
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MenuSelect, { value: form.hourlyMinute, options: MINUTES.map((item) => ({ value: item, label: item })), onChange: (value) => update({ hourlyMinute: value }) }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.minutesShort") })
1172
+ ] }),
1173
+ (form.scheduleKind === "daily" || form.scheduleKind === "weekly") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) }),
1174
+ form.scheduleKind === "monthly" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1176
+ MenuSelect,
1177
+ {
1178
+ value: form.monthDay,
1179
+ options: Array.from({ length: 31 }, (_, index) => {
1180
+ const day = String(index + 1);
1181
+ return { value: day, label: t("form.monthDay", { day }) };
1182
+ }),
1183
+ onChange: (value) => update({ monthDay: value })
1184
+ }
1185
+ ),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) })
1187
+ ] }),
1188
+ form.scheduleKind === "custom" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { className: "is-narrow", type: "number", min: 1, value: form.customDays, onChange: (event) => update({ customDays: event.target.value }) }),
1190
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-st-suffix", children: t("form.daysShort") }),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TimeSelect, { value: form.time, onChange: (value) => update({ time: value }) })
1192
+ ] })
1186
1193
  ] })
1194
+ ] }),
1195
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "dsh-st-field dsh-st-concurrency", title: t("form.maxConcurrentRunsHint"), children: [
1196
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t("form.maxConcurrentRuns") }),
1197
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "number", min: 1, step: 1, required: true, value: form.maxConcurrentRuns, onChange: (event) => update({ maxConcurrentRuns: event.target.value }) })
1187
1198
  ] })
1188
1199
  ] }),
1189
1200
  form.scheduleKind === "weekly" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-st-weekdays", children: WEEKDAYS.map((day) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2633,6 +2644,9 @@ var en = {
2633
2644
  "form.time": "Time",
2634
2645
  "form.days": "Weekdays",
2635
2646
  "form.timeZone": "Time zone",
2647
+ "form.maxConcurrentRuns": "Concurrency",
2648
+ "form.maxConcurrentRunsHint": "Queued and running executions share this limit. Defaults to 1; when full, scheduled triggers are skipped and manual runs are rejected.",
2649
+ "form.error.maxConcurrentRuns": "Enter a positive whole number for concurrent runs.",
2636
2650
  "form.permission": "Permission boundary",
2637
2651
  "permission.readOnly": "Read Only",
2638
2652
  "permission.workspaceWrite": "Workspace Write",
@@ -2643,7 +2657,7 @@ var en = {
2643
2657
  "form.error.name": "Enter a name.",
2644
2658
  "form.error.prompt": "Enter a complete, standalone task prompt.",
2645
2659
  "form.error.once": "Choose a valid future date and time.",
2646
- "form.error.interval": "The interval must be between 5 and 43,200 minutes.",
2660
+ "form.error.interval": "The interval must be between 1 and 43,200 minutes.",
2647
2661
  "form.error.weekdays": "Select at least one weekday.",
2648
2662
  "day.1": "Mon",
2649
2663
  "day.2": "Tue",
@@ -2848,6 +2862,9 @@ var zh = {
2848
2862
  "form.time": "\u65F6\u95F4",
2849
2863
  "form.days": "\u661F\u671F",
2850
2864
  "form.timeZone": "\u65F6\u533A",
2865
+ "form.maxConcurrentRuns": "\u5E76\u53D1\u6570\u91CF",
2866
+ "form.maxConcurrentRunsHint": "\u540C\u4E00\u4EFB\u52A1\u7684\u6392\u961F\u53CA\u8FD0\u884C\u6B21\u6570\u5171\u7528\u6B64\u4E0A\u9650\uFF0C\u9ED8\u8BA4 1\uFF1B\u6EE1\u989D\u65F6\u8DF3\u8FC7\u5F53\u6B21\u5B9A\u65F6\u89E6\u53D1\uFF0C\u624B\u52A8\u8FD0\u884C\u5C06\u88AB\u62D2\u7EDD\u3002",
2867
+ "form.error.maxConcurrentRuns": "\u5E76\u53D1\u6570\u91CF\u5FC5\u987B\u4E3A\u5927\u4E8E\u7B49\u4E8E 1 \u7684\u6574\u6570\u3002",
2851
2868
  "form.permission": "\u6743\u9650\u8FB9\u754C",
2852
2869
  "permission.readOnly": "\u53EA\u8BFB",
2853
2870
  "permission.workspaceWrite": "\u5DE5\u4F5C\u533A\u5199\u5165",
@@ -2858,7 +2875,7 @@ var zh = {
2858
2875
  "form.error.name": "\u8BF7\u8F93\u5165\u540D\u79F0\u3002",
2859
2876
  "form.error.prompt": "\u8BF7\u8F93\u5165\u5B8C\u6574\u3001\u72EC\u7ACB\u7684\u4EFB\u52A1\u6307\u4EE4\u3002",
2860
2877
  "form.error.once": "\u8BF7\u9009\u62E9\u6709\u6548\u7684\u672A\u6765\u65E5\u671F\u548C\u65F6\u95F4\u3002",
2861
- "form.error.interval": "\u8FD0\u884C\u95F4\u9694\u5FC5\u987B\u5728 5 \u5230 43,200 \u5206\u949F\u4E4B\u95F4\u3002",
2878
+ "form.error.interval": "\u8FD0\u884C\u95F4\u9694\u5FC5\u987B\u5728 1 \u5230 43,200 \u5206\u949F\u4E4B\u95F4\u3002",
2862
2879
  "form.error.weekdays": "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u5929\u3002",
2863
2880
  "day.1": "\u5468\u4E00",
2864
2881
  "day.2": "\u5468\u4E8C",
@@ -4051,6 +4068,7 @@ var CSS_TEXT = `
4051
4068
  .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:max-content;min-width:min(240px,calc(100vw - 32px));max-width: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-side>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}
4052
4069
  .dsh-st-suffix{color:var(--dsw-alias-label-secondary);font-size:13px}
4053
4070
  .dsh-st-inline input.is-narrow{width:72px;flex:none}
4071
+ .dsh-st-plan-row{display:flex;align-items:flex-start;gap:16px}.dsh-st-plan-row>.dsh-st-field:first-child{flex:1}.dsh-st-plan-row>.dsh-st-concurrency{flex:0 0 120px}
4054
4072
  .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)}
4055
4073
  .dsh-st-time .dsh-st-select{min-width:48px}
4056
4074
  .dsh-st-time .dsh-st-select-btn{width:auto;min-height:32px;padding:0 6px;border:0;background:transparent}
@@ -4630,10 +4648,16 @@ function apply(ctx) {
4630
4648
  ctx.slots.inject("sidebar.workspaces", () => {
4631
4649
  let wrappedEntry;
4632
4650
  let originalComp;
4651
+ let ownedWrapper;
4652
+ let ownedRegistry;
4653
+ let insertedRegistry;
4633
4654
  let removeInsertedTab = () => void 0;
4634
4655
  let wrapped = false;
4635
4656
  let syncing = false;
4636
4657
  let retryTimer;
4658
+ const notifyPeers = () => {
4659
+ queueMicrotask(() => window.dispatchEvent(new Event("dsh-native-sidebar-change")));
4660
+ };
4637
4661
  const listenChannels = (listener) => ctx.slots.subscribe?.("sidebar.channels", listener) ?? (() => void 0);
4638
4662
  const stopRetry = () => {
4639
4663
  if (retryTimer !== void 0) {
@@ -4645,19 +4669,27 @@ function apply(ctx) {
4645
4669
  stopRetry();
4646
4670
  removeInsertedTab();
4647
4671
  removeInsertedTab = () => void 0;
4648
- if (wrappedEntry !== void 0 && originalComp !== void 0) {
4649
- try {
4650
- wrappedEntry.component = originalComp;
4651
- } catch {
4652
- }
4672
+ insertedRegistry = void 0;
4673
+ if (wrappedEntry !== void 0) {
4674
+ if (originalComp !== void 0 && wrappedEntry.component === ownedWrapper) wrappedEntry.component = originalComp;
4675
+ const record = wrappedEntry;
4676
+ if (record.__dshNativeTabs === ownedRegistry) delete record.__dshNativeTabs;
4677
+ notifyPeers();
4653
4678
  }
4654
4679
  wrappedEntry = void 0;
4655
4680
  originalComp = void 0;
4681
+ ownedWrapper = void 0;
4682
+ ownedRegistry = void 0;
4656
4683
  wrapped = false;
4657
4684
  };
4658
4685
  const insertScheduleTab = (entry, openSession) => {
4659
4686
  const registry = findNativeTabRegistry(entry);
4660
4687
  if (registry === void 0) return false;
4688
+ if (insertedRegistry !== registry) {
4689
+ removeInsertedTab();
4690
+ removeInsertedTab = () => void 0;
4691
+ insertedRegistry = registry;
4692
+ }
4661
4693
  if (registry.getTabs().some((item) => item.id === "schedule")) return true;
4662
4694
  removeInsertedTab = registry.insert({
4663
4695
  id: "schedule",
@@ -4689,7 +4721,7 @@ function apply(ctx) {
4689
4721
  const record = entry;
4690
4722
  if (insertScheduleTab(entry) || insertScheduleTab(record.component)) return true;
4691
4723
  }
4692
- return wrappedEntry !== void 0 && insertScheduleTab(wrappedEntry);
4724
+ return false;
4693
4725
  };
4694
4726
  const ensureScheduleTab = () => {
4695
4727
  if (insertIntoKnownHosts()) {
@@ -4722,7 +4754,7 @@ function apply(ctx) {
4722
4754
  ...innerProps.useSessions === void 0 ? {} : { useSessions: innerProps.useSessions },
4723
4755
  ...innerProps.useWorkspaces === void 0 ? {} : { useWorkspaces: innerProps.useWorkspaces },
4724
4756
  ...innerProps.renderSlot === void 0 ? {} : { renderSlot: innerProps.renderSlot },
4725
- ...originalComp === void 0 ? {} : { officialTree: originalComp }
4757
+ officialTree: tree
4726
4758
  });
4727
4759
  };
4728
4760
  var AutomationNativeWorkspaceShell = AutomationNativeWorkspaceShell2;
@@ -4731,13 +4763,19 @@ function apply(ctx) {
4731
4763
  return;
4732
4764
  }
4733
4765
  const entries = readSlotEntries(ctx, "sidebar.workspaces");
4734
- const occupant = pickWrappableWorkspacesEntry(entries);
4735
- const current = occupant?.component;
4736
- if (current !== void 0 && isForeignSidebarHost(current)) {
4766
+ const active = entries.find((entry) => entry?.component !== void 0);
4767
+ if (wrappedEntry !== void 0 && (active !== wrappedEntry || active.component !== ownedWrapper)) unwrap();
4768
+ if (active === wrappedEntry && active?.component === ownedWrapper && ownedWrapper !== void 0) {
4737
4769
  ensureScheduleTab();
4738
4770
  return;
4739
4771
  }
4740
- if (wrappedEntry?.component !== void 0 && wrappedEntry.component.__dshAutomationWrapped === true) {
4772
+ if (active === void 0) {
4773
+ unwrap();
4774
+ return;
4775
+ }
4776
+ const occupant = pickWrappableWorkspacesEntry(entries);
4777
+ const current = occupant?.component;
4778
+ if (current !== void 0 && isForeignSidebarHost(current)) {
4741
4779
  ensureScheduleTab();
4742
4780
  return;
4743
4781
  }
@@ -4746,17 +4784,21 @@ function apply(ctx) {
4746
4784
  if (resolved === void 0) return;
4747
4785
  originalComp = resolved;
4748
4786
  const registry = createNativeTabRegistry(originalComp);
4787
+ ownedRegistry = registry;
4749
4788
  attachNativeTabRegistry(occupant, registry);
4750
4789
  const marked = AutomationNativeWorkspaceShell2;
4751
4790
  marked.displayName = "AutomationNativeWorkspaceShell";
4752
4791
  marked.__dshNativeTabHost = true;
4753
4792
  marked.__dshAutomationWrapped = true;
4754
4793
  marked.__dshAutomationOriginal = originalComp;
4794
+ const tree = originalComp;
4755
4795
  attachNativeTabRegistry(marked, registry);
4756
4796
  occupant.component = marked;
4797
+ ownedWrapper = marked;
4757
4798
  wrappedEntry = occupant;
4758
4799
  wrapped = true;
4759
4800
  ensureScheduleTab();
4801
+ notifyPeers();
4760
4802
  } catch (error) {
4761
4803
  console.warn("[dsh-automation] \u5305\u88F9\u5B98\u65B9\u4EFB\u52A1\u6811\u5931\u8D25", error);
4762
4804
  } finally {
@@ -4765,9 +4807,13 @@ function apply(ctx) {
4765
4807
  };
4766
4808
  sync();
4767
4809
  const unsub = typeof ctx.slots.subscribe === "function" ? ctx.slots.subscribe("sidebar.workspaces", sync) : () => void 0;
4810
+ const unsubSidebar = ctx.slots.subscribe?.("sidebar", sync) ?? (() => void 0);
4811
+ window.addEventListener("dsh-native-sidebar-change", sync);
4768
4812
  ensureScheduleTab();
4769
4813
  return () => {
4770
4814
  unsub();
4815
+ unsubSidebar();
4816
+ window.removeEventListener("dsh-native-sidebar-change", sync);
4771
4817
  unwrap();
4772
4818
  };
4773
4819
  });