@monotykamary/localterm-server 1.21.0 → 1.23.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 (55) hide show
  1. package/dist/automation-scheduler.d.ts.map +1 -1
  2. package/dist/automation-scheduler.js +5 -1
  3. package/dist/automation-scheduler.js.map +1 -1
  4. package/dist/automation-store.d.ts.map +1 -1
  5. package/dist/automation-store.js +29 -12
  6. package/dist/automation-store.js.map +1 -1
  7. package/dist/caffeinate-manager.d.ts +42 -0
  8. package/dist/caffeinate-manager.d.ts.map +1 -0
  9. package/dist/caffeinate-manager.js +158 -0
  10. package/dist/caffeinate-manager.js.map +1 -0
  11. package/dist/caffeinate-preferences-store.d.ts +13 -0
  12. package/dist/caffeinate-preferences-store.d.ts.map +1 -0
  13. package/dist/caffeinate-preferences-store.js +98 -0
  14. package/dist/caffeinate-preferences-store.js.map +1 -0
  15. package/dist/caffeinate-process-match.d.ts +10 -0
  16. package/dist/caffeinate-process-match.d.ts.map +1 -0
  17. package/dist/caffeinate-process-match.js +76 -0
  18. package/dist/caffeinate-process-match.js.map +1 -0
  19. package/dist/constants.d.ts +7 -1
  20. package/dist/constants.d.ts.map +1 -1
  21. package/dist/constants.js +27 -3
  22. package/dist/constants.js.map +1 -1
  23. package/dist/folder-watch-manager.d.ts +33 -0
  24. package/dist/folder-watch-manager.d.ts.map +1 -0
  25. package/dist/folder-watch-manager.js +129 -0
  26. package/dist/folder-watch-manager.js.map +1 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +83 -23
  30. package/dist/index.js.map +1 -1
  31. package/dist/protocol.d.ts +3 -3
  32. package/dist/protocol.d.ts.map +1 -1
  33. package/dist/protocol.js +2 -2
  34. package/dist/protocol.js.map +1 -1
  35. package/dist/schemas.d.ts +612 -185
  36. package/dist/schemas.d.ts.map +1 -1
  37. package/dist/schemas.js +112 -25
  38. package/dist/schemas.js.map +1 -1
  39. package/dist/session-registry.d.ts +1 -0
  40. package/dist/session-registry.d.ts.map +1 -1
  41. package/dist/session-registry.js +6 -0
  42. package/dist/session-registry.js.map +1 -1
  43. package/dist/types.d.ts +4 -1
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/utils/compile-schedule.d.ts +4 -1
  46. package/dist/utils/compile-schedule.d.ts.map +1 -1
  47. package/dist/utils/compile-schedule.js +14 -0
  48. package/dist/utils/compile-schedule.js.map +1 -1
  49. package/dist/utils/compute-next-automation-run-at.d.ts.map +1 -1
  50. package/dist/utils/compute-next-automation-run-at.js +4 -1
  51. package/dist/utils/compute-next-automation-run-at.js.map +1 -1
  52. package/dist/utils/reconcile-downtime.d.ts.map +1 -1
  53. package/dist/utils/reconcile-downtime.js +4 -1
  54. package/dist/utils/reconcile-downtime.js.map +1 -1
  55. package/package.json +1 -1
@@ -9,6 +9,11 @@ export declare const COLORTERM_VALUE = "truecolor";
9
9
  export declare const LOCALTERM_VALUE = "1";
10
10
  export declare const CAFFEINATE_BINARY = "caffeinate";
11
11
  export declare const CAFFEINATE_ARGS: readonly string[];
12
+ export declare const CAFFEINATE_AUTO_DEFAULT_COMMANDS: readonly string[];
13
+ export declare const CAFFEINATE_PREFERENCES_FILE_VERSION = 1;
14
+ export declare const CAFFEINATE_AUTO_POKE_DEBOUNCE_MS = 150;
15
+ export declare const MAX_CAFFEINATE_COMMANDS = 50;
16
+ export declare const MAX_CAFFEINATE_COMMAND_LENGTH = 128;
12
17
  export declare const TITLE_MAX_PATH_SEGMENTS = 1;
13
18
  /**
14
19
  * Strip terminal-emulator identity env vars inherited from the daemon's parent.
@@ -57,7 +62,7 @@ export declare const MAX_AUTOMATIONS = 100;
57
62
  export declare const MAX_AUTOMATION_NAME_LENGTH = 120;
58
63
  export declare const MAX_AUTOMATION_COMMAND_LENGTH = 4096;
59
64
  export declare const MAX_CRON_EXPRESSION_LENGTH = 256;
60
- export declare const AUTOMATIONS_FILE_VERSION = 2;
65
+ export declare const AUTOMATIONS_FILE_VERSION = 3;
61
66
  export declare const AUTOMATION_RUN_LIMIT_MAX = 100000;
62
67
  export declare const AUTOMATION_RUN_HISTORY_CAP = 50;
63
68
  export declare const AUTOMATION_DOWNTIME_RECONCILE_CAP = 10;
@@ -67,6 +72,7 @@ export declare const AUTOMATION_RECONCILE_LOOKBACK_MS: number;
67
72
  export declare const DAEMON_HEARTBEAT_FILE_VERSION = 1;
68
73
  export declare const AUTOMATION_TICK_ALIGNMENT_DELAY_MS = 50;
69
74
  export declare const AUTOMATION_PENDING_RUN_EXPIRY_MS: number;
75
+ export declare const AUTOMATION_WATCH_DEBOUNCE_MS = 500;
70
76
  export declare const CRON_NEXT_OCCURRENCE_SCAN_LIMIT_DAYS = 1466;
71
77
  export declare const MAX_AUTOMATION_EXIT_CODE_DIGITS = 4;
72
78
  export declare const WS_READY_STATE_OPEN = 1;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AACvD,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAC1C,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,eAAe,MAAM,CAAC;AAInC,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAC9C,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAAc,CAAC;AAE5D,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,UAgB5B,CAAC;AAEF,eAAO,MAAM,eAAe,QAAY,CAAC;AACzC,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAChD,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,gBAAgB,QAAW,CAAC;AACzC,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAO1C,eAAO,MAAM,kCAAkC,QAAkB,CAAC;AAClE,eAAO,MAAM,kCAAkC,QAAkB,CAAC;AAClE,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,+BAA+B,QAAmB,CAAC;AAUhE,eAAO,MAAM,wBAAwB,QAAW,CAAC;AAajD,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAOxC,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAK/C,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAE1C,eAAO,MAAM,cAAc,aAAoE,CAAC;AAEhG,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAM3C,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAI7C,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAGrD,eAAO,MAAM,mBAAmB,6CAA6C,CAAC;AAK9E,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,4BAA4B,QAAkB,CAAC;AAG5D,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAI3C,eAAO,MAAM,4BAA4B,QAAkB,CAAC;AAC5D,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAC1D,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAClD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAK9C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAG1C,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAGhD,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAI7C,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAI/C,eAAO,MAAM,oCAAoC,QAAS,CAAC;AAI3D,eAAO,MAAM,gCAAgC,QAA2B,CAAC;AACzE,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAG/C,eAAO,MAAM,kCAAkC,KAAK,CAAC;AAGrD,eAAO,MAAM,gCAAgC,QAAgB,CAAC;AAE9D,eAAO,MAAM,oCAAoC,OAAO,CAAC;AACzD,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,eAAO,MAAM,yBAAyB,OAAO,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
package/dist/constants.js CHANGED
@@ -11,6 +11,23 @@ export const LOCALTERM_VALUE = "1";
11
11
  // sleep — held for as long as the spawned process lives.
12
12
  export const CAFFEINATE_BINARY = "caffeinate";
13
13
  export const CAFFEINATE_ARGS = ["-dims"];
14
+ // Keep-awake "automatic" mode recognizes these commands out of the box and
15
+ // caffeinates whenever one is running in any localterm session. Fixed — the
16
+ // user can add their own on top but cannot remove these.
17
+ export const CAFFEINATE_AUTO_DEFAULT_COMMANDS = [
18
+ "claude",
19
+ "codex",
20
+ "opencode",
21
+ "pi",
22
+ ];
23
+ export const CAFFEINATE_PREFERENCES_FILE_VERSION = 1;
24
+ // Automatic detection is event-driven (no timer): a `ps` snapshot is taken only
25
+ // in response to a foreground change or a session connect/disconnect. This
26
+ // debounce window coalesces a burst of such events into a single snapshot; it
27
+ // fires once and does not repeat.
28
+ export const CAFFEINATE_AUTO_POKE_DEBOUNCE_MS = 150;
29
+ export const MAX_CAFFEINATE_COMMANDS = 50;
30
+ export const MAX_CAFFEINATE_COMMAND_LENGTH = 128;
14
31
  export const TITLE_MAX_PATH_SEGMENTS = 1;
15
32
  /**
16
33
  * Strip terminal-emulator identity env vars inherited from the daemon's parent.
@@ -129,9 +146,11 @@ export const MAX_AUTOMATION_COMMAND_LENGTH = 4096;
129
146
  export const MAX_CRON_EXPRESSION_LENGTH = 256;
130
147
  // v1 stored a raw cron string + a single lastRun. v2 stores a structured
131
148
  // schedule (with a derived cron computed on the fly), a run-count limit, a
132
- // lifecycle, and a capped run-history array. AutomationStore.load() migrates
133
- // v1 -> v2 in place on first boot so existing automations are never lost.
134
- export const AUTOMATIONS_FILE_VERSION = 2;
149
+ // lifecycle, and a capped run-history array. v3 wraps the schedule in a
150
+ // top-level `trigger` union so an automation can fire on a schedule OR when a
151
+ // folder changes. AutomationStore.load() migrates v1/v2 -> v3 in place on first
152
+ // boot so existing automations are never lost.
153
+ export const AUTOMATIONS_FILE_VERSION = 3;
135
154
  // Largest "stop after N runs" budget. Generous — a limit is opt-in; the common
136
155
  // case is "forever".
137
156
  export const AUTOMATION_RUN_LIMIT_MAX = 100_000;
@@ -159,6 +178,11 @@ export const AUTOMATION_TICK_ALIGNMENT_DELAY_MS = 50;
159
178
  // A launched run that no browser tab claims within this window is marked
160
179
  // "missed" (browser closed, headless host, open() failed silently).
161
180
  export const AUTOMATION_PENDING_RUN_EXPIRY_MS = 5 * 60 * 1000;
181
+ // Quiet period after the last filesystem event before a folder-watch trigger
182
+ // fires. Coalesces an event storm (one editor save emits several events; a
183
+ // build emits thousands) into a single run. Trailing-edge: the timer resets on
184
+ // every event and fires once the directory settles.
185
+ export const AUTOMATION_WATCH_DEBOUNCE_MS = 500;
162
186
  // Covers schedules that only fire on Feb 29 (the rarest valid cron target).
163
187
  export const CRON_NEXT_OCCURRENCE_SCAN_LIMIT_DAYS = 1466;
164
188
  export const MAX_AUTOMATION_EXIT_CODE_DIGITS = 4;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Automation } from "./types.js";
3
+ interface FolderWatchManagerEvents {
4
+ due: [automation: Automation];
5
+ }
6
+ interface WatchHandle {
7
+ close: () => void;
8
+ unref?: () => void;
9
+ }
10
+ type WatchFn = (target: string, options: {
11
+ recursive: boolean;
12
+ }, listener: () => void) => WatchHandle;
13
+ interface FolderWatchManagerOptions {
14
+ debounceMs: number;
15
+ isRunInFlight: (automationId: string) => boolean;
16
+ getAutomation: (automationId: string) => Automation | null;
17
+ watch?: WatchFn;
18
+ }
19
+ export declare class FolderWatchManager extends EventEmitter<FolderWatchManagerEvents> {
20
+ private readonly options;
21
+ private readonly entries;
22
+ private readonly watch;
23
+ private disposed;
24
+ constructor(options: FolderWatchManagerOptions);
25
+ sync(automations: Automation[]): void;
26
+ dispose(): void;
27
+ private startEntry;
28
+ private onFsEvent;
29
+ private fire;
30
+ private stopEntry;
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=folder-watch-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-watch-manager.d.ts","sourceRoot":"","sources":["../src/folder-watch-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,UAAU,wBAAwB;IAChC,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC/B;AAGD,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAID,KAAK,OAAO,GAAG,CACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,EAC/B,QAAQ,EAAE,MAAM,IAAI,KACjB,WAAW,CAAC;AAEjB,UAAU,yBAAyB;IAIjC,UAAU,EAAE,MAAM,CAAC;IAInB,aAAa,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IAIjD,aAAa,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;IAG3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAkBD,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,wBAAwB,CAAC;IAKhE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAS;gBAEI,OAAO,EAAE,yBAAyB;IAS/D,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI;IAoBrC,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,IAAI;IAcZ,OAAO,CAAC,SAAS;CAgBlB"}
@@ -0,0 +1,129 @@
1
+ import { EventEmitter } from "node:events";
2
+ import fs from "node:fs";
3
+ const signatureOf = (automation) => automation.trigger.kind === "watch"
4
+ ? `${automation.trigger.recursive}:${automation.cwd}`
5
+ : automation.cwd;
6
+ // Event-driven folder triggers for automations: one native fs.watch per "watch"
7
+ // automation, on its cwd. No polling — mirrors GitDiffWatcher. A burst of events
8
+ // is coalesced by a trailing debounce, and a launch is suppressed while a prior
9
+ // run is still in-flight (at most one run per automation at a time).
10
+ export class FolderWatchManager extends EventEmitter {
11
+ options;
12
+ entries = new Map();
13
+ watch;
14
+ disposed = false;
15
+ constructor(options) {
16
+ super();
17
+ this.options = options;
18
+ this.watch =
19
+ options.watch ??
20
+ ((target, watchOptions, listener) => fs.watch(target, watchOptions, () => listener()));
21
+ }
22
+ // Reconcile the live watchers with the desired set (enabled + active + watch).
23
+ // Idempotent and cheap, so it can be called after any automation mutation.
24
+ sync(automations) {
25
+ if (this.disposed)
26
+ return;
27
+ const desired = new Map();
28
+ for (const automation of automations) {
29
+ if (!automation.enabled)
30
+ continue;
31
+ if (automation.lifecycle !== "active")
32
+ continue;
33
+ if (automation.trigger.kind !== "watch")
34
+ continue;
35
+ desired.set(automation.id, automation);
36
+ }
37
+ // Stop watchers that are no longer desired or whose target changed.
38
+ for (const [id, entry] of this.entries) {
39
+ const automation = desired.get(id);
40
+ if (!automation || signatureOf(automation) !== entry.signature)
41
+ this.stopEntry(id);
42
+ }
43
+ // Start watchers for newly-desired (or just-rebuilt) automations.
44
+ for (const [id, automation] of desired) {
45
+ if (!this.entries.has(id))
46
+ this.startEntry(automation);
47
+ }
48
+ }
49
+ dispose() {
50
+ this.disposed = true;
51
+ for (const id of [...this.entries.keys()])
52
+ this.stopEntry(id);
53
+ this.removeAllListeners();
54
+ }
55
+ startEntry(automation) {
56
+ if (automation.trigger.kind !== "watch")
57
+ return;
58
+ const { recursive } = automation.trigger;
59
+ const entry = {
60
+ watchers: [],
61
+ signature: signatureOf(automation),
62
+ debounceTimer: null,
63
+ };
64
+ try {
65
+ const watcher = this.watch(automation.cwd, { recursive }, () => {
66
+ this.onFsEvent(automation.id);
67
+ });
68
+ // Don't keep the daemon alive on the watch alone (the http server does).
69
+ watcher.unref?.();
70
+ entry.watchers.push(watcher);
71
+ }
72
+ catch {
73
+ // cwd doesn't exist or isn't watchable right now — leave the entry empty;
74
+ // a later sync (after the directory is fixed) retries.
75
+ }
76
+ this.entries.set(automation.id, entry);
77
+ }
78
+ onFsEvent(automationId) {
79
+ if (this.disposed)
80
+ return;
81
+ const entry = this.entries.get(automationId);
82
+ if (!entry)
83
+ return;
84
+ if (entry.debounceTimer !== null)
85
+ clearTimeout(entry.debounceTimer);
86
+ entry.debounceTimer = setTimeout(() => {
87
+ entry.debounceTimer = null;
88
+ this.fire(automationId);
89
+ }, this.options.debounceMs);
90
+ entry.debounceTimer.unref?.();
91
+ }
92
+ fire(automationId) {
93
+ if (this.disposed)
94
+ return;
95
+ if (!this.entries.has(automationId))
96
+ return;
97
+ // No overlap: drop this change if a run is still in-flight. The next event
98
+ // after it settles re-arms, so writes made *during* the run are ignored.
99
+ if (this.options.isRunInFlight(automationId))
100
+ return;
101
+ // Re-read live state — the automation may have been disabled, hit its limit,
102
+ // or switched to a schedule while the debounce was pending.
103
+ const automation = this.options.getAutomation(automationId);
104
+ if (!automation || !automation.enabled || automation.lifecycle !== "active")
105
+ return;
106
+ if (automation.trigger.kind !== "watch")
107
+ return;
108
+ this.emit("due", automation);
109
+ }
110
+ stopEntry(automationId) {
111
+ const entry = this.entries.get(automationId);
112
+ if (!entry)
113
+ return;
114
+ if (entry.debounceTimer !== null) {
115
+ clearTimeout(entry.debounceTimer);
116
+ entry.debounceTimer = null;
117
+ }
118
+ for (const watcher of entry.watchers) {
119
+ try {
120
+ watcher.close();
121
+ }
122
+ catch {
123
+ /* already closed */
124
+ }
125
+ }
126
+ this.entries.delete(automationId);
127
+ }
128
+ }
129
+ //# sourceMappingURL=folder-watch-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-watch-manager.js","sourceRoot":"","sources":["../src/folder-watch-manager.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CaffeinateController } from "./caffeinate-controller.js";
2
+ import type { SnapshotProcesses } from "./caffeinate-process-match.js";
2
3
  import { SessionRegistry } from "./session-registry.js";
3
4
  export interface ServerOptions {
4
5
  port?: number;
@@ -19,6 +20,12 @@ export interface ServerOptions {
19
20
  * power assertion.
20
21
  */
21
22
  caffeinateController?: CaffeinateController;
23
+ /**
24
+ * Override how automatic-mode keep-awake inspects running processes. Defaults
25
+ * to a real `ps` snapshot. Injectable so tests can drive automatic detection
26
+ * deterministically without spawning processes.
27
+ */
28
+ caffeinateSnapshotProcesses?: SnapshotProcesses;
22
29
  }
23
30
  /** Opens and (optionally) closes the browser tab for an automation run. */
24
31
  export interface AutomationTabController {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAwClE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkBxD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,0EAA0E;IAC1E,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AA+DD,eAAO,MAAM,YAAY,GAAU,UAAS,aAAkB,KAAG,OAAO,CAAC,aAAa,CAkyBrF,CAAC;AAEF,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,mBAAmB,YAAY,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AA0CvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkBxD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,iBAAiB,CAAC;CACjD;AAED,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,0EAA0E;IAC1E,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AA+DD,eAAO,MAAM,YAAY,GAAU,UAAS,aAAkB,KAAG,OAAO,CAAC,aAAa,CA41BrF,CAAC;AAEF,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,mBAAmB,YAAY,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -10,9 +10,12 @@ import { AutomationRunTracker } from "./automation-run-tracker.js";
10
10
  import { AutomationScheduler } from "./automation-scheduler.js";
11
11
  import { AutomationStore } from "./automation-store.js";
12
12
  import { CaffeinateController } from "./caffeinate-controller.js";
13
+ import { CaffeinateManager } from "./caffeinate-manager.js";
14
+ import { CaffeinatePreferencesStore } from "./caffeinate-preferences-store.js";
13
15
  import { CdpClient } from "./cdp/cdp-client.js";
14
- import { AUTOMATION_RECONCILE_MIN_DOWNTIME_MS, DEFAULT_HOST, DEFAULT_PORT, FRIENDLY_HOSTNAME, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CREATED, HTTP_STATUS_NOT_FOUND, MAX_AUTOMATIONS, MAX_CONCURRENT_SESSIONS, MS_PER_MINUTE, OUTPUT_BATCH_FLUSH_BYTES, OUTPUT_BATCH_WINDOW_MS, SERVER_STOP_GRACE_MS, WS_BACKPRESSURE_THRESHOLD_BYTES, WS_CLOSE_BACKPRESSURE, WS_CLOSE_CAPACITY_REACHED, WS_CLOSE_POLICY_VIOLATION, WS_HEARTBEAT_INTERVAL_MS, WS_HEARTBEAT_TIMEOUT_MS, WS_OUTBOUND_DRAIN_POLL_MS, WS_OUTBOUND_PAUSE_HIGH_WATER_BYTES, WS_OUTBOUND_RESUME_LOW_WATER_BYTES, WS_READY_STATE_OPEN, } from "./constants.js";
16
+ import { AUTOMATION_RECONCILE_MIN_DOWNTIME_MS, AUTOMATION_WATCH_DEBOUNCE_MS, DEFAULT_HOST, DEFAULT_PORT, FRIENDLY_HOSTNAME, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CREATED, HTTP_STATUS_NOT_FOUND, MAX_AUTOMATIONS, MAX_CONCURRENT_SESSIONS, MS_PER_MINUTE, OUTPUT_BATCH_FLUSH_BYTES, OUTPUT_BATCH_WINDOW_MS, SERVER_STOP_GRACE_MS, WS_BACKPRESSURE_THRESHOLD_BYTES, WS_CLOSE_BACKPRESSURE, WS_CLOSE_CAPACITY_REACHED, WS_CLOSE_POLICY_VIOLATION, WS_HEARTBEAT_INTERVAL_MS, WS_HEARTBEAT_TIMEOUT_MS, WS_OUTBOUND_DRAIN_POLL_MS, WS_OUTBOUND_PAUSE_HIGH_WATER_BYTES, WS_OUTBOUND_RESUME_LOW_WATER_BYTES, WS_READY_STATE_OPEN, } from "./constants.js";
15
17
  import { ServerErrorException, serverError } from "./errors.js";
18
+ import { FolderWatchManager } from "./folder-watch-manager.js";
16
19
  import { getGitDiff, getGitDiffFilePatch, getGitDiffFiles, getGitDiffSummary } from "./git-diff.js";
17
20
  import { GitDiffWatcher } from "./git-diff-watcher.js";
18
21
  import { HeartbeatStore } from "./heartbeat-store.js";
@@ -22,7 +25,7 @@ import { Session } from "./session.js";
22
25
  import { createNetworkPolicyMiddleware, isAllowedSourceIp, isLoopbackHost } from "./security.js";
23
26
  import { SessionRegistry } from "./session-registry.js";
24
27
  import { resolveStaticAsset } from "./static-resolver.js";
25
- import { compileSchedule, compileScheduleAll, normalizeScheduleInput, } from "./utils/compile-schedule.js";
28
+ import { compileSchedule, compileScheduleAll, normalizeTriggerInput, } from "./utils/compile-schedule.js";
26
29
  import { computeNextAutomationRunAt } from "./utils/compute-next-automation-run-at.js";
27
30
  import { enumerateMissedOccurrences } from "./utils/reconcile-downtime.js";
28
31
  const getRawBufferedAmount = (raw) => {
@@ -102,8 +105,27 @@ export const createServer = async (options = {}) => {
102
105
  const automationStore = new AutomationStore(path.join(stateDirectory, "automations.json"));
103
106
  const automationRunTracker = new AutomationRunTracker();
104
107
  const automationScheduler = new AutomationScheduler(automationStore);
108
+ // Folder-watch triggers: one fs.watch per watch automation's cwd (no polling).
109
+ // isRunInFlight gates overlap (a launched/running latest run blocks a new
110
+ // launch); getAutomation re-reads live state when the debounce fires.
111
+ const folderWatchManager = new FolderWatchManager({
112
+ debounceMs: AUTOMATION_WATCH_DEBOUNCE_MS,
113
+ isRunInFlight: (automationId) => {
114
+ const status = automationStore.get(automationId)?.runs[0]?.status;
115
+ return status === "launched" || status === "running";
116
+ },
117
+ getAutomation: (automationId) => automationStore.get(automationId),
118
+ });
119
+ const syncFolderWatchers = () => folderWatchManager.sync(automationStore.list());
105
120
  const heartbeatStore = new HeartbeatStore(path.join(stateDirectory, "daemon-heartbeat.json"));
106
121
  const caffeinateController = options.caffeinateController ?? new CaffeinateController();
122
+ const caffeinatePreferencesStore = new CaffeinatePreferencesStore(path.join(stateDirectory, "caffeinate.json"));
123
+ const caffeinateManager = new CaffeinateManager({
124
+ controller: caffeinateController,
125
+ store: caffeinatePreferencesStore,
126
+ listSessionPids: () => registry.pids(),
127
+ snapshotProcesses: options.caffeinateSnapshotProcesses,
128
+ });
107
129
  const clientSockets = new Set();
108
130
  const cdpBackgroundTabsDisabled = process.env.LOCALTERM_DISABLE_CDP_TABS === "1";
109
131
  // One persistent CDP socket for the daemon's lifetime — opened once at start
@@ -153,7 +175,7 @@ export const createServer = async (options = {}) => {
153
175
  const toAutomationWithNextRun = (automation, from) => ({
154
176
  ...automation,
155
177
  nextRunAt: computeNextAutomationRunAt(automation, from),
156
- cron: compileSchedule(automation.schedule),
178
+ cron: automation.trigger.kind === "schedule" ? compileSchedule(automation.trigger.schedule) : null,
157
179
  lastRun: deriveLastRun(automation),
158
180
  });
159
181
  const listAutomationsWithNextRun = () => {
@@ -171,29 +193,32 @@ export const createServer = async (options = {}) => {
171
193
  };
172
194
  const caffeinateStatePayload = () => ({
173
195
  type: "caffeinate",
174
- active: caffeinateController.active,
175
- supported: caffeinateController.supported,
196
+ supported: caffeinateManager.supported,
197
+ active: caffeinateManager.active,
198
+ mode: caffeinateManager.mode,
199
+ defaultCommands: [...caffeinateManager.defaultCommands],
200
+ commands: caffeinateManager.commands,
176
201
  });
177
202
  // The daemon owns the single keep-awake process, so its state is broadcast to
178
- // every tab — exactly like automations — and the coffee toggles stay in sync.
203
+ // every tab — exactly like automations — and the coffee controls stay in sync.
179
204
  const broadcastCaffeinate = () => {
180
205
  const payload = caffeinateStatePayload();
181
206
  for (const clientSocket of clientSockets) {
182
207
  safeSend(clientSocket, payload);
183
208
  }
184
209
  };
185
- caffeinateController.on("change", broadcastCaffeinate);
186
- // Open a browser tab for a run and record it in history. Scheduled launches
187
- // count toward the limit (and can finish the automation); manual launches
188
- // never count and are allowed even on a finished/disabled automation.
210
+ caffeinateManager.on("change", broadcastCaffeinate);
211
+ // Open a browser tab for a run and record it in history. Scheduled and watch
212
+ // launches count toward the limit (and can finish the automation); manual
213
+ // launches never count and are allowed even on a finished/disabled automation.
189
214
  const tryLaunch = (automation, trigger) => {
190
- if (trigger === "schedule") {
215
+ if (trigger !== "manual") {
191
216
  const current = automationStore.get(automation.id);
192
217
  if (!current || !current.enabled || current.lifecycle === "finished")
193
218
  return null;
194
219
  }
195
220
  const run = automationRunTracker.create(automation);
196
- const counts = trigger === "schedule";
221
+ const counts = trigger !== "manual";
197
222
  automationStore.appendRun(automation.id, {
198
223
  runId: run.runId,
199
224
  scheduledFor: trigger === "schedule"
@@ -209,6 +234,9 @@ export const createServer = async (options = {}) => {
209
234
  if (counts)
210
235
  automationStore.incrementRunCount(automation.id);
211
236
  broadcastAutomations();
237
+ // A watch automation that just reached its limit is now "finished"; stop
238
+ // watching its folder promptly instead of waiting for the next mutation.
239
+ syncFolderWatchers();
212
240
  const runUrl = `http://${FRIENDLY_HOSTNAME}:${actualPort}/?run=${run.runId}`;
213
241
  void tabController
214
242
  .open(runUrl)
@@ -335,10 +363,17 @@ export const createServer = async (options = {}) => {
335
363
  return undefined;
336
364
  }
337
365
  };
338
- // A structured schedule (or legacy bare cron string) is valid iff every cron
339
- // it compiles to parses.
340
- const isValidScheduleInput = (input) => {
341
- const crons = compileScheduleAll(normalizeScheduleInput(input));
366
+ // The trigger (new `trigger` union or legacy bare `schedule`) is valid iff a
367
+ // schedule trigger compiles to ≥1 parseable cron; a watch trigger is always
368
+ // valid (its watched cwd is validated separately). Returns true when neither
369
+ // field is present — a PATCH that doesn't touch the trigger.
370
+ const isValidTriggerInput = (input) => {
371
+ if (input.trigger === undefined && input.schedule === undefined)
372
+ return true;
373
+ const trigger = normalizeTriggerInput(input);
374
+ if (trigger.kind === "watch")
375
+ return true;
376
+ const crons = compileScheduleAll(trigger.schedule);
342
377
  return crons.length > 0 && crons.every((cron) => parseCronExpression(cron) !== null);
343
378
  };
344
379
  api.get("/automations", (context) => context.json({ automations: listAutomationsWithNextRun() }));
@@ -349,7 +384,7 @@ export const createServer = async (options = {}) => {
349
384
  if (automationStore.size() >= MAX_AUTOMATIONS) {
350
385
  return context.json({ error: "too_many_automations" }, HTTP_STATUS_BAD_REQUEST);
351
386
  }
352
- if (!isValidScheduleInput(parsed.data.schedule)) {
387
+ if (!isValidTriggerInput(parsed.data)) {
353
388
  return context.json({ error: "invalid_schedule" }, HTTP_STATUS_BAD_REQUEST);
354
389
  }
355
390
  if (!resolveCwdQuery(parsed.data.cwd)) {
@@ -357,13 +392,14 @@ export const createServer = async (options = {}) => {
357
392
  }
358
393
  const automation = automationStore.create(parsed.data);
359
394
  broadcastAutomations();
395
+ syncFolderWatchers();
360
396
  return context.json({ automation: toAutomationWithNextRun(automation, new Date()) }, HTTP_STATUS_CREATED);
361
397
  });
362
398
  api.patch("/automations/:id", async (context) => {
363
399
  const parsed = updateAutomationInputSchema.safeParse(await readJsonBody(context));
364
400
  if (!parsed.success)
365
401
  return context.json({ error: "invalid_body" }, HTTP_STATUS_BAD_REQUEST);
366
- if (parsed.data.schedule !== undefined && !isValidScheduleInput(parsed.data.schedule)) {
402
+ if (!isValidTriggerInput(parsed.data)) {
367
403
  return context.json({ error: "invalid_schedule" }, HTTP_STATUS_BAD_REQUEST);
368
404
  }
369
405
  if (parsed.data.cwd !== undefined && !resolveCwdQuery(parsed.data.cwd)) {
@@ -381,6 +417,7 @@ export const createServer = async (options = {}) => {
381
417
  if (!automation)
382
418
  return context.json({ error: "not_found" }, HTTP_STATUS_NOT_FOUND);
383
419
  broadcastAutomations();
420
+ syncFolderWatchers();
384
421
  return context.json({ automation: toAutomationWithNextRun(automation, new Date()) });
385
422
  });
386
423
  api.delete("/automations/:id", (context) => {
@@ -388,13 +425,14 @@ export const createServer = async (options = {}) => {
388
425
  return context.json({ error: "not_found" }, HTTP_STATUS_NOT_FOUND);
389
426
  }
390
427
  broadcastAutomations();
428
+ syncFolderWatchers();
391
429
  return context.json({ ok: true });
392
430
  });
393
431
  api.post("/automations/:id/run", (context) => {
394
432
  const automation = automationStore.get(context.req.param("id"));
395
433
  if (!automation)
396
434
  return context.json({ error: "not_found" }, HTTP_STATUS_NOT_FOUND);
397
- // tryLaunch only returns null for the "schedule" trigger (finished/disabled
435
+ // tryLaunch only returns null for non-manual triggers (the finished/disabled
398
436
  // guard); a manual launch always succeeds. The guard satisfies the shared
399
437
  // nullable return type before reading run.runId.
400
438
  const run = tryLaunch(automation, "manual");
@@ -410,6 +448,8 @@ export const createServer = async (options = {}) => {
410
448
  if (!automation)
411
449
  return context.json({ error: "not_found" }, HTTP_STATUS_NOT_FOUND);
412
450
  broadcastAutomations();
451
+ // Reset re-enables + reactivates; a watch automation resumes watching.
452
+ syncFolderWatchers();
413
453
  return context.json({ automation: toAutomationWithNextRun(automation, new Date()) });
414
454
  });
415
455
  api.notFound((context) => context.json({ error: "not_found" }, HTTP_STATUS_NOT_FOUND));
@@ -592,7 +632,12 @@ export const createServer = async (options = {}) => {
592
632
  };
593
633
  const onTitle = (title) => safeSend(ws, { type: "title", title });
594
634
  const onCwd = (cwd) => safeSend(ws, { type: "cwd", cwd });
595
- const onForeground = (process) => safeSend(ws, { type: "foreground", process });
635
+ const onForeground = (process) => {
636
+ safeSend(ws, { type: "foreground", process });
637
+ // A foreground transition is the cheap signal that a recognized
638
+ // program may have started or stopped — nudge automatic detection.
639
+ caffeinateManager.pokeAuto();
640
+ };
596
641
  const onNotification = (body) => safeSend(ws, { type: "notification", body });
597
642
  const onExit = (code) => {
598
643
  if (outputBatchTimer !== null) {
@@ -665,8 +710,11 @@ export const createServer = async (options = {}) => {
665
710
  if (parsed.data.type === "input") {
666
711
  session.write(parsed.data.data);
667
712
  }
668
- else if (parsed.data.type === "caffeinate") {
669
- caffeinateController.setActive(parsed.data.enabled);
713
+ else if (parsed.data.type === "caffeinate-mode") {
714
+ caffeinateManager.setMode(parsed.data.mode);
715
+ }
716
+ else if (parsed.data.type === "caffeinate-commands") {
717
+ caffeinateManager.setCommands(parsed.data.commands);
670
718
  }
671
719
  else {
672
720
  session.resize(parsed.data.cols, parsed.data.rows, parsed.data.pixelWidth, parsed.data.pixelHeight);
@@ -695,6 +743,9 @@ export const createServer = async (options = {}) => {
695
743
  if (!session)
696
744
  return;
697
745
  registry.unregister(session);
746
+ // A closed session may have been the only one running a trigger;
747
+ // re-evaluate automatic keep-awake now that its pid is gone.
748
+ caffeinateManager.pokeAuto();
698
749
  session.dispose();
699
750
  session = null;
700
751
  activeWs = null;
@@ -720,6 +771,9 @@ export const createServer = async (options = {}) => {
720
771
  if (!session)
721
772
  return;
722
773
  registry.unregister(session);
774
+ // A closed session may have been the only one running a trigger;
775
+ // re-evaluate automatic keep-awake now that its pid is gone.
776
+ caffeinateManager.pokeAuto();
723
777
  session.dispose();
724
778
  session = null;
725
779
  activeWs = null;
@@ -771,6 +825,9 @@ export const createServer = async (options = {}) => {
771
825
  automationScheduler.on("due", (automation) => {
772
826
  tryLaunch(automation, "schedule");
773
827
  });
828
+ folderWatchManager.on("due", (automation) => {
829
+ tryLaunch(automation, "watch");
830
+ });
774
831
  automationScheduler.on("tick", (now) => {
775
832
  // Liveness heartbeat for downtime detection on the next boot.
776
833
  heartbeatStore.write(now.getTime());
@@ -791,6 +848,8 @@ export const createServer = async (options = {}) => {
791
848
  });
792
849
  reconcileOnStartup(Date.now());
793
850
  automationScheduler.start();
851
+ // Arm folder-watch triggers for the automations loaded at boot.
852
+ syncFolderWatchers();
794
853
  // Open the persistent CDP connection now, while the user is present at
795
854
  // `start` to clear any one-time remote-debugging prompt. Fire-and-forget:
796
855
  // failure just means runs fall back to the OS opener.
@@ -808,7 +867,8 @@ export const createServer = async (options = {}) => {
808
867
  }
809
868
  const stop = async () => {
810
869
  automationScheduler.dispose();
811
- caffeinateController.dispose();
870
+ folderWatchManager.dispose();
871
+ caffeinateManager.dispose();
812
872
  cdpClient?.close();
813
873
  registry.disposeAll();
814
874
  // Forcibly tear down every WS first. node-pty + ws upgraded sockets