@masslessai/push-todo 3.10.9 → 4.0.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.
- package/lib/daemon-health.js +1 -1
- package/lib/daemon.js +58 -7
- package/package.json +1 -1
package/lib/daemon-health.js
CHANGED
|
@@ -125,7 +125,7 @@ function killOrphanedDaemons() {
|
|
|
125
125
|
const trackedPid = existsSync(PID_FILE)
|
|
126
126
|
? parseInt(readFileSync(PID_FILE, 'utf8').trim(), 10)
|
|
127
127
|
: null;
|
|
128
|
-
const output = execFileSync('pgrep', ['-f', '
|
|
128
|
+
const output = execFileSync('pgrep', ['-f', 'push-todo.*daemon\\.js'], {
|
|
129
129
|
encoding: 'utf8',
|
|
130
130
|
timeout: 5000
|
|
131
131
|
}).trim();
|
package/lib/daemon.js
CHANGED
|
@@ -78,6 +78,7 @@ const PID_FILE = join(PUSH_DIR, 'daemon.pid');
|
|
|
78
78
|
const LOG_FILE = join(PUSH_DIR, 'daemon.log');
|
|
79
79
|
const STATUS_FILE = join(PUSH_DIR, 'daemon_status.json');
|
|
80
80
|
const VERSION_FILE = join(PUSH_DIR, 'daemon.version');
|
|
81
|
+
const LOCK_FILE = join(PUSH_DIR, 'daemon.lock');
|
|
81
82
|
const CONFIG_FILE = join(CONFIG_DIR, 'config');
|
|
82
83
|
const MACHINE_ID_FILE = join(CONFIG_DIR, 'machine_id');
|
|
83
84
|
const REGISTRY_FILE = join(CONFIG_DIR, 'projects.json');
|
|
@@ -90,6 +91,14 @@ const LOG_BACKUP_COUNT = 3;
|
|
|
90
91
|
const runningTasks = new Map(); // displayNumber -> taskInfo
|
|
91
92
|
const taskDetails = new Map(); // displayNumber -> details
|
|
92
93
|
const completedToday = [];
|
|
94
|
+
const COMPLETED_TODAY_MAX = 100;
|
|
95
|
+
|
|
96
|
+
function trackCompleted(entry) {
|
|
97
|
+
completedToday.push(entry);
|
|
98
|
+
if (completedToday.length > COMPLETED_TODAY_MAX) {
|
|
99
|
+
completedToday.splice(0, completedToday.length - COMPLETED_TODAY_MAX);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
93
102
|
const taskLastOutput = new Map(); // displayNumber -> timestamp
|
|
94
103
|
const taskStdoutBuffer = new Map(); // displayNumber -> lines[]
|
|
95
104
|
const taskStderrBuffer = new Map(); // displayNumber -> lines[]
|
|
@@ -126,7 +135,7 @@ function rotateLogs() {
|
|
|
126
135
|
|
|
127
136
|
function log(message, level = 'INFO') {
|
|
128
137
|
const timestamp = new Date().toISOString();
|
|
129
|
-
const line = `[${timestamp}] [${level}] ${message}\n`;
|
|
138
|
+
const line = `[${timestamp}] [PID:${process.pid}] [${level}] ${message}\n`;
|
|
130
139
|
|
|
131
140
|
if (process.env.PUSH_DAEMON !== '1') {
|
|
132
141
|
process.stdout.write(line);
|
|
@@ -141,6 +150,38 @@ function logError(message) {
|
|
|
141
150
|
log(message, 'ERROR');
|
|
142
151
|
}
|
|
143
152
|
|
|
153
|
+
// ==================== Lock File ====================
|
|
154
|
+
|
|
155
|
+
function isProcessRunning(pid) {
|
|
156
|
+
try {
|
|
157
|
+
process.kill(pid, 0);
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function acquireLock() {
|
|
165
|
+
try {
|
|
166
|
+
if (existsSync(LOCK_FILE)) {
|
|
167
|
+
const holderPid = parseInt(readFileSync(LOCK_FILE, 'utf8').trim(), 10);
|
|
168
|
+
if (!isNaN(holderPid) && holderPid !== process.pid && isProcessRunning(holderPid)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
// Holder is dead or is us — clean up stale lock
|
|
172
|
+
try { unlinkSync(LOCK_FILE); } catch {}
|
|
173
|
+
}
|
|
174
|
+
writeFileSync(LOCK_FILE, String(process.pid));
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function releaseLock() {
|
|
182
|
+
try { unlinkSync(LOCK_FILE); } catch {}
|
|
183
|
+
}
|
|
184
|
+
|
|
144
185
|
// ==================== Mac Notifications ====================
|
|
145
186
|
|
|
146
187
|
function sendMacNotification(title, message, sound = 'default') {
|
|
@@ -1025,7 +1066,7 @@ async function autoHealExistingWork(displayNumber, summary, projectPath, taskId)
|
|
|
1025
1066
|
}
|
|
1026
1067
|
}
|
|
1027
1068
|
|
|
1028
|
-
|
|
1069
|
+
trackCompleted({
|
|
1029
1070
|
displayNumber, summary,
|
|
1030
1071
|
completedAt: new Date().toISOString(),
|
|
1031
1072
|
duration: 0, status, prUrl
|
|
@@ -1062,7 +1103,7 @@ async function autoHealExistingWork(displayNumber, summary, projectPath, taskId)
|
|
|
1062
1103
|
}
|
|
1063
1104
|
}
|
|
1064
1105
|
|
|
1065
|
-
|
|
1106
|
+
trackCompleted({
|
|
1066
1107
|
displayNumber, summary,
|
|
1067
1108
|
completedAt: new Date().toISOString(),
|
|
1068
1109
|
duration: 0, status, prUrl
|
|
@@ -1079,7 +1120,7 @@ async function autoHealExistingWork(displayNumber, summary, projectPath, taskId)
|
|
|
1079
1120
|
await updateTaskStatus(displayNumber, 'session_finished', {
|
|
1080
1121
|
summary: executionSummary
|
|
1081
1122
|
});
|
|
1082
|
-
|
|
1123
|
+
trackCompleted({
|
|
1083
1124
|
displayNumber, summary,
|
|
1084
1125
|
completedAt: new Date().toISOString(),
|
|
1085
1126
|
duration: 0, status: 'session_finished', prUrl: newPrUrl
|
|
@@ -1585,7 +1626,7 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1585
1626
|
}
|
|
1586
1627
|
}
|
|
1587
1628
|
|
|
1588
|
-
|
|
1629
|
+
trackCompleted({
|
|
1589
1630
|
displayNumber,
|
|
1590
1631
|
summary,
|
|
1591
1632
|
completedAt: new Date().toISOString(),
|
|
@@ -1621,7 +1662,7 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1621
1662
|
);
|
|
1622
1663
|
}
|
|
1623
1664
|
|
|
1624
|
-
|
|
1665
|
+
trackCompleted({
|
|
1625
1666
|
displayNumber,
|
|
1626
1667
|
summary,
|
|
1627
1668
|
completedAt: new Date().toISOString(),
|
|
@@ -1765,7 +1806,7 @@ async function checkTimeouts() {
|
|
|
1765
1806
|
);
|
|
1766
1807
|
}
|
|
1767
1808
|
|
|
1768
|
-
|
|
1809
|
+
trackCompleted({
|
|
1769
1810
|
displayNumber,
|
|
1770
1811
|
summary: info.summary || 'Unknown task',
|
|
1771
1812
|
completedAt: new Date().toISOString(),
|
|
@@ -2043,6 +2084,7 @@ async function cleanup() {
|
|
|
2043
2084
|
}
|
|
2044
2085
|
|
|
2045
2086
|
// Clean up files
|
|
2087
|
+
releaseLock();
|
|
2046
2088
|
try { unlinkSync(PID_FILE); } catch {}
|
|
2047
2089
|
|
|
2048
2090
|
// Update status
|
|
@@ -2072,6 +2114,15 @@ mkdirSync(CONFIG_DIR, { recursive: true });
|
|
|
2072
2114
|
// Rotate logs
|
|
2073
2115
|
rotateLogs();
|
|
2074
2116
|
|
|
2117
|
+
// Acquire lock (prevents zombie daemons from racing)
|
|
2118
|
+
if (!acquireLock()) {
|
|
2119
|
+
const holderPid = parseInt(readFileSync(LOCK_FILE, 'utf8').trim(), 10);
|
|
2120
|
+
// Log to file directly since log() may not work yet
|
|
2121
|
+
const msg = `[${new Date().toISOString()}] [PID:${process.pid}] [INFO] Another daemon (PID ${holderPid}) holds the lock. Exiting.\n`;
|
|
2122
|
+
try { appendFileSync(LOG_FILE, msg); } catch {}
|
|
2123
|
+
process.exit(0);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2075
2126
|
// Write PID file
|
|
2076
2127
|
writeFileSync(PID_FILE, String(process.pid));
|
|
2077
2128
|
|