@monotykamary/localterm-server 2.80.3 → 2.81.0

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.
Files changed (70) hide show
  1. package/dist/agent-runner.d.ts +1 -0
  2. package/dist/agent-runner.d.ts.map +1 -1
  3. package/dist/agent-runner.js +203 -151
  4. package/dist/agent-runner.js.map +1 -1
  5. package/dist/automation-dispatcher.d.ts +27 -0
  6. package/dist/automation-dispatcher.d.ts.map +1 -0
  7. package/dist/automation-dispatcher.js +195 -0
  8. package/dist/automation-dispatcher.js.map +1 -0
  9. package/dist/automation-run-tracker.d.ts +1 -1
  10. package/dist/automation-run-tracker.d.ts.map +1 -1
  11. package/dist/automation-run-tracker.js +2 -2
  12. package/dist/automation-run-tracker.js.map +1 -1
  13. package/dist/automation-scheduler.d.ts +7 -4
  14. package/dist/automation-scheduler.d.ts.map +1 -1
  15. package/dist/automation-scheduler.js +59 -39
  16. package/dist/automation-scheduler.js.map +1 -1
  17. package/dist/automation-store.d.ts +1 -0
  18. package/dist/automation-store.d.ts.map +1 -1
  19. package/dist/automation-store.js +59 -23
  20. package/dist/automation-store.js.map +1 -1
  21. package/dist/constants.d.ts +12 -1
  22. package/dist/constants.d.ts.map +1 -1
  23. package/dist/constants.js +13 -1
  24. package/dist/constants.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +195 -213
  27. package/dist/index.js.map +1 -1
  28. package/dist/pi-rpc-client.d.ts +9 -3
  29. package/dist/pi-rpc-client.d.ts.map +1 -1
  30. package/dist/pi-rpc-client.js +133 -32
  31. package/dist/pi-rpc-client.js.map +1 -1
  32. package/dist/protocol.d.ts +2 -0
  33. package/dist/protocol.d.ts.map +1 -1
  34. package/dist/protocol.js +2 -0
  35. package/dist/protocol.js.map +1 -1
  36. package/dist/schemas.d.ts +882 -1
  37. package/dist/schemas.d.ts.map +1 -1
  38. package/dist/schemas.js +57 -8
  39. package/dist/schemas.js.map +1 -1
  40. package/dist/utils/compile-schedule.d.ts +1 -1
  41. package/dist/utils/compile-schedule.d.ts.map +1 -1
  42. package/dist/utils/compile-schedule.js +7 -6
  43. package/dist/utils/compile-schedule.js.map +1 -1
  44. package/dist/utils/compute-next-automation-run-at.d.ts.map +1 -1
  45. package/dist/utils/compute-next-automation-run-at.js +3 -18
  46. package/dist/utils/compute-next-automation-run-at.js.map +1 -1
  47. package/dist/utils/is-active-automation-run.d.ts +3 -0
  48. package/dist/utils/is-active-automation-run.d.ts.map +1 -0
  49. package/dist/utils/is-active-automation-run.js +2 -0
  50. package/dist/utils/is-active-automation-run.js.map +1 -0
  51. package/dist/utils/is-valid-time-zone.d.ts +2 -0
  52. package/dist/utils/is-valid-time-zone.d.ts.map +1 -0
  53. package/dist/utils/is-valid-time-zone.js +14 -0
  54. package/dist/utils/is-valid-time-zone.js.map +1 -0
  55. package/dist/utils/next-schedule-occurrence.d.ts +4 -0
  56. package/dist/utils/next-schedule-occurrence.d.ts.map +1 -0
  57. package/dist/utils/next-schedule-occurrence.js +201 -0
  58. package/dist/utils/next-schedule-occurrence.js.map +1 -0
  59. package/dist/utils/reconcile-downtime.d.ts.map +1 -1
  60. package/dist/utils/reconcile-downtime.js +4 -24
  61. package/dist/utils/reconcile-downtime.js.map +1 -1
  62. package/dist/utils/trim-automation-runs.d.ts +3 -0
  63. package/dist/utils/trim-automation-runs.d.ts.map +1 -0
  64. package/dist/utils/trim-automation-runs.js +7 -0
  65. package/dist/utils/trim-automation-runs.js.map +1 -0
  66. package/package.json +1 -1
  67. package/vendor/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  68. package/vendor/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  69. package/vendor/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  70. package/vendor/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
@@ -0,0 +1,195 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { MAX_AUTOMATION_CONCURRENT_RUNS } from "./constants.js";
3
+ import { isActiveAutomationRun } from "./utils/is-active-automation-run.js";
4
+ import { trimAutomationRuns } from "./utils/trim-automation-runs.js";
5
+ export class AutomationDispatcher {
6
+ options;
7
+ exclusive = new Set();
8
+ disposed = false;
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ request(id, trigger, scheduledFor = this.now(), skippedReason) {
13
+ if (this.disposed)
14
+ return null;
15
+ const automation = this.options.store.get(id);
16
+ if (!automation)
17
+ return null;
18
+ if (trigger !== "manual" && (!automation.enabled || automation.lifecycle === "finished"))
19
+ return null;
20
+ if (trigger === "schedule" && scheduledFor <= (automation.lastScheduledAt ?? -1)) {
21
+ return (automation.runs.find((run) => run.trigger === trigger && run.scheduledFor === scheduledFor)
22
+ ?.runId ?? null);
23
+ }
24
+ const active = automation.runs.filter(isActiveAutomationRun);
25
+ const thread = automation.runner.kind === "agent" && automation.runner.sessionMode === "thread";
26
+ const policy = thread && automation.concurrencyPolicy === "allow"
27
+ ? "queue-latest"
28
+ : (automation.concurrencyPolicy ?? "skip");
29
+ const busy = active.length > 0 || this.exclusive.has(id);
30
+ const reason = skippedReason ??
31
+ (busy && policy === "skip"
32
+ ? "overlap"
33
+ : policy === "allow" && active.length >= MAX_AUTOMATION_CONCURRENT_RUNS
34
+ ? "capacity"
35
+ : undefined);
36
+ const now = this.now();
37
+ const run = {
38
+ runId: randomUUID(),
39
+ scheduledFor,
40
+ startedAt: null,
41
+ finishedAt: reason ? now : null,
42
+ status: reason ? "skipped" : "queued",
43
+ reason,
44
+ exitCode: null,
45
+ trigger,
46
+ countsTowardLimit: false,
47
+ findings: null,
48
+ changedFiles: [],
49
+ unread: false,
50
+ log: null,
51
+ execution: {
52
+ cwd: automation.cwd,
53
+ runner: structuredClone(automation.runner),
54
+ requestedSecrets: [...automation.requestedSecrets],
55
+ redactOutput: automation.redactOutput ?? false,
56
+ closeOnFinish: automation.closeOnFinish,
57
+ },
58
+ };
59
+ this.options.store.transact(id, (current) => ({
60
+ ...current,
61
+ ...(trigger === "schedule" ? { lastScheduledAt: scheduledFor } : {}),
62
+ runs: trimAutomationRuns([
63
+ run,
64
+ ...current.runs.map((previous) => !reason && policy === "queue-latest" && previous.status === "queued"
65
+ ? { ...previous, status: "skipped", reason: "superseded", finishedAt: now }
66
+ : previous),
67
+ ]),
68
+ }));
69
+ this.options.changed();
70
+ if (!reason)
71
+ this.drain(id);
72
+ return run.runId;
73
+ }
74
+ // Only accepted but unstarted work is replayable. Once launch was committed,
75
+ // external side effects may have happened; never rerun an interrupted command.
76
+ recover() {
77
+ const now = this.now();
78
+ for (const automation of this.options.store.list()) {
79
+ if (!automation.runs.some((run) => run.status === "running" || run.status === "launched"))
80
+ continue;
81
+ this.options.store.transact(automation.id, (current) => ({
82
+ ...current,
83
+ runs: current.runs.map((run) => run.status === "running" || run.status === "launched"
84
+ ? { ...run, status: "interrupted", reason: "restart", finishedAt: now }
85
+ : run),
86
+ }));
87
+ }
88
+ this.options.changed();
89
+ for (const automation of this.options.store.list())
90
+ this.drain(automation.id);
91
+ }
92
+ drain(id) {
93
+ if (this.disposed || this.exclusive.has(id))
94
+ return;
95
+ let automation = this.options.store.get(id);
96
+ if (!automation)
97
+ return;
98
+ for (const pending of [...automation.runs].reverse()) {
99
+ if (pending.status !== "queued")
100
+ continue;
101
+ automation = this.options.store.get(id);
102
+ if (!automation)
103
+ return;
104
+ const run = automation.runs.find((entry) => entry.runId === pending.runId);
105
+ if (!run || run.status !== "queued")
106
+ continue;
107
+ if (run.trigger !== "manual" &&
108
+ (!automation.enabled || automation.lifecycle === "finished")) {
109
+ this.finish(id, run.runId, "cancelled", automation.enabled ? "limit" : "disabled");
110
+ continue;
111
+ }
112
+ const active = automation.runs.filter((entry) => entry.status === "running" || entry.status === "launched");
113
+ const currentRunner = automation.runner;
114
+ const runner = run.execution?.runner ?? currentRunner;
115
+ const sharedThread = (runner.kind === "agent" && runner.sessionMode === "thread") ||
116
+ active.some((entry) => {
117
+ const activeRunner = entry.execution?.runner ?? currentRunner;
118
+ return activeRunner.kind === "agent" && activeRunner.sessionMode === "thread";
119
+ });
120
+ if (active.length >= MAX_AUTOMATION_CONCURRENT_RUNS ||
121
+ (active.length > 0 && (sharedThread || automation.concurrencyPolicy !== "allow")))
122
+ continue;
123
+ const now = this.now();
124
+ const counts = run.trigger !== "manual";
125
+ const status = runner.kind === "agent" ? "running" : "launched";
126
+ const updated = this.options.store.transact(id, (current) => {
127
+ const runCount = current.runCount + (counts ? 1 : 0);
128
+ return {
129
+ ...current,
130
+ runCount,
131
+ lifecycle: current.limit.kind === "count" && runCount >= current.limit.max
132
+ ? "finished"
133
+ : current.lifecycle,
134
+ runs: current.runs.map((entry) => entry.runId === run.runId
135
+ ? { ...entry, status, startedAt: now, countsTowardLimit: counts }
136
+ : entry),
137
+ };
138
+ });
139
+ if (!updated)
140
+ return;
141
+ const started = updated.runs.find((entry) => entry.runId === run.runId);
142
+ if (!started)
143
+ continue;
144
+ this.options.changed();
145
+ try {
146
+ const result = this.options.launch({ ...updated, ...started.execution }, started);
147
+ void Promise.resolve(result).catch(() => this.launchFailed(id, run.runId));
148
+ }
149
+ catch {
150
+ this.launchFailed(id, run.runId);
151
+ }
152
+ }
153
+ }
154
+ cancel(id, runId) {
155
+ const run = this.options.store.get(id)?.runs.find((entry) => entry.runId === runId);
156
+ if (run?.status !== "queued")
157
+ return false;
158
+ this.finish(id, runId, "cancelled", "cancelled");
159
+ return true;
160
+ }
161
+ isBusy(id) {
162
+ return (this.exclusive.has(id) ||
163
+ (this.options.store.get(id)?.runs.some(isActiveAutomationRun) ?? false));
164
+ }
165
+ acquireExclusive(id) {
166
+ if (this.isBusy(id))
167
+ return false;
168
+ this.exclusive.add(id);
169
+ return true;
170
+ }
171
+ releaseExclusive(id) {
172
+ this.exclusive.delete(id);
173
+ this.drain(id);
174
+ }
175
+ dispose() {
176
+ this.disposed = true;
177
+ }
178
+ finish(id, runId, status, reason) {
179
+ this.options.store.updateRun(id, runId, { status, reason, finishedAt: this.now() });
180
+ this.options.changed();
181
+ }
182
+ launchFailed(id, runId) {
183
+ if (this.disposed)
184
+ return;
185
+ const run = this.options.store.get(id)?.runs.find((entry) => entry.runId === runId);
186
+ if (!run || !isActiveAutomationRun(run))
187
+ return;
188
+ this.finish(id, runId, "failed", "launch-failed");
189
+ this.drain(id);
190
+ }
191
+ now() {
192
+ return this.options.now?.() ?? Date.now();
193
+ }
194
+ }
195
+ //# sourceMappingURL=automation-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automation-dispatcher.js","sourceRoot":"","sources":["../src/automation-dispatcher.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { Automation, PendingAutomationRun } from "./types.js";
2
2
  export declare class AutomationRunTracker {
3
3
  private readonly pendingRuns;
4
- create(automation: Automation, now?: number): PendingAutomationRun;
4
+ create(automation: Automation, now?: number, runId?: string): PendingAutomationRun;
5
5
  claim(runId: string): PendingAutomationRun | null;
6
6
  setEnv(runId: string, env: Record<string, string>): void;
7
7
  setRedactionValues(runId: string, values: readonly string[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"automation-run-tracker.d.ts","sourceRoot":"","sources":["../src/automation-run-tracker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEnE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2C;IAEvE,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,GAAE,MAAmB,GAAG,oBAAoB,CAU7E;IAED,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAKhD;IAKD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAIvD;IAOD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIjE;IAED,YAAY,CAAC,GAAG,GAAE,MAAmB,GAAG,oBAAoB,EAAE,CAS7D;IAED,IAAI,IAAI,MAAM,CAEb;CACF"}
1
+ {"version":3,"file":"automation-run-tracker.d.ts","sourceRoot":"","sources":["../src/automation-run-tracker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEnE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2C;IAEvE,MAAM,CACJ,UAAU,EAAE,UAAU,EACtB,GAAG,GAAE,MAAmB,EACxB,KAAK,GAAE,MAAqB,GAC3B,oBAAoB,CAUtB;IAED,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAKhD;IAKD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAIvD;IAOD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIjE;IAED,YAAY,CAAC,GAAG,GAAE,MAAmB,GAAG,oBAAoB,EAAE,CAS7D;IAED,IAAI,IAAI,MAAM,CAEb;CACF"}
@@ -2,9 +2,9 @@ import { randomUUID } from "node:crypto";
2
2
  import { AUTOMATION_PENDING_RUN_EXPIRY_MS } from "./constants.js";
3
3
  export class AutomationRunTracker {
4
4
  pendingRuns = new Map();
5
- create(automation, now = Date.now()) {
5
+ create(automation, now = Date.now(), runId = randomUUID()) {
6
6
  const run = {
7
- runId: randomUUID(),
7
+ runId,
8
8
  automationId: automation.id,
9
9
  cwd: automation.cwd,
10
10
  runner: automation.runner,
@@ -1 +1 @@
1
- {"version":3,"file":"automation-run-tracker.js","sourceRoot":"","sources":["../src/automation-run-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gCAAgC,EAAE,MAAM,gBAAgB,CAAC;AAGlE,MAAM,OAAO,oBAAoB;IACd,WAAW,GAAG,IAAI,GAAG,EAAgC,CAAC;IAEvE,MAAM,CAAC,UAAsB,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;QACrD,MAAM,GAAG,GAAyB;YAChC,KAAK,EAAE,UAAU,EAAE;YACnB,YAAY,EAAE,UAAU,CAAC,EAAE;YAC3B,GAAG,EAAE,UAAU,CAAC,GAAG;YACnB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,SAAS,EAAE,GAAG;SACf,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACrC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,KAAa;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,sEAAsE;IACtE,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,CAAC,KAAa,EAAE,GAA2B;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,0EAA0E;IAC1E,uCAAuC;IACvC,kBAAkB,CAAC,KAAa,EAAE,MAAyB;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,GAAG,CAAC,eAAe,GAAG,MAAM,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;QACnC,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,IAAI,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,gCAAgC;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC/B,CAAC;CACF"}
1
+ {"version":3,"file":"automation-run-tracker.js","sourceRoot":"","sources":["../src/automation-run-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gCAAgC,EAAE,MAAM,gBAAgB,CAAC;AAGlE,MAAM,OAAO,oBAAoB;IACd,WAAW,GAAG,IAAI,GAAG,EAAgC,CAAC;IAEvE,MAAM,CACJ,UAAsB,EACtB,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE,EACxB,KAAK,GAAW,UAAU,EAAE;QAE5B,MAAM,GAAG,GAAyB;YAChC,KAAK;YACL,YAAY,EAAE,UAAU,CAAC,EAAE;YAC3B,GAAG,EAAE,UAAU,CAAC,GAAG;YACnB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,SAAS,EAAE,GAAG;SACf,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACrC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,KAAa;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,sEAAsE;IACtE,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,CAAC,KAAa,EAAE,GAA2B;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,0EAA0E;IAC1E,uCAAuC;IACvC,kBAAkB,CAAC,KAAa,EAAE,MAAyB;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,GAAG,CAAC,eAAe,GAAG,MAAM,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;QACnC,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,IAAI,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,gCAAgC;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC/B,CAAC;CACF"}
@@ -2,16 +2,19 @@ import { EventEmitter } from "node:events";
2
2
  import type { AutomationStore } from "./automation-store.js";
3
3
  import type { Automation } from "./types.js";
4
4
  interface AutomationSchedulerEvents {
5
- due: [automation: Automation];
6
- tick: [now: Date];
5
+ due: [automation: Automation, scheduledFor: number];
6
+ skipped: [automation: Automation, scheduledFor: number];
7
+ tick: [now: Date, healthy: boolean];
8
+ fault: [error: unknown];
7
9
  }
8
10
  export declare class AutomationScheduler extends EventEmitter<AutomationSchedulerEvents> {
9
11
  private readonly store;
10
12
  private tickTimer;
11
13
  private disposed;
12
- private readonly lastFiredMinuteByAutomationId;
14
+ private lastTickAt;
13
15
  constructor(store: AutomationStore);
14
- start(): void;
16
+ start(lastAliveAt?: number | null): void;
17
+ refresh(): void;
15
18
  dispose(): void;
16
19
  runTick(now?: Date): void;
17
20
  private scheduleNextTick;
@@ -1 +1 @@
1
- {"version":3,"file":"automation-scheduler.d.ts","sourceRoot":"","sources":["../src/automation-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,UAAU,yBAAyB;IACjC,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;CACnB;AAED,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IAKlE,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJlC,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAA6B;IAE3E,YAA6B,KAAK,EAAE,eAAe,EAElD;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,IAAI,IAAI,CAOd;IAED,OAAO,CAAC,GAAG,GAAE,IAAiB,GAAG,IAAI,CAyBpC;IAED,OAAO,CAAC,gBAAgB;CAWzB"}
1
+ {"version":3,"file":"automation-scheduler.d.ts","sourceRoot":"","sources":["../src/automation-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,UAAU,yBAAyB;IACjC,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IACpD,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACpC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;CACzB;AAED,qBAAa,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IAKlE,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJlC,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAuB;IAEzC,YAA6B,KAAK,EAAE,eAAe,EAElD;IAED,KAAK,CAAC,WAAW,GAAE,MAAM,GAAG,IAAW,GAAG,IAAI,CAK7C;IAED,OAAO,IAAI,IAAI,CAKd;IAED,OAAO,IAAI,IAAI,CAKd;IAED,OAAO,CAAC,GAAG,GAAE,IAAiB,GAAG,IAAI,CAkCpC;IAED,OAAO,CAAC,gBAAgB;CAwBzB"}
@@ -1,72 +1,92 @@
1
1
  import { EventEmitter } from "node:events";
2
- import { AUTOMATION_TICK_ALIGNMENT_DELAY_MS, MS_PER_MINUTE } from "./constants.js";
3
- import { cronMatchesDate, parseCronExpression } from "./cron-expression.js";
4
- import { compileScheduleAll } from "./utils/compile-schedule.js";
2
+ import { AUTOMATION_RECONCILE_MIN_DOWNTIME_MS, AUTOMATION_TICK_ALIGNMENT_DELAY_MS, MS_PER_MINUTE, } from "./constants.js";
3
+ import { computeNextAutomationRunAt } from "./utils/compute-next-automation-run-at.js";
4
+ import { enumerateMissedOccurrences } from "./utils/reconcile-downtime.js";
5
5
  export class AutomationScheduler extends EventEmitter {
6
6
  store;
7
7
  tickTimer = null;
8
8
  disposed = false;
9
- lastFiredMinuteByAutomationId = new Map();
9
+ lastTickAt = null;
10
10
  constructor(store) {
11
11
  super();
12
12
  this.store = store;
13
13
  }
14
- start() {
14
+ start(lastAliveAt = null) {
15
15
  if (this.disposed || this.tickTimer !== null)
16
16
  return;
17
+ this.lastTickAt = lastAliveAt;
18
+ this.runTick();
19
+ this.scheduleNextTick();
20
+ }
21
+ refresh() {
22
+ if (this.disposed)
23
+ return;
24
+ if (this.tickTimer !== null)
25
+ clearTimeout(this.tickTimer);
26
+ this.tickTimer = null;
17
27
  this.scheduleNextTick();
18
28
  }
19
29
  dispose() {
20
30
  this.disposed = true;
21
- if (this.tickTimer !== null) {
31
+ if (this.tickTimer !== null)
22
32
  clearTimeout(this.tickTimer);
23
- this.tickTimer = null;
24
- }
33
+ this.tickTimer = null;
25
34
  this.removeAllListeners();
26
35
  }
27
36
  runTick(now = new Date()) {
28
37
  if (this.disposed)
29
38
  return;
30
- const minuteKey = Math.floor(now.getTime() / MS_PER_MINUTE);
31
- const automations = this.store.list();
32
- for (const automation of automations) {
33
- if (!automation.enabled)
39
+ const at = now.getTime();
40
+ const since = this.lastTickAt ?? Math.floor(at / MS_PER_MINUTE) * MS_PER_MINUTE - 1;
41
+ const gap = this.lastTickAt !== null && at - since >= AUTOMATION_RECONCILE_MIN_DOWNTIME_MS;
42
+ let healthy = true;
43
+ for (const automation of this.store.list()) {
44
+ if (!automation.enabled ||
45
+ automation.lifecycle === "finished" ||
46
+ automation.trigger.kind !== "schedule")
34
47
  continue;
35
- if (automation.lifecycle === "finished")
36
- continue;
37
- // Watch and event triggers are event-driven (FolderWatchManager and
38
- // SessionEventManager own them); the minute ticker only fires time-based
39
- // schedules.
40
- if (automation.trigger.kind !== "schedule")
41
- continue;
42
- const matched = compileScheduleAll(automation.trigger.schedule).some((expression) => {
43
- const parsed = parseCronExpression(expression);
44
- return parsed !== null && cronMatchesDate(parsed, now);
45
- });
46
- if (!matched)
47
- continue;
48
- if (this.lastFiredMinuteByAutomationId.get(automation.id) === minuteKey)
49
- continue;
50
- this.lastFiredMinuteByAutomationId.set(automation.id, minuteKey);
51
- this.emit("due", automation);
52
- }
53
- const liveIds = new Set(automations.map((automation) => automation.id));
54
- for (const trackedId of [...this.lastFiredMinuteByAutomationId.keys()]) {
55
- if (!liveIds.has(trackedId))
56
- this.lastFiredMinuteByAutomationId.delete(trackedId);
48
+ try {
49
+ const from = Math.max(since, automation.createdAt, automation.lastScheduledAt ?? -1);
50
+ const occurrences = enumerateMissedOccurrences(automation, from, at + 1);
51
+ const latest = occurrences.at(-1);
52
+ for (const scheduledFor of occurrences) {
53
+ const shouldRun = scheduledFor === latest &&
54
+ (!gap ||
55
+ automation.missedRunPolicy === "run-latest" ||
56
+ at - scheduledFor < MS_PER_MINUTE);
57
+ this.emit(shouldRun ? "due" : "skipped", automation, scheduledFor);
58
+ }
59
+ }
60
+ catch (error) {
61
+ healthy = false;
62
+ this.emit("fault", error);
63
+ }
57
64
  }
58
- this.emit("tick", now);
65
+ // If persistence failed, retry the window; the durable occurrence watermark
66
+ // deduplicates automations whose decisions already committed.
67
+ if (healthy)
68
+ this.lastTickAt = at;
69
+ this.emit("tick", now, healthy);
59
70
  }
60
71
  scheduleNextTick() {
61
72
  if (this.disposed)
62
73
  return;
63
74
  const now = Date.now();
64
- const delayMs = MS_PER_MINUTE - (now % MS_PER_MINUTE) + AUTOMATION_TICK_ALIGNMENT_DELAY_MS;
75
+ let nextAt = now + MS_PER_MINUTE - (now % MS_PER_MINUTE);
76
+ for (const automation of this.store.list()) {
77
+ const next = computeNextAutomationRunAt(automation, new Date(now));
78
+ if (next !== null)
79
+ nextAt = Math.min(nextAt, next);
80
+ }
65
81
  this.tickTimer = setTimeout(() => {
66
82
  this.tickTimer = null;
67
- this.runTick();
68
- this.scheduleNextTick();
69
- }, delayMs);
83
+ try {
84
+ this.runTick();
85
+ }
86
+ finally {
87
+ this.scheduleNextTick();
88
+ }
89
+ }, Math.max(AUTOMATION_TICK_ALIGNMENT_DELAY_MS, nextAt - now + AUTOMATION_TICK_ALIGNMENT_DELAY_MS));
70
90
  this.tickTimer.unref?.();
71
91
  }
72
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"automation-scheduler.js","sourceRoot":"","sources":["../src/automation-scheduler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"automation-scheduler.js","sourceRoot":"","sources":["../src/automation-scheduler.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ export declare class AutomationStore {
11
11
  update(id: string, patch: UpdateAutomationInput): Automation | null;
12
12
  remove(id: string): boolean;
13
13
  removeSecretFromAll(secretName: string): boolean;
14
+ transact(id: string, update: (automation: Automation) => Automation): Automation | null;
14
15
  appendRun(id: string, record: AutomationRunRecord): Automation | null;
15
16
  updateRun(id: string, runId: string, patch: Partial<AutomationRunRecord>): Automation | null;
16
17
  markRunRead(id: string, runId: string): Automation | null;
@@ -1 +1 @@
1
- {"version":3,"file":"automation-store.d.ts","sourceRoot":"","sources":["../src/automation-store.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EAKnB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAkIpB,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,WAAW,CAAoB;IAEvC,YAA6B,QAAQ,EAAE,MAAM,EAE5C;IAED,IAAI,IAAI,UAAU,EAAE,CAEnB;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEjC;IAKD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOnD;IAED,IAAI,IAAI,MAAM,CAEb;IAED,MAAM,CAAC,KAAK,EAAE,qBAAqB,GAAG,UAAU,CAsB/C;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,UAAU,GAAG,IAAI,CA4BlE;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAM1B;IASD,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAgB/C;IAGD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,UAAU,GAAG,IAAI,CASpE;IAID,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,UAAU,GAAG,IAAI,CAa3F;IAKD,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAcxD;IAKD,eAAe,IAAI,OAAO,CAezB;IAMD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CASvC;IAMD,YAAY,IAAI,OAAO,CAOtB;IAID,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAa/C;IAID,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,UAAQ,GAAG,UAAU,GAAG,IAAI,CAezD;IASD,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,IAAI;IAmEZ,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,OAAO;CAOhB"}
1
+ {"version":3,"file":"automation-store.d.ts","sourceRoot":"","sources":["../src/automation-store.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EAKnB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAoIpB,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,WAAW,CAAoB;IAEvC,YAA6B,QAAQ,EAAE,MAAM,EAE5C;IAED,IAAI,IAAI,UAAU,EAAE,CAEnB;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEjC;IAKD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOnD;IAED,IAAI,IAAI,MAAM,CAEb;IAED,MAAM,CAAC,KAAK,EAAE,qBAAqB,GAAG,UAAU,CAyB/C;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,UAAU,GAAG,IAAI,CAoClE;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAM1B;IASD,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAgB/C;IAID,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,UAAU,GAAG,UAAU,GAAG,IAAI,CAetF;IAGD,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,UAAU,GAAG,IAAI,CASpE;IAID,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,UAAU,GAAG,IAAI,CAQ3F;IAKD,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAcxD;IAKD,eAAe,IAAI,OAAO,CAezB;IAMD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CASvC;IAMD,YAAY,IAAI,OAAO,CAStB;IAID,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAa/C;IAID,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,UAAQ,GAAG,UAAU,GAAG,IAAI,CAgBzD;IASD,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,IAAI;IA0EZ,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,OAAO;CAOhB"}
@@ -2,10 +2,12 @@ import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { AUTOMATION_RUN_HISTORY_CAP, AUTOMATIONS_FILE_VERSION, MAX_AUTOMATION_FINDINGS_LENGTH, MAX_AUTOMATION_LOG_LENGTH, MAX_AUTOMATION_TOOL_RESULT_LENGTH, } from "./constants.js";
5
- import { automationsFileSchema, automationsFileV1Schema, automationsFileV2Schema, automationsFileV3Schema, } from "./schemas.js";
5
+ import { automationsFileSchema, automationsFileV1Schema, automationsFileV2Schema, automationsFileV3Schema, automationsFileV4Schema, } from "./schemas.js";
6
6
  import { normalizeScheduleInput } from "./utils/compile-schedule.js";
7
7
  import { generateWebhookId } from "./utils/generate-webhook-id.js";
8
8
  import { normalizeTriggerInput } from "./utils/normalize-trigger.js";
9
+ import { isActiveAutomationRun } from "./utils/is-active-automation-run.js";
10
+ import { trimAutomationRuns } from "./utils/trim-automation-runs.js";
9
11
  // A run is terminal once it has a definitive outcome; only those carry a
10
12
  // finishedAt when migrated from a v1 lastRun.
11
13
  const V1_TERMINAL_STATUSES = new Set(["completed", "failed", "missed"]);
@@ -163,6 +165,9 @@ export class AutomationStore {
163
165
  closeOnFinish: input.closeOnFinish ?? false,
164
166
  requestedSecrets: input.requestedSecrets ?? [],
165
167
  redactOutput: input.redactOutput ?? false,
168
+ timezone: input.timezone,
169
+ concurrencyPolicy: input.concurrencyPolicy ?? "skip",
170
+ missedRunPolicy: input.missedRunPolicy ?? "skip",
166
171
  runCount: 0,
167
172
  lifecycle: "active",
168
173
  runs: [],
@@ -194,6 +199,14 @@ export class AutomationStore {
194
199
  ...(patch.closeOnFinish !== undefined ? { closeOnFinish: patch.closeOnFinish } : {}),
195
200
  ...(patch.requestedSecrets !== undefined ? { requestedSecrets: patch.requestedSecrets } : {}),
196
201
  ...(patch.redactOutput !== undefined ? { redactOutput: patch.redactOutput } : {}),
202
+ ...(patch.timezone !== undefined ? { timezone: patch.timezone } : {}),
203
+ ...(patch.concurrencyPolicy !== undefined
204
+ ? { concurrencyPolicy: patch.concurrencyPolicy }
205
+ : {}),
206
+ ...(patch.missedRunPolicy !== undefined ? { missedRunPolicy: patch.missedRunPolicy } : {}),
207
+ ...(patch.trigger !== undefined || patch.timezone !== undefined || patch.enabled === true
208
+ ? { lastScheduledAt: Date.now() }
209
+ : {}),
197
210
  limit,
198
211
  lifecycle,
199
212
  updatedAt: Date.now(),
@@ -235,13 +248,31 @@ export class AutomationStore {
235
248
  }
236
249
  return changed;
237
250
  }
238
- // Push a new run onto the newest-first history ring, trimming to the cap.
251
+ // One atomic file replacement commits a queue decision, its occurrence watermark,
252
+ // and run-count changes together. A failed write must not reserve work in memory.
253
+ transact(id, update) {
254
+ const index = this.automations.findIndex((automation) => automation.id === id);
255
+ if (index === -1)
256
+ return null;
257
+ const previous = this.automations;
258
+ const updated = update(previous[index]);
259
+ this.automations = previous.map((automation, position) => position === index ? updated : automation);
260
+ try {
261
+ this.persist();
262
+ }
263
+ catch (error) {
264
+ this.automations = previous;
265
+ throw error;
266
+ }
267
+ return updated;
268
+ }
269
+ // Push a new run onto the newest-first history ring, preserving active work.
239
270
  appendRun(id, record) {
240
271
  const index = this.automations.findIndex((automation) => automation.id === id);
241
272
  if (index === -1)
242
273
  return null;
243
274
  const current = this.automations[index];
244
- const runs = [record, ...current.runs].slice(0, AUTOMATION_RUN_HISTORY_CAP);
275
+ const runs = trimAutomationRuns([record, ...current.runs]);
245
276
  const updated = { ...current, runs };
246
277
  this.automations[index] = updated;
247
278
  this.persist();
@@ -250,18 +281,12 @@ export class AutomationStore {
250
281
  // Advance an existing run in place (launched -> running -> completed/failed/
251
282
  // missed). No-op (null) if the run has already aged out of the ring.
252
283
  updateRun(id, runId, patch) {
253
- const index = this.automations.findIndex((automation) => automation.id === id);
254
- if (index === -1)
284
+ if (!this.get(id)?.runs.some((run) => run.runId === runId))
255
285
  return null;
256
- const current = this.automations[index];
257
- const runIndex = current.runs.findIndex((run) => run.runId === runId);
258
- if (runIndex === -1)
259
- return null;
260
- const runs = current.runs.map((run, position) => position === runIndex ? { ...run, ...patch } : run);
261
- const updated = { ...current, runs };
262
- this.automations[index] = updated;
263
- this.persist();
264
- return updated;
286
+ return this.transact(id, (current) => ({
287
+ ...current,
288
+ runs: trimAutomationRuns(current.runs.map((run) => (run.runId === runId ? { ...run, ...patch } : run))),
289
+ }));
265
290
  }
266
291
  // Clear a single run's Triage unread flag (the user opened it). No-op if the
267
292
  // run is gone or already read — returns the current automation without
@@ -313,7 +338,7 @@ export class AutomationStore {
313
338
  const current = this.automations[index];
314
339
  if (current.runs.length === 0)
315
340
  return current;
316
- const updated = { ...current, runs: [] };
341
+ const updated = { ...current, runs: current.runs.filter(isActiveAutomationRun) };
317
342
  this.automations[index] = updated;
318
343
  this.persist();
319
344
  return updated;
@@ -325,7 +350,9 @@ export class AutomationStore {
325
350
  clearAllRuns() {
326
351
  if (this.automations.every((automation) => automation.runs.length === 0))
327
352
  return false;
328
- this.automations = this.automations.map((automation) => automation.runs.length === 0 ? automation : { ...automation, runs: [] });
353
+ this.automations = this.automations.map((automation) => automation.runs.length === 0
354
+ ? automation
355
+ : { ...automation, runs: automation.runs.filter(isActiveAutomationRun) });
329
356
  this.persist();
330
357
  return true;
331
358
  }
@@ -357,7 +384,8 @@ export class AutomationStore {
357
384
  runCount: 0,
358
385
  lifecycle: "active",
359
386
  enabled: true,
360
- runs: clearHistory ? [] : current.runs,
387
+ runs: clearHistory ? current.runs.filter(isActiveAutomationRun) : current.runs,
388
+ lastScheduledAt: Date.now(),
361
389
  updatedAt: Date.now(),
362
390
  };
363
391
  this.automations[index] = updated;
@@ -398,12 +426,12 @@ export class AutomationStore {
398
426
  console.warn(`automations file invalid; starting with an empty list (${this.filePath})`);
399
427
  return;
400
428
  }
401
- // Fast path: already v4. Trim any runs left above the cap by an older
429
+ // Fast path: current file version. Trim any runs left above the cap by an older
402
430
  // (higher) cap and persist, so lowering the trim cap never strands a
403
431
  // user's automations behind a schema rejection.
404
- const v4 = automationsFileSchema.safeParse(json);
405
- if (v4.success) {
406
- this.automations = v4.data.automations;
432
+ const current = automationsFileSchema.safeParse(json);
433
+ if (current.success) {
434
+ this.automations = current.data.automations;
407
435
  if (this.trimRunsToCap())
408
436
  this.persist();
409
437
  return;
@@ -421,6 +449,13 @@ export class AutomationStore {
421
449
  return;
422
450
  }
423
451
  }
452
+ const legacyV4 = automationsFileV4Schema.safeParse(json);
453
+ if (legacyV4.success) {
454
+ this.automations = legacyV4.data.automations;
455
+ this.trimRunsToCap();
456
+ this.persist();
457
+ return;
458
+ }
424
459
  // Migrate v3 -> v4 (wrap the bare command in a shell runner), then persist
425
460
  // so later loads hit the fast path.
426
461
  const v3 = automationsFileV3Schema.safeParse(json);
@@ -455,8 +490,9 @@ export class AutomationStore {
455
490
  let changed = false;
456
491
  for (const automation of this.automations) {
457
492
  if (automation.runs.length > AUTOMATION_RUN_HISTORY_CAP) {
458
- automation.runs = automation.runs.slice(0, AUTOMATION_RUN_HISTORY_CAP);
459
- changed = true;
493
+ const trimmed = trimAutomationRuns(automation.runs);
494
+ changed ||= trimmed.length !== automation.runs.length;
495
+ automation.runs = trimmed;
460
496
  }
461
497
  }
462
498
  return changed;