loop-task 1.1.0 → 1.4.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 (130) hide show
  1. package/README.md +250 -113
  2. package/dist/board/App.js +340 -0
  3. package/dist/board/board-log.js +18 -0
  4. package/dist/board/components/ActionButtons.js +45 -0
  5. package/dist/board/components/BoardButton.js +58 -0
  6. package/dist/board/components/ConfirmModal.js +36 -0
  7. package/dist/board/components/CreateForm.js +309 -0
  8. package/dist/board/components/CreateProjectModal.js +104 -0
  9. package/dist/board/components/DeleteProjectConfirm.js +53 -0
  10. package/dist/board/components/DetailView.js +25 -0
  11. package/dist/board/components/EditProjectModal.js +106 -0
  12. package/dist/board/components/FilterBar.js +16 -0
  13. package/dist/board/components/Footer.js +52 -0
  14. package/dist/board/components/Header.js +46 -0
  15. package/dist/board/components/HelpModal.js +36 -0
  16. package/dist/board/components/Inspector.js +12 -0
  17. package/dist/board/components/LogModal.js +58 -0
  18. package/dist/board/components/Navigator.js +84 -0
  19. package/dist/board/components/ProjectsModal.js +70 -0
  20. package/dist/board/components/ProjectsPage.js +105 -0
  21. package/dist/board/components/RunHistory.js +83 -0
  22. package/dist/board/components/TaskBrowser.js +95 -0
  23. package/dist/board/components/TaskFilterBar.js +6 -0
  24. package/dist/board/components/TaskForm.js +177 -0
  25. package/dist/board/components/Timeline.js +5 -0
  26. package/dist/board/daemon.js +117 -0
  27. package/dist/board/format.js +102 -0
  28. package/dist/board/hooks/useBoardKeybindings.js +299 -0
  29. package/dist/board/hooks/useBreakpoint.js +6 -0
  30. package/dist/board/hooks/useHoverState.js +11 -0
  31. package/dist/board/hooks/useLogStream.js +32 -0
  32. package/dist/board/hooks/useLoopPolling.js +28 -0
  33. package/dist/board/hooks/useTaskKeybindings.js +114 -0
  34. package/dist/board/index.js +32 -0
  35. package/dist/board/router.js +16 -0
  36. package/dist/board/state.js +99 -0
  37. package/dist/board/toast.js +65 -0
  38. package/dist/board/types.js +1 -0
  39. package/dist/cli.js +134 -93
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/commands.d.ts +11 -0
  42. package/dist/client/commands.d.ts.map +1 -0
  43. package/dist/client/commands.js +317 -0
  44. package/dist/client/commands.js.map +1 -0
  45. package/dist/client/ipc.d.ts +5 -0
  46. package/dist/client/ipc.d.ts.map +1 -0
  47. package/dist/client/ipc.js +96 -0
  48. package/dist/client/ipc.js.map +1 -0
  49. package/dist/config/constants.js +29 -0
  50. package/dist/config/paths.js +49 -0
  51. package/dist/core/command-runner.js +82 -0
  52. package/dist/core/foreground-loop.js +81 -0
  53. package/dist/core/log-parser.js +29 -0
  54. package/dist/core/log-rotator.js +25 -0
  55. package/dist/core/loop-controller.js +429 -0
  56. package/dist/daemon/daemon-log.js +11 -0
  57. package/dist/daemon/index.d.ts +2 -0
  58. package/dist/daemon/index.d.ts.map +1 -0
  59. package/dist/daemon/index.js +43 -0
  60. package/dist/daemon/index.js.map +1 -0
  61. package/dist/daemon/manager.d.ts +17 -0
  62. package/dist/daemon/manager.d.ts.map +1 -0
  63. package/dist/daemon/manager.js +260 -0
  64. package/dist/daemon/manager.js.map +1 -0
  65. package/dist/daemon/projects.js +104 -0
  66. package/dist/daemon/server.d.ts +15 -0
  67. package/dist/daemon/server.d.ts.map +1 -0
  68. package/dist/daemon/server.js +345 -0
  69. package/dist/daemon/server.js.map +1 -0
  70. package/dist/daemon/spawner.d.ts +3 -0
  71. package/dist/daemon/spawner.d.ts.map +1 -0
  72. package/dist/daemon/spawner.js +82 -0
  73. package/dist/daemon/spawner.js.map +1 -0
  74. package/dist/daemon/state.d.ts +15 -0
  75. package/dist/daemon/state.d.ts.map +1 -0
  76. package/dist/daemon/state.js +151 -0
  77. package/dist/daemon/state.js.map +1 -0
  78. package/dist/daemon/task-manager.js +59 -0
  79. package/dist/duration.js +4 -4
  80. package/dist/entry.js +11 -0
  81. package/dist/esm-loader.js +36 -0
  82. package/dist/i18n/en.json +386 -0
  83. package/dist/i18n/index.js +11 -0
  84. package/dist/ipc/handlers/logs-stream.js +65 -0
  85. package/dist/ipc/send.js +5 -0
  86. package/dist/logger.js +0 -1
  87. package/dist/loop-config.d.ts +13 -0
  88. package/dist/loop-config.d.ts.map +1 -0
  89. package/dist/loop-config.js +83 -0
  90. package/dist/loop-config.js.map +1 -0
  91. package/dist/loop.d.ts +43 -2
  92. package/dist/loop.d.ts.map +1 -1
  93. package/dist/loop.js +247 -2
  94. package/dist/loop.js.map +1 -1
  95. package/dist/open-board.d.ts +2 -0
  96. package/dist/open-board.d.ts.map +1 -0
  97. package/dist/open-board.js +19 -0
  98. package/dist/open-board.js.map +1 -0
  99. package/dist/shared/clipboard.js +19 -0
  100. package/dist/shared/fs-utils.js +11 -0
  101. package/dist/shared/sleep.js +17 -0
  102. package/dist/shared/tail.js +4 -0
  103. package/dist/tui/app.d.ts +57 -0
  104. package/dist/tui/app.d.ts.map +1 -0
  105. package/dist/tui/app.js +167 -0
  106. package/dist/tui/app.js.map +1 -0
  107. package/dist/tui/create-loop.d.ts +3 -0
  108. package/dist/tui/create-loop.d.ts.map +1 -0
  109. package/dist/tui/create-loop.js +90 -0
  110. package/dist/tui/create-loop.js.map +1 -0
  111. package/dist/tui/dashboard.d.ts +2 -0
  112. package/dist/tui/dashboard.d.ts.map +1 -0
  113. package/dist/tui/dashboard.js +45 -0
  114. package/dist/tui/dashboard.js.map +1 -0
  115. package/dist/tui/events.d.ts +3 -0
  116. package/dist/tui/events.d.ts.map +1 -0
  117. package/dist/tui/events.js +244 -0
  118. package/dist/tui/events.js.map +1 -0
  119. package/dist/tui/popup.d.ts +3 -0
  120. package/dist/tui/popup.d.ts.map +1 -0
  121. package/dist/tui/popup.js +58 -0
  122. package/dist/tui/popup.js.map +1 -0
  123. package/dist/tui/render.d.ts +9 -0
  124. package/dist/tui/render.d.ts.map +1 -0
  125. package/dist/tui/render.js +305 -0
  126. package/dist/tui/render.js.map +1 -0
  127. package/dist/types.d.ts +74 -0
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/types.js +0 -1
  130. package/package.json +64 -55
@@ -0,0 +1,429 @@
1
+ import { EventEmitter } from "node:events";
2
+ import fs from "node:fs";
3
+ import crypto from "node:crypto";
4
+ import { sleep } from "../shared/sleep.js";
5
+ import { SLEEP_CHUNK_MS } from "../config/constants.js";
6
+ import { executeCommand } from "./command-runner.js";
7
+ import { rotateLogIfNeeded } from "./log-rotator.js";
8
+ export class LoopController extends EventEmitter {
9
+ abortController;
10
+ runAbortController = null;
11
+ _paused = false;
12
+ _forceRun = false;
13
+ _savedRemainingMs = null;
14
+ _resetSchedule = false;
15
+ _stopAfterRun = false;
16
+ _maxRunsReached = false;
17
+ _status = "running";
18
+ resumeResolve = null;
19
+ runCount = 0;
20
+ lastRunAt = null;
21
+ lastExitCode = null;
22
+ lastDuration = null;
23
+ nextRunAt = null;
24
+ createdAt;
25
+ id;
26
+ options;
27
+ logPath;
28
+ taskResolver;
29
+ remainingDelayMs = null;
30
+ logStream = null;
31
+ loopPromise = null;
32
+ sessionStartedAt = null;
33
+ runHistory = [];
34
+ currentRunStartOffset = 0;
35
+ skippedCount = 0;
36
+ constructor(id, options, logPath, taskResolver, state) {
37
+ super();
38
+ this.id = id;
39
+ this.options = options;
40
+ this.logPath = logPath;
41
+ this.taskResolver = taskResolver;
42
+ this.abortController = new AbortController();
43
+ this.createdAt = state?.createdAt ?? new Date().toISOString();
44
+ this.runCount = state?.runCount ?? 0;
45
+ this._maxRunsReached = state?.maxRunsReached ?? false;
46
+ this.sessionStartedAt = state?.sessionStartedAt ?? null;
47
+ this.lastRunAt = state?.lastRunAt ?? null;
48
+ this.lastExitCode = state?.lastExitCode ?? null;
49
+ this.lastDuration = state?.lastDuration ?? null;
50
+ this.nextRunAt = state?.nextRunAt ?? null;
51
+ this.remainingDelayMs = state?.remainingDelayMs ?? null;
52
+ this._status = state?.status ?? "running";
53
+ this._paused = state?.status === "paused" || state?.status === "idle";
54
+ this.runHistory = (state?.runHistory ?? []).map((r) => r.status === "running" ? { ...r, status: "completed" } : r).map((r) => ({ ...r, logOffset: r.logOffset ?? 0 }));
55
+ this.skippedCount = state?.skippedCount ?? 0;
56
+ }
57
+ get status() {
58
+ return this._status;
59
+ }
60
+ start() {
61
+ this.skippedCount = 0;
62
+ this.logStream?.end();
63
+ this.abortController = new AbortController();
64
+ this.logStream = fs.createWriteStream(this.logPath, { flags: "a" });
65
+ this.loopPromise = this.run();
66
+ if (this.sessionStartedAt === null) {
67
+ this.sessionStartedAt = new Date().toISOString();
68
+ }
69
+ }
70
+ pause(interruptCurrentRun = false) {
71
+ if (this._status === "running" || this._status === "waiting") {
72
+ this._paused = true;
73
+ this._status = "paused";
74
+ if (interruptCurrentRun && this._status === "paused") {
75
+ this.runAbortController?.abort();
76
+ }
77
+ this.emit("paused");
78
+ }
79
+ }
80
+ stopLoop(interruptCurrentRun = false) {
81
+ if (this._status === "running" || this._status === "waiting" || this._status === "paused") {
82
+ this._paused = true;
83
+ this._status = "idle";
84
+ this.sessionStartedAt = null;
85
+ this.remainingDelayMs = null;
86
+ this.nextRunAt = null;
87
+ if (interruptCurrentRun) {
88
+ this.runAbortController?.abort();
89
+ }
90
+ this.emit("stopped");
91
+ }
92
+ }
93
+ resume() {
94
+ if (this._status === "paused" && this._paused && this.resumeResolve) {
95
+ this._paused = false;
96
+ if (this.remainingDelayMs !== null) {
97
+ this._status = "waiting";
98
+ this.nextRunAt = new Date(Date.now() + this.remainingDelayMs).toISOString();
99
+ }
100
+ this.resumeResolve();
101
+ this.resumeResolve = null;
102
+ this.emit("resumed");
103
+ }
104
+ }
105
+ playLoop() {
106
+ if (this._status !== "idle" && this._status !== "stopped")
107
+ return false;
108
+ if (this._maxRunsReached)
109
+ return false;
110
+ if (this.options.maxRuns !== null && this.runCount >= this.options.maxRuns) {
111
+ this._maxRunsReached = true;
112
+ return false;
113
+ }
114
+ this.sessionStartedAt = new Date().toISOString();
115
+ this._resetSchedule = true;
116
+ this._paused = false;
117
+ this._status = "waiting";
118
+ this.nextRunAt = new Date(Date.now() + this.options.interval).toISOString();
119
+ if (this.resumeResolve) {
120
+ this.resumeResolve();
121
+ this.resumeResolve = null;
122
+ }
123
+ this.start();
124
+ this.emit("resumed");
125
+ return true;
126
+ }
127
+ triggerNow() {
128
+ if (this._status === "running")
129
+ return false;
130
+ if (this._maxRunsReached)
131
+ return false;
132
+ const needsStart = this._status === "stopped" || this._status === "idle";
133
+ this._savedRemainingMs = this.remainingDelayMs;
134
+ this._forceRun = true;
135
+ if (needsStart) {
136
+ this._stopAfterRun = true;
137
+ this._paused = false;
138
+ this._status = "running";
139
+ this.start();
140
+ }
141
+ else {
142
+ if (this._paused)
143
+ this.resume();
144
+ }
145
+ this.emit("triggered");
146
+ return true;
147
+ }
148
+ async stop() {
149
+ this.abortController.abort();
150
+ if (this._paused)
151
+ this.resume();
152
+ if (this.loopPromise) {
153
+ await this.loopPromise;
154
+ }
155
+ this.logStream?.end();
156
+ this.logStream = null;
157
+ }
158
+ getMeta() {
159
+ return {
160
+ id: this.id,
161
+ taskId: this.options.taskId,
162
+ status: this._status,
163
+ createdAt: this.createdAt,
164
+ maxRunsReached: this._maxRunsReached,
165
+ sessionStartedAt: this.sessionStartedAt,
166
+ runCount: this.runCount,
167
+ lastRunAt: this.lastRunAt,
168
+ lastExitCode: this.lastExitCode,
169
+ lastDuration: this.lastDuration,
170
+ nextRunAt: this.nextRunAt,
171
+ remainingDelayMs: this.remainingDelayMs,
172
+ runHistory: this.runHistory,
173
+ skippedCount: this.skippedCount,
174
+ };
175
+ }
176
+ clearMaxRunsReached() {
177
+ this._maxRunsReached = false;
178
+ this._paused = false;
179
+ this._status = "idle";
180
+ this.remainingDelayMs = null;
181
+ this.nextRunAt = null;
182
+ }
183
+ isMaxRunsReached() {
184
+ return this._maxRunsReached;
185
+ }
186
+ async waitForResume() {
187
+ const savedStatus = this._status;
188
+ this._status = savedStatus === "idle" ? "idle" : "paused";
189
+ return new Promise((resolve) => {
190
+ this.resumeResolve = resolve;
191
+ });
192
+ }
193
+ async waitForDelay(ms, signal) {
194
+ let remaining = ms;
195
+ this.remainingDelayMs = remaining;
196
+ let announced = false;
197
+ while (remaining > 0) {
198
+ if (this._forceRun) {
199
+ this._savedRemainingMs = remaining;
200
+ this.remainingDelayMs = null;
201
+ this.nextRunAt = null;
202
+ return true;
203
+ }
204
+ if (this._resetSchedule) {
205
+ this._resetSchedule = false;
206
+ remaining = ms;
207
+ this.remainingDelayMs = remaining;
208
+ announced = false;
209
+ }
210
+ if (this._paused) {
211
+ if (this._status !== "idle") {
212
+ this._status = "paused";
213
+ }
214
+ this.emit("paused");
215
+ await this.waitForResume();
216
+ announced = false;
217
+ if (signal.aborted) {
218
+ this.remainingDelayMs = null;
219
+ return false;
220
+ }
221
+ if (this._resetSchedule) {
222
+ this._resetSchedule = false;
223
+ remaining = ms;
224
+ this.remainingDelayMs = remaining;
225
+ }
226
+ }
227
+ if (!announced) {
228
+ this._status = "waiting";
229
+ this.nextRunAt = new Date(Date.now() + remaining).toISOString();
230
+ this.emit("waiting");
231
+ announced = true;
232
+ }
233
+ const chunk = Math.min(remaining, SLEEP_CHUNK_MS);
234
+ const startedAt = Date.now();
235
+ try {
236
+ await sleep(chunk, signal);
237
+ }
238
+ catch {
239
+ this.remainingDelayMs = null;
240
+ return false;
241
+ }
242
+ remaining = Math.max(0, remaining - (Date.now() - startedAt));
243
+ this.remainingDelayMs = remaining;
244
+ }
245
+ this.remainingDelayMs = null;
246
+ this.nextRunAt = null;
247
+ return true;
248
+ }
249
+ async run() {
250
+ const signal = this.abortController.signal;
251
+ let isFirstRun = true;
252
+ try {
253
+ while (!signal.aborted) {
254
+ if (this.options.maxRuns !== null && this.runCount >= this.options.maxRuns) {
255
+ this._maxRunsReached = true;
256
+ this._paused = true;
257
+ this._status = "paused";
258
+ this.emit("paused");
259
+ return;
260
+ }
261
+ if (isFirstRun && this._paused) {
262
+ await this.waitForResume();
263
+ if (signal.aborted)
264
+ break;
265
+ }
266
+ if (isFirstRun && this.nextRunAt) {
267
+ const delay = Math.max(0, new Date(this.nextRunAt).getTime() - Date.now());
268
+ if (delay > 0) {
269
+ const completed = await this.waitForDelay(delay, signal);
270
+ if (!completed) {
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ else if (isFirstRun && !this.options.immediate) {
276
+ const completed = await this.waitForDelay(this.options.interval, signal);
277
+ if (!completed) {
278
+ break;
279
+ }
280
+ }
281
+ isFirstRun = false;
282
+ if (this._paused) {
283
+ await this.waitForResume();
284
+ if (signal.aborted)
285
+ break;
286
+ }
287
+ this._status = "running";
288
+ this._forceRun = false;
289
+ this.runCount++;
290
+ this.lastRunAt = new Date().toISOString();
291
+ const runStartedAtMs = Date.now();
292
+ if (this.sessionStartedAt === null) {
293
+ this.sessionStartedAt = this.lastRunAt;
294
+ }
295
+ this.nextRunAt = null;
296
+ this.emit("run:start", this.runCount);
297
+ this.logStream = rotateLogIfNeeded(this.logPath, this.logStream);
298
+ this.currentRunStartOffset = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size : 0;
299
+ this.runHistory.push({
300
+ runNumber: this.runCount,
301
+ startedAt: this.lastRunAt,
302
+ exitCode: -1,
303
+ duration: 0,
304
+ logSize: 0,
305
+ status: "running",
306
+ logOffset: this.currentRunStartOffset,
307
+ });
308
+ this.runAbortController = new AbortController();
309
+ const task = this.options.taskId ? this.taskResolver(this.options.taskId) : null;
310
+ const command = task?.command ?? this.options.command;
311
+ const commandArgs = task?.commandArgs ?? this.options.commandArgs;
312
+ const cwd = task?.cwd ?? this.options.cwd;
313
+ const result = await executeCommand(command, commandArgs, cwd, this.logStream, AbortSignal.any([signal, this.runAbortController.signal]), this.runCount);
314
+ this.runAbortController = null;
315
+ this.lastExitCode = result.exitCode;
316
+ this.lastDuration = result.duration;
317
+ const logSize = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size - this.currentRunStartOffset : 0;
318
+ const runningRecord = this.runHistory.find((r) => r.runNumber === this.runCount);
319
+ if (runningRecord) {
320
+ runningRecord.exitCode = result.exitCode;
321
+ runningRecord.duration = result.duration;
322
+ runningRecord.logSize = Math.max(0, logSize);
323
+ runningRecord.status = "completed";
324
+ }
325
+ else {
326
+ this.runHistory.push({
327
+ runNumber: this.runCount,
328
+ startedAt: this.lastRunAt,
329
+ exitCode: result.exitCode,
330
+ duration: result.duration,
331
+ logSize: Math.max(0, logSize),
332
+ status: "completed",
333
+ logOffset: this.currentRunStartOffset,
334
+ });
335
+ }
336
+ const chainTargetId = result.exitCode === 0 ? task?.onSuccessTaskId : task?.onFailureTaskId;
337
+ if (chainTargetId) {
338
+ const chainTask = this.taskResolver(chainTargetId);
339
+ if (chainTask) {
340
+ const chainGroupId = crypto.randomUUID().slice(0, 8);
341
+ const mainRecord = this.runHistory[this.runHistory.length - 1];
342
+ if (mainRecord)
343
+ mainRecord.chainGroupId = chainGroupId;
344
+ const chainStartedAt = new Date().toISOString();
345
+ const chainOffset = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size : 0;
346
+ this.runHistory.push({
347
+ runNumber: this.runCount,
348
+ startedAt: chainStartedAt,
349
+ exitCode: -1,
350
+ duration: 0,
351
+ logSize: 0,
352
+ status: "running",
353
+ logOffset: chainOffset,
354
+ chainGroupId,
355
+ chainName: chainTask.name,
356
+ });
357
+ const chainResult = await executeCommand(chainTask.command, chainTask.commandArgs, chainTask.cwd, this.logStream, signal, this.runCount);
358
+ const chainLogSize = fs.existsSync(this.logPath) ? fs.statSync(this.logPath).size - chainOffset : 0;
359
+ const chainRecord = this.runHistory.find((r) => r.chainGroupId === chainGroupId && r.status === "running");
360
+ if (chainRecord) {
361
+ chainRecord.exitCode = chainResult.exitCode;
362
+ chainRecord.duration = chainResult.duration;
363
+ chainRecord.logSize = Math.max(0, chainLogSize);
364
+ chainRecord.status = "completed";
365
+ }
366
+ this.lastExitCode = chainResult.exitCode;
367
+ this.lastDuration = (this.lastDuration ?? 0) + chainResult.duration;
368
+ }
369
+ }
370
+ if (this.runHistory.length > 50) {
371
+ this.runHistory = this.runHistory.slice(-50);
372
+ }
373
+ this.emit("run:end", result);
374
+ if (signal.aborted)
375
+ break;
376
+ if (this.options.maxRuns !== null && this.runCount >= this.options.maxRuns) {
377
+ this._maxRunsReached = true;
378
+ this._paused = true;
379
+ this._status = "paused";
380
+ this.remainingDelayMs = null;
381
+ this.nextRunAt = null;
382
+ this.emit("paused");
383
+ return;
384
+ }
385
+ if (this._stopAfterRun) {
386
+ this._stopAfterRun = false;
387
+ this._paused = true;
388
+ this._status = "idle";
389
+ this.remainingDelayMs = null;
390
+ this.nextRunAt = null;
391
+ this.emit("stopped");
392
+ return;
393
+ }
394
+ const saved = this._savedRemainingMs;
395
+ this._savedRemainingMs = null;
396
+ if (saved !== null) {
397
+ const remaining = Math.max(0, saved - result.duration);
398
+ if (remaining > 0) {
399
+ const completed = await this.waitForDelay(remaining, signal);
400
+ if (!completed)
401
+ break;
402
+ }
403
+ }
404
+ else {
405
+ const nextSlotMs = runStartedAtMs + this.options.interval;
406
+ const overrunMs = Date.now() - nextSlotMs;
407
+ if (overrunMs >= 0) {
408
+ const missed = Math.floor(overrunMs / this.options.interval) + 1;
409
+ this.skippedCount += missed;
410
+ const adjustedDelay = this.options.interval - (overrunMs % this.options.interval);
411
+ const completed = await this.waitForDelay(adjustedDelay, signal);
412
+ if (!completed)
413
+ break;
414
+ }
415
+ else {
416
+ const completed = await this.waitForDelay(this.options.interval, signal);
417
+ if (!completed)
418
+ break;
419
+ }
420
+ }
421
+ }
422
+ }
423
+ finally {
424
+ if (this._status !== "stopped" && this._status !== "idle" && this._status !== "paused" && !this._maxRunsReached) {
425
+ this._status = "stopped";
426
+ }
427
+ }
428
+ }
429
+ }
@@ -0,0 +1,11 @@
1
+ import fs from "node:fs";
2
+ import { getDataDir, getDaemonLogFile } from "../config/paths.js";
3
+ export function daemonLog(message) {
4
+ try {
5
+ fs.mkdirSync(getDataDir(), { recursive: true });
6
+ fs.appendFileSync(getDaemonLogFile(), `${new Date().toISOString()} ${message}\n`);
7
+ }
8
+ catch {
9
+ // operational logging is best effort
10
+ }
11
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import { LoopManager } from "./manager.js";
2
+ import { TaskManager } from "./task-manager.js";
3
+ import { IpcServer } from "./server.js";
4
+ import { writeDaemonPid, removeDaemonPid, writeDaemonSignature, removeDaemonSignature, computeCodeSignature, } from "./state.js";
5
+ import { t } from "../i18n/index.js";
6
+ import { daemonLog } from "./daemon-log.js";
7
+ async function main() {
8
+ const taskManager = new TaskManager();
9
+ taskManager.init();
10
+ const manager = new LoopManager(taskManager);
11
+ const server = new IpcServer(manager, taskManager);
12
+ try {
13
+ await server.listen();
14
+ }
15
+ catch (err) {
16
+ daemonLog(`listen failed (another daemon already holds the socket): ${String(err)}`);
17
+ process.exit(0);
18
+ }
19
+ manager.init();
20
+ writeDaemonPid(process.pid);
21
+ writeDaemonSignature(computeCodeSignature());
22
+ daemonLog(`started pid=${process.pid}`);
23
+ const cleanup = async () => {
24
+ daemonLog(`shutting down pid=${process.pid}`);
25
+ removeDaemonPid();
26
+ removeDaemonSignature();
27
+ await manager.shutdown();
28
+ await server.close();
29
+ process.exit(0);
30
+ };
31
+ process.on("SIGINT", cleanup);
32
+ process.on("SIGTERM", cleanup);
33
+ process.on("uncaughtException", (err) => {
34
+ daemonLog(`uncaught exception: ${String(err)}`);
35
+ console.error(t("errors.daemonUncaught"), err);
36
+ cleanup();
37
+ });
38
+ }
39
+ main().catch((err) => {
40
+ daemonLog(`failed to start: ${String(err)}`);
41
+ console.error(t("errors.daemonFailed"), err);
42
+ process.exit(1);
43
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7D,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,OAAO,CAAC,IAAI,EAAE,CAAC;IAEf,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IAEtB,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAE5B,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,eAAe,EAAE,CAAC;QAClB,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE;QACtC,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { LoopOptions, LoopMeta } from "../types.js";
2
+ export declare class LoopManager {
3
+ private loops;
4
+ init(): void;
5
+ start(options: LoopOptions, intervalHuman: string): string;
6
+ list(): LoopMeta[];
7
+ status(id: string): LoopMeta | null;
8
+ pause(id: string): boolean;
9
+ resume(id: string): boolean;
10
+ delete(id: string): Promise<boolean>;
11
+ getLogPath(id: string): string | null;
12
+ shutdown(): Promise<void>;
13
+ private wireEvents;
14
+ private persist;
15
+ private buildMeta;
16
+ }
17
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/daemon/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAczD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAiC;IAE9C,IAAI,IAAI,IAAI;IAiCZ,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IAW1D,IAAI,IAAI,QAAQ,EAAE;IAQlB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAMnC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAQ1B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAQrB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAS/B,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,OAAO;IAsBf,OAAO,CAAC,SAAS;CAelB"}