loop-task 2.1.7 → 2.1.9

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.
@@ -22,6 +22,7 @@ export class LoopController extends EventEmitter {
22
22
  this.remainingDelayMs = null;
23
23
  this.logStream = null;
24
24
  this.loopPromise = null;
25
+ this._loopActive = false;
25
26
  this.sessionStartedAt = null;
26
27
  this.runHistory = [];
27
28
  this.currentRunStartOffset = 0;
@@ -50,11 +51,14 @@ export class LoopController extends EventEmitter {
50
51
  return this._status;
51
52
  }
52
53
  start() {
54
+ if (this._loopActive)
55
+ return;
56
+ this._loopActive = true;
53
57
  this.skippedCount = 0;
54
58
  this.logStream?.end();
55
59
  this.abortController = new AbortController();
56
60
  this.logStream = fs.createWriteStream(this.logPath, { flags: "a" });
57
- this.loopPromise = this.run();
61
+ this.loopPromise = this.run().finally(() => { this._loopActive = false; });
58
62
  if (this.sessionStartedAt === null) {
59
63
  this.sessionStartedAt = new Date().toISOString();
60
64
  }
@@ -111,7 +115,6 @@ export class LoopController extends EventEmitter {
111
115
  this.sessionStartedAt = new Date().toISOString();
112
116
  this._resetSchedule = true;
113
117
  this._paused = false;
114
- this._status = "waiting";
115
118
  const phase = this.options.offset !== null
116
119
  ? this.options.offset
117
120
  : computePhase(this.id, this.options.interval);
@@ -122,6 +125,7 @@ export class LoopController extends EventEmitter {
122
125
  this.resumeResolve = null;
123
126
  }
124
127
  this.start();
128
+ this._status = "waiting";
125
129
  this.emit("resumed");
126
130
  return true;
127
131
  }
@@ -136,8 +140,8 @@ export class LoopController extends EventEmitter {
136
140
  if (needsStart) {
137
141
  this._stopAfterRun = true;
138
142
  this._paused = false;
139
- this._status = "running";
140
143
  this.start();
144
+ this._status = "running";
141
145
  }
142
146
  else {
143
147
  if (this._paused)
@@ -3,7 +3,8 @@ import { TaskManager } from "./managers/task-manager.js";
3
3
  import { IpcServer } from "./server/index.js";
4
4
  import { HttpApiServer } from "./http/server.js";
5
5
  import { FileWatcher } from "./watcher/index.js";
6
- import { writeDaemonPid, removeDaemonPid, writeDaemonSignature, removeDaemonSignature, computeCodeSignature, migrateTasksToJson, migrateLoopsToJson, } from "./state/index.js";
6
+ import { writeDaemonPid, removeDaemonPid, writeDaemonSignature, removeDaemonSignature, computeCodeSignature, migrateTasksToJson, migrateLoopsToJson, setSelfWriteNotifier, } from "./state/index.js";
7
+ import { setProjectSelfWriteNotifier } from "./managers/project-manager.js";
7
8
  import { t } from "../shared/i18n/index.js";
8
9
  import { daemonLog } from "./daemon-log.js";
9
10
  async function main() {
@@ -37,6 +38,8 @@ async function main() {
37
38
  fileWatcher.setManagers(manager, taskManager, manager["projectManager"]);
38
39
  fileWatcher.start();
39
40
  daemonLog(`file watcher started for hot-reloading JSON configs`);
41
+ setSelfWriteNotifier((filePath, content) => fileWatcher.registerSelfWrite(filePath, content));
42
+ setProjectSelfWriteNotifier((filePath, content) => fileWatcher.registerSelfWrite(filePath, content));
40
43
  let shuttingDown = false;
41
44
  const cleanup = async () => {
42
45
  if (shuttingDown)
@@ -4,6 +4,10 @@ import { daemonLog } from "../daemon-log.js";
4
4
  import crypto from "node:crypto";
5
5
  import { getDataDir, projectsJson } from "../../shared/config/paths.js";
6
6
  import path from "node:path";
7
+ let selfWriteNotifier = null;
8
+ export function setProjectSelfWriteNotifier(notifier) {
9
+ selfWriteNotifier = notifier;
10
+ }
7
11
  export class ProjectManager {
8
12
  constructor() {
9
13
  this.projects = new Map();
@@ -68,7 +72,10 @@ export class ProjectManager {
68
72
  }
69
73
  saveAllProjects() {
70
74
  const all = Array.from(this.projects.values());
71
- writeFileAtomic(projectsJson(), JSON.stringify(all, null, 2));
75
+ const content = JSON.stringify(all, null, 2);
76
+ writeFileAtomic(projectsJson(), content);
77
+ if (selfWriteNotifier)
78
+ selfWriteNotifier(projectsJson(), content);
72
79
  }
73
80
  saveProject(project) {
74
81
  this.projects.set(project.id, project);
@@ -5,6 +5,10 @@ import { fileURLToPath } from "node:url";
5
5
  import { removeIfExists, writeFileAtomic } from "../../shared/fs-utils.js";
6
6
  import { getLoopsDir, getTasksDir, getLogsDir, logFile, getPidFile, getSignatureFile, getSocketPath, loopsJson, tasksJson, getDataDir, } from "../../shared/config/paths.js";
7
7
  export { getDataDir, getPidFile, getSocketPath } from "../../shared/config/paths.js";
8
+ let selfWriteNotifier = null;
9
+ export function setSelfWriteNotifier(notifier) {
10
+ selfWriteNotifier = notifier;
11
+ }
8
12
  function ensureDirs() {
9
13
  fs.mkdirSync(getDataDir(), { recursive: true });
10
14
  fs.mkdirSync(getLogsDir(), { recursive: true });
@@ -23,7 +27,10 @@ function readJsonArray(filePath) {
23
27
  }
24
28
  function writeJsonArray(filePath, items) {
25
29
  ensureDirs();
26
- writeFileAtomic(filePath, JSON.stringify(items, null, 2));
30
+ const content = JSON.stringify(items, null, 2);
31
+ writeFileAtomic(filePath, content);
32
+ if (selfWriteNotifier)
33
+ selfWriteNotifier(filePath, content);
27
34
  }
28
35
  export function migrateLoopsToJson() {
29
36
  ensureDirs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loop-task",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
5
5
  "type": "module",
6
6
  "bin": {