@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.
@@ -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', 'node.*daemon\\.js'], {
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
- completedToday.push({
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
- completedToday.push({
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
- completedToday.push({
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
- completedToday.push({
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
- completedToday.push({
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
- completedToday.push({
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "3.10.9",
3
+ "version": "4.0.0",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {