@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/lib/index.js CHANGED
@@ -276,7 +276,7 @@ var AUTOMATION_CREATE_DESCRIPTION = [
276
276
  "\u4E3A\u5F53\u524D\u5DE5\u4F5C\u533A\u521B\u5EFA\u4E00\u6761\u72EC\u7ACB DSH \u81EA\u52A8\u5316\uFF1B\u6BCF\u6B21\u89E6\u53D1\u5F00\u542F\u5168\u65B0 Session\uFF0C\u4E0D\u7EE7\u627F\u5F53\u524D\u5BF9\u8BDD\u3002",
277
277
  '\u5DE5\u4F5C\u65E5\u65E9\u4E0A 8 \u70B9\u793A\u4F8B\uFF1Akind=weekly, weekdays=["MO","TU","WE","TH","FR"], time="08:00", time_zone="Asia/Shanghai"\u3002',
278
278
  "\u540C\u65F6\u652F\u6301 hourly(minute)\u3001monthly(month_day + time) \u548C custom(every_days + time)\u3002",
279
- "\u5FC5\u987B\u4F7F\u7528\u663E\u5F0F IANA \u65F6\u533A\u3002\u6700\u77ED\u95F4\u9694 5 \u5206\u949F\u3002\u9ED8\u8BA4\u6743\u9650\u6765\u81EA Host\uFF1B\u4E0D\u9700\u8981\u5199\u5165\u65F6\u5E94\u663E\u5F0F\u9009\u62E9 read-only\uFF0C\u53EA\u6709\u9700\u8981\u6539\u6587\u4EF6\u65F6\u624D\u9009 workspace-write\u3002"
279
+ "\u5FC5\u987B\u4F7F\u7528\u663E\u5F0F IANA \u65F6\u533A\u3002\u6700\u77ED\u95F4\u9694 1 \u5206\u949F\u3002\u9ED8\u8BA4\u6743\u9650\u6765\u81EA Host\uFF1B\u4E0D\u9700\u8981\u5199\u5165\u65F6\u5E94\u663E\u5F0F\u9009\u62E9 read-only\uFF0C\u53EA\u6709\u9700\u8981\u6539\u6587\u4EF6\u65F6\u624D\u9009 workspace-write\u3002"
280
280
  ].join("");
281
281
 
282
282
  // src/service.ts
@@ -21330,8 +21330,8 @@ function assertValidSchedule(schedule) {
21330
21330
  return;
21331
21331
  }
21332
21332
  if (schedule.kind === "interval") {
21333
- if (!Number.isInteger(schedule.everyMinutes) || schedule.everyMinutes < 5) {
21334
- throw new Error("interval.everyMinutes must be an integer of at least 5");
21333
+ if (!Number.isInteger(schedule.everyMinutes) || schedule.everyMinutes < 1) {
21334
+ throw new Error("interval.everyMinutes must be an integer of at least 1");
21335
21335
  }
21336
21336
  parseInstant(schedule.anchor, "interval.anchor");
21337
21337
  return;
@@ -21610,7 +21610,7 @@ var timeZone = nonBlank;
21610
21610
  var weekday = external_exports.enum(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]);
21611
21611
  var automationScheduleSchema = external_exports.discriminatedUnion("kind", [
21612
21612
  external_exports.object({ kind: external_exports.literal("once"), at: instant, timeZone }),
21613
- external_exports.object({ kind: external_exports.literal("interval"), everyMinutes: external_exports.number().int().min(5), anchor: instant, timeZone }),
21613
+ external_exports.object({ kind: external_exports.literal("interval"), everyMinutes: external_exports.number().int().min(1), anchor: instant, timeZone }),
21614
21614
  external_exports.object({ kind: external_exports.literal("daily"), time: external_exports.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/), timeZone }),
21615
21615
  external_exports.object({
21616
21616
  kind: external_exports.literal("weekly"),
@@ -21647,6 +21647,7 @@ var automationDefinitionSchema = external_exports.object({
21647
21647
  version: external_exports.literal(1),
21648
21648
  id: nonBlank,
21649
21649
  revision: external_exports.number().int().positive(),
21650
+ maxConcurrentRuns: external_exports.number().int().positive().default(1),
21650
21651
  name: nonBlank.max(200),
21651
21652
  prompt: nonBlank.max(1e5),
21652
21653
  status: external_exports.enum(["active", "paused"]),
@@ -21712,6 +21713,7 @@ function createDefinition(input) {
21712
21713
  name: requireNonBlank(input.name, "name"),
21713
21714
  prompt: requireNonBlank(input.prompt, "prompt"),
21714
21715
  status: "active",
21716
+ maxConcurrentRuns: input.maxConcurrentRuns ?? 1,
21715
21717
  schedule,
21716
21718
  rrule: scheduleToRRule(schedule),
21717
21719
  timeZone: schedule.timeZone,
@@ -21736,6 +21738,7 @@ function updateDefinition(current, input) {
21736
21738
  name: input.name === void 0 ? current.name : requireNonBlank(input.name, "name"),
21737
21739
  prompt: input.prompt === void 0 ? current.prompt : requireNonBlank(input.prompt, "prompt"),
21738
21740
  status: input.status ?? current.status,
21741
+ maxConcurrentRuns: input.maxConcurrentRuns ?? current.maxConcurrentRuns ?? 1,
21739
21742
  schedule,
21740
21743
  rrule: scheduleToRRule(schedule),
21741
21744
  timeZone: schedule.timeZone,
@@ -21981,6 +21984,7 @@ var AutomationService = class _AutomationService {
21981
21984
  try {
21982
21985
  value = createDefinition({
21983
21986
  id: `automation_${randomUUID2()}`,
21987
+ maxConcurrentRuns: request.maxConcurrentRuns ?? 1,
21984
21988
  name: request.name,
21985
21989
  prompt: request.prompt,
21986
21990
  schedule: request.schedule,
@@ -22073,11 +22077,12 @@ var AutomationService = class _AutomationService {
22073
22077
  const run = await this.serialize(async () => {
22074
22078
  const definition = await this.ownedDefinition(scope, id);
22075
22079
  throwIfCancelled(signal);
22076
- const alreadyActive = [...this.runs.entries()].some(
22080
+ const limit = definition.maxConcurrentRuns ?? 1;
22081
+ const activeCount = [...this.runs.entries()].filter(
22077
22082
  ([, candidate]) => candidate.automationId === id && (candidate.status === "queued" || candidate.status === "running")
22078
- );
22079
- if (alreadyActive)
22080
- throw new AutomationRequestError("\u8BE5\u81EA\u52A8\u5316\u5DF2\u6709\u6392\u961F\u6216\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002");
22083
+ ).length;
22084
+ if (activeCount >= limit)
22085
+ throw new AutomationRequestError(`\u8BE5\u81EA\u52A8\u5316\u5DF2\u6709\u6392\u961F\u6216\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\uFF0C\u5DF2\u8FBE\u5230\u5E76\u53D1\u4E0A\u9650\uFF08${limit}\uFF09\u3002`);
22081
22086
  const value = createManualRun(definition, toIso());
22082
22087
  await this.runs.put(value.id, value);
22083
22088
  return value;
@@ -22356,12 +22361,12 @@ var AutomationService = class _AutomationService {
22356
22361
  return;
22357
22362
  const candidate = createScheduledRun(definition, scheduledFor);
22358
22363
  if (this.runs.get(candidate.id) !== void 0) return;
22359
- const overlapping = related.some(
22364
+ const overlapping = related.filter(
22360
22365
  (run) => run.status === "queued" || run.status === "running"
22361
- );
22366
+ ).length >= (definition.maxConcurrentRuns ?? 1);
22362
22367
  const age = Date.parse(now2) - Date.parse(scheduledFor);
22363
22368
  if (overlapping || age > this.config.misfireGraceMs) {
22364
- const reason = overlapping ? { code: "overlap", message: "\u4E0A\u4E00\u6B21\u8FD0\u884C\u4ECD\u5728\u8FDB\u884C\uFF0C\u672C\u6B21\u5DF2\u8DF3\u8FC7\u3002" } : {
22369
+ const reason = overlapping ? { code: "overlap", message: "\u5DF2\u8FBE\u5230\u8BE5\u81EA\u52A8\u5316\u7684\u5E76\u53D1\u4E0A\u9650\uFF0C\u672C\u6B21\u5DF2\u8DF3\u8FC7\u3002" } : {
22365
22370
  code: "misfire",
22366
22371
  message: "Host \u6062\u590D\u65F6\u5DF2\u8D85\u51FA\u8865\u8DD1\u7A97\u53E3\uFF0C\u672C\u6B21\u5DF2\u8DF3\u8FC7\u3002"
22367
22372
  };
@@ -22378,16 +22383,20 @@ var AutomationService = class _AutomationService {
22378
22383
  }
22379
22384
  async startQueuedRuns() {
22380
22385
  if (this.stopping) return;
22381
- const activeAutomationIds = new Set(
22382
- [...this.active.keys()].map((id) => this.runs.get(id)?.automationId).filter((id) => id !== void 0)
22383
- );
22386
+ const activeCounts = /* @__PURE__ */ new Map();
22387
+ for (const id of this.active.keys()) {
22388
+ const automationId = this.runs.get(id)?.automationId;
22389
+ if (automationId !== void 0) activeCounts.set(automationId, (activeCounts.get(automationId) ?? 0) + 1);
22390
+ }
22384
22391
  const candidates = [...this.runs.entries()].map(([, run]) => run).filter((run) => run.status === "queued" && !this.active.has(run.id)).sort(
22385
22392
  (left, right) => Date.parse(left.scheduledFor) - Date.parse(right.scheduledFor)
22386
22393
  );
22387
22394
  const queued = [];
22388
22395
  for (const run of candidates) {
22389
- if (activeAutomationIds.has(run.automationId)) continue;
22390
- activeAutomationIds.add(run.automationId);
22396
+ const count = activeCounts.get(run.automationId) ?? 0;
22397
+ const limit = this.definitions.get(run.automationId)?.maxConcurrentRuns ?? 1;
22398
+ if (count >= limit) continue;
22399
+ activeCounts.set(run.automationId, count + 1);
22391
22400
  queued.push(run);
22392
22401
  }
22393
22402
  for (const run of queued) this.startRun(run);
@@ -22756,6 +22765,12 @@ function integer2(value, label) {
22756
22765
  if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new RpcRequestError(`${label} must be an integer`);
22757
22766
  return value;
22758
22767
  }
22768
+ function positiveInteger2(value, label) {
22769
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
22770
+ throw new RpcRequestError(`${label} must be a positive integer`);
22771
+ }
22772
+ return value;
22773
+ }
22759
22774
  function toDomainSchedule(raw, timeZone2) {
22760
22775
  const schedule = record2(raw, "schedule");
22761
22776
  const kind = string4(schedule.kind, "schedule.kind");
@@ -22848,6 +22863,7 @@ async function snapshotValue(service, payload, signal) {
22848
22863
  scheduleSummary: definition.rrule,
22849
22864
  timeZone: definition.timeZone,
22850
22865
  permission: definition.permissionPreset,
22866
+ maxConcurrentRuns: definition.maxConcurrentRuns ?? 1,
22851
22867
  ...definition.nextRunAt === null ? {} : { nextRunAt: definition.nextRunAt },
22852
22868
  ...definition.lastRun === null ? {} : {
22853
22869
  lastRunAt: definition.lastRun.finishedAt ?? definition.lastRun.startedAt ?? definition.lastRun.scheduledFor,
@@ -22893,6 +22909,7 @@ function registerAutomationRpc(ctx, service) {
22893
22909
  prompt: string4(input.prompt, "input.prompt", 1e5),
22894
22910
  schedule: toDomainSchedule(input.schedule, timeZone2),
22895
22911
  permissionPreset: string4(input.permission, "input.permission"),
22912
+ ...input.maxConcurrentRuns === void 0 ? {} : { maxConcurrentRuns: positiveInteger2(input.maxConcurrentRuns, "input.maxConcurrentRuns") },
22896
22913
  ...input.workspaceId === void 0 ? {} : { workspaceId: string4(input.workspaceId, "input.workspaceId") },
22897
22914
  ...input.cwd === void 0 ? {} : { cwd: string4(input.cwd, "input.cwd") },
22898
22915
  ...input.provider === void 0 ? {} : { provider: input.provider === null ? null : string4(input.provider, "input.provider") },
@@ -22924,6 +22941,7 @@ function registerAutomationRpc(ctx, service) {
22924
22941
  prompt: string4(input.prompt, "input.prompt", 1e5),
22925
22942
  schedule: toDomainSchedule(input.schedule, timeZone2),
22926
22943
  permissionPreset: string4(input.permission, "input.permission"),
22944
+ ...input.maxConcurrentRuns === void 0 ? {} : { maxConcurrentRuns: positiveInteger2(input.maxConcurrentRuns, "input.maxConcurrentRuns") },
22927
22945
  ...input.workspaceId === void 0 ? {} : { workspaceId: string4(input.workspaceId, "input.workspaceId") },
22928
22946
  ...input.cwd === void 0 ? {} : { cwd: string4(input.cwd, "input.cwd") },
22929
22947
  ...input.provider === void 0 ? {} : { provider: input.provider === null ? null : string4(input.provider, "input.provider") },
@@ -23046,12 +23064,13 @@ function registerAutomationTools(service, agent) {
23046
23064
  kind: { type: "string", required: true, enum: ["once", "interval", "hourly", "daily", "weekly", "monthly", "custom"] },
23047
23065
  time_zone: { type: "string", required: true, description: "IANA \u65F6\u533A\uFF0C\u4F8B\u5982 Asia/Shanghai\u3002" },
23048
23066
  at: { type: "string", description: "\u4E00\u6B21\u6027\u8BA1\u5212\u7684\u5E26\u504F\u79FB ISO \u65F6\u95F4\u3002" },
23049
- every_minutes: { type: "integer", description: "\u95F4\u9694\u8BA1\u5212\u7684\u5206\u949F\u6570\uFF0C\u6700\u5C0F 5\u3002" },
23067
+ every_minutes: { type: "integer", description: "\u95F4\u9694\u8BA1\u5212\u7684\u5206\u949F\u6570\uFF0C\u6700\u5C0F 1\u3002" },
23050
23068
  minute: { type: "integer", description: "\u6BCF\u5C0F\u65F6\u8BA1\u5212\u5728\u7B2C\u51E0\u5206\u949F\u8FD0\u884C\uFF0C\u8303\u56F4 0-59\u3002" },
23051
23069
  time: { type: "string", description: "\u6BCF\u5929\u3001\u6BCF\u5468\u3001\u6BCF\u6708\u6216\u81EA\u5B9A\u4E49\u8BA1\u5212\u7684\u672C\u5730 HH:mm\u3002" },
23052
23070
  weekdays: { type: "array", items: { type: "string", enum: WEEKDAYS2 } },
23053
23071
  month_day: { type: "integer", description: "\u6BCF\u6708\u8BA1\u5212\u5728\u7B2C\u51E0\u65E5\u8FD0\u884C\uFF0C\u8303\u56F4 1-31\u3002" },
23054
23072
  every_days: { type: "integer", description: "\u81EA\u5B9A\u4E49\u8BA1\u5212\u6BCF\u9694\u51E0\u5929\u8FD0\u884C\uFF0C\u8303\u56F4 1-365\u3002" },
23073
+ max_concurrent_runs: { type: "integer", description: "\u540C\u4E00\u81EA\u52A8\u5316\u7684\u5E76\u53D1\u8FD0\u884C\u4E0A\u9650\uFF0C\u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u9ED8\u8BA4 1" },
23055
23074
  permission: { type: "string", enum: permissionNames }
23056
23075
  },
23057
23076
  output: JSON_OUTPUT,
@@ -23062,6 +23081,7 @@ function registerAutomationTools(service, agent) {
23062
23081
  name: args.name,
23063
23082
  prompt: args.prompt,
23064
23083
  schedule: scheduleFromArgs(args, (/* @__PURE__ */ new Date()).toISOString()),
23084
+ ...args.max_concurrent_runs === void 0 ? {} : { maxConcurrentRuns: args.max_concurrent_runs },
23065
23085
  ...args.permission === void 0 ? {} : { permissionPreset: args.permission }
23066
23086
  }, exec.signal);
23067
23087
  return json2({ ok: true, automation: value });
@@ -23111,6 +23131,7 @@ function registerAutomationTools(service, agent) {
23111
23131
  weekdays: { type: "array", items: { type: "string", enum: WEEKDAYS2 } },
23112
23132
  month_day: { type: "integer" },
23113
23133
  every_days: { type: "integer" },
23134
+ max_concurrent_runs: { type: "integer", description: "\u540C\u4E00\u81EA\u52A8\u5316\u7684\u5E76\u53D1\u8FD0\u884C\u4E0A\u9650\uFF0C\u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u9ED8\u8BA4 1" },
23114
23135
  permission: { type: "string", enum: permissionNames }
23115
23136
  },
23116
23137
  output: JSON_OUTPUT,
@@ -23122,6 +23143,7 @@ function registerAutomationTools(service, agent) {
23122
23143
  if (args.name !== void 0) input.name = String(args.name);
23123
23144
  if (args.prompt !== void 0) input.prompt = String(args.prompt);
23124
23145
  if (args.status !== void 0) input.status = args.status;
23146
+ if (args.max_concurrent_runs !== void 0) input.maxConcurrentRuns = args.max_concurrent_runs;
23125
23147
  if (args.permission !== void 0) input.permissionPreset = args.permission;
23126
23148
  if (args.kind !== void 0) input.schedule = scheduleFromArgs(args, (/* @__PURE__ */ new Date()).toISOString());
23127
23149
  if (Object.keys(input).length === 0) throw new Error("automation_update \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u53D8\u66F4\u5B57\u6BB5");
@@ -8,6 +8,7 @@ export interface AutomationFormState {
8
8
  readonly prompt: string;
9
9
  readonly scheduleKind: ScheduleKind;
10
10
  readonly onceAt: string;
11
+ readonly maxConcurrentRuns: string;
11
12
  readonly everyMinutes: string;
12
13
  readonly intervalAnchor: string;
13
14
  readonly time: string;
@@ -22,7 +23,7 @@ export interface AutomationFormState {
22
23
  readonly reasoningEffort: string;
23
24
  readonly skills: readonly string[];
24
25
  }
25
- export type FormErrorKey = 'form.error.name' | 'form.error.prompt' | 'form.error.once' | 'form.error.interval' | 'form.error.weekdays' | 'form.error.workspace';
26
+ export type FormErrorKey = 'form.error.maxConcurrentRuns' | 'form.error.name' | 'form.error.prompt' | 'form.error.once' | 'form.error.interval' | 'form.error.weekdays' | 'form.error.workspace';
26
27
  export declare class AutomationFormError extends Error {
27
28
  readonly key: FormErrorKey;
28
29
  constructor(key: FormErrorKey);
@@ -152,6 +152,9 @@ export declare const en: {
152
152
  readonly "form.time": "Time";
153
153
  readonly "form.days": "Weekdays";
154
154
  readonly "form.timeZone": "Time zone";
155
+ readonly "form.maxConcurrentRuns": "Concurrency";
156
+ readonly "form.maxConcurrentRunsHint": "Queued and running executions share this limit. Defaults to 1; when full, scheduled triggers are skipped and manual runs are rejected.";
157
+ readonly "form.error.maxConcurrentRuns": "Enter a positive whole number for concurrent runs.";
155
158
  readonly "form.permission": "Permission boundary";
156
159
  readonly "permission.readOnly": "Read Only";
157
160
  readonly "permission.workspaceWrite": "Workspace Write";
@@ -162,7 +165,7 @@ export declare const en: {
162
165
  readonly "form.error.name": "Enter a name.";
163
166
  readonly "form.error.prompt": "Enter a complete, standalone task prompt.";
164
167
  readonly "form.error.once": "Choose a valid future date and time.";
165
- readonly "form.error.interval": "The interval must be between 5 and 43,200 minutes.";
168
+ readonly "form.error.interval": "The interval must be between 1 and 43,200 minutes.";
166
169
  readonly "form.error.weekdays": "Select at least one weekday.";
167
170
  readonly "day.1": "Mon";
168
171
  readonly "day.2": "Tue";
@@ -74,6 +74,7 @@ export interface AutomationViewModel {
74
74
  readonly schedule: AutomationSchedule;
75
75
  readonly scheduleSummary: string;
76
76
  readonly timeZone: string;
77
+ readonly maxConcurrentRuns?: number;
77
78
  readonly permission: AutomationPermission;
78
79
  readonly workspaceId?: string;
79
80
  readonly cwd?: string;
@@ -125,6 +126,7 @@ export interface CreateAutomationInput {
125
126
  readonly prompt: string;
126
127
  readonly schedule: AutomationSchedule;
127
128
  readonly timeZone: string;
129
+ readonly maxConcurrentRuns?: number;
128
130
  readonly permission: AutomationPermission;
129
131
  readonly workspaceId: string;
130
132
  readonly cwd: string;
@@ -37,6 +37,7 @@ export interface CreateRequest {
37
37
  readonly name: string;
38
38
  readonly prompt: string;
39
39
  readonly schedule: AutomationSchedule;
40
+ readonly maxConcurrentRuns?: number;
40
41
  readonly permissionPreset?: PermissionPreset;
41
42
  readonly workspaceId?: string;
42
43
  readonly cwd?: string;
@@ -62,6 +62,7 @@ declare module '@deepseek-ai/dsh-llm' {
62
62
  export interface AutomationDefinition {
63
63
  readonly version: 1;
64
64
  readonly id: string;
65
+ readonly maxConcurrentRuns?: number;
65
66
  readonly revision: number;
66
67
  readonly name: string;
67
68
  readonly prompt: string;
@@ -123,6 +124,7 @@ export interface CreateAutomationInput {
123
124
  readonly provider?: string | null;
124
125
  readonly model?: string | null;
125
126
  readonly reasoningEffort?: string | null | undefined;
127
+ readonly maxConcurrentRuns?: number;
126
128
  readonly permissionPreset?: PermissionPreset;
127
129
  readonly createdBy: AutomationCreator;
128
130
  readonly now: string;
@@ -136,6 +138,7 @@ export interface UpdateAutomationInput {
136
138
  readonly provider?: string | null;
137
139
  readonly model?: string | null;
138
140
  readonly reasoningEffort?: string | null | undefined;
141
+ readonly maxConcurrentRuns?: number;
139
142
  readonly permissionPreset?: PermissionPreset;
140
143
  readonly workspaceId?: string;
141
144
  readonly cwd?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-automation",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "在独立 DSH Session 中按计划执行编码任务,支持 Web 设置页与 Agent 双入口管理。",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",