@masslessai/push-todo 4.1.7 → 4.1.8
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.js +68 -2
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -1482,11 +1482,38 @@ function maybesSendStreamProgress(displayNumber, activity) {
|
|
|
1482
1482
|
filesRead: activity.filesRead.size
|
|
1483
1483
|
}
|
|
1484
1484
|
})
|
|
1485
|
+
}).then(async (response) => {
|
|
1486
|
+
// Check for cancellation signal in response
|
|
1487
|
+
const result = await response.json().catch(() => null);
|
|
1488
|
+
if (result?.status === 'cancelling') {
|
|
1489
|
+
handleCancellation(displayNumber, 'stream_progress');
|
|
1490
|
+
}
|
|
1485
1491
|
}).catch(err => {
|
|
1486
1492
|
log(`Task #${displayNumber}: stream progress failed (non-fatal): ${err.message}`);
|
|
1487
1493
|
});
|
|
1488
1494
|
}
|
|
1489
1495
|
|
|
1496
|
+
// ==================== Cancellation (Phase 2) ====================
|
|
1497
|
+
|
|
1498
|
+
function handleCancellation(displayNumber, source) {
|
|
1499
|
+
const taskInfo = runningTasks.get(displayNumber);
|
|
1500
|
+
if (!taskInfo) return;
|
|
1501
|
+
|
|
1502
|
+
log(`Task #${displayNumber}: cancellation detected via ${source}, sending SIGTERM`);
|
|
1503
|
+
|
|
1504
|
+
try {
|
|
1505
|
+
taskInfo.process.kill('SIGTERM');
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
log(`Task #${displayNumber}: SIGTERM failed: ${err.message}`);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// Mark as cancelled so handleTaskCompletion reports the right reason
|
|
1511
|
+
updateTaskDetail(displayNumber, {
|
|
1512
|
+
phase: 'cancelled',
|
|
1513
|
+
detail: 'Cancelled by user'
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1490
1517
|
function monitorTaskStdout(displayNumber, proc) {
|
|
1491
1518
|
if (!proc.stdout) return;
|
|
1492
1519
|
|
|
@@ -1623,6 +1650,12 @@ async function sendProgressHeartbeats() {
|
|
|
1623
1650
|
}
|
|
1624
1651
|
// No status field — event-only update, won't change execution_status
|
|
1625
1652
|
})
|
|
1653
|
+
}).then(async (response) => {
|
|
1654
|
+
// Check for cancellation signal in heartbeat response
|
|
1655
|
+
const result = await response.json().catch(() => null);
|
|
1656
|
+
if (result?.status === 'cancelling') {
|
|
1657
|
+
handleCancellation(displayNumber, 'heartbeat');
|
|
1658
|
+
}
|
|
1626
1659
|
}).catch(err => {
|
|
1627
1660
|
log(`Task #${displayNumber}: heartbeat failed (non-fatal): ${err.message}`);
|
|
1628
1661
|
});
|
|
@@ -2162,12 +2195,45 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
2162
2195
|
const summary = info.summary || 'Unknown task';
|
|
2163
2196
|
const projectPath = taskProjectPaths.get(displayNumber);
|
|
2164
2197
|
|
|
2165
|
-
|
|
2198
|
+
const durationStr = duration < 60 ? `${duration}s` : `${Math.floor(duration / 60)}m ${duration % 60}s`;
|
|
2199
|
+
const wasCancelled = info.phase === 'cancelled';
|
|
2200
|
+
log(`Task #${displayNumber} ${wasCancelled ? 'cancelled' : 'completed'} with code ${exitCode} (${duration}s)`);
|
|
2201
|
+
|
|
2202
|
+
// Handle user cancellation — report as failed with clear reason, skip PR/merge/summary
|
|
2203
|
+
if (wasCancelled) {
|
|
2204
|
+
const cancelMsg = `Cancelled by user after ${durationStr}.`;
|
|
2205
|
+
await updateTaskStatus(displayNumber, 'failed', {
|
|
2206
|
+
error: cancelMsg,
|
|
2207
|
+
sessionId: taskInfo.sessionId
|
|
2208
|
+
}, info.taskId);
|
|
2209
|
+
|
|
2210
|
+
cleanupWorktree(displayNumber, projectPath);
|
|
2211
|
+
|
|
2212
|
+
trackCompleted({
|
|
2213
|
+
displayNumber,
|
|
2214
|
+
summary,
|
|
2215
|
+
completedAt: new Date().toISOString(),
|
|
2216
|
+
duration,
|
|
2217
|
+
status: 'cancelled'
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2220
|
+
// Cleanup internal tracking
|
|
2221
|
+
taskDetails.delete(displayNumber);
|
|
2222
|
+
taskLastOutput.delete(displayNumber);
|
|
2223
|
+
taskStdoutBuffer.delete(displayNumber);
|
|
2224
|
+
taskStderrBuffer.delete(displayNumber);
|
|
2225
|
+
taskProjectPaths.delete(displayNumber);
|
|
2226
|
+
taskLastHeartbeat.delete(displayNumber);
|
|
2227
|
+
taskStreamLineBuffer.delete(displayNumber);
|
|
2228
|
+
taskActivityState.delete(displayNumber);
|
|
2229
|
+
taskLastStreamProgress.delete(displayNumber);
|
|
2230
|
+
updateStatusFile();
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2166
2233
|
|
|
2167
2234
|
// Session ID: prefer pre-assigned ID (reliable), fall back to stdout extraction
|
|
2168
2235
|
const sessionId = taskInfo.sessionId || extractSessionIdFromStdout(taskInfo.process, taskStdoutBuffer.get(displayNumber) || [], displayNumber);
|
|
2169
2236
|
const worktreePath = getWorktreePath(displayNumber, projectPath);
|
|
2170
|
-
const durationStr = duration < 60 ? `${duration}s` : `${Math.floor(duration / 60)}m ${duration % 60}s`;
|
|
2171
2237
|
const machineName = getMachineName() || 'Mac';
|
|
2172
2238
|
|
|
2173
2239
|
if (sessionId) {
|