@masslessai/push-todo 4.0.0 → 4.0.2
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 +88 -10
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -105,6 +105,17 @@ const taskStderrBuffer = new Map(); // displayNumber -> lines[]
|
|
|
105
105
|
const taskProjectPaths = new Map(); // displayNumber -> projectPath
|
|
106
106
|
let daemonStartTime = null;
|
|
107
107
|
|
|
108
|
+
// ==================== Utilities ====================
|
|
109
|
+
|
|
110
|
+
function parseJsonField(value) {
|
|
111
|
+
if (!value) return [];
|
|
112
|
+
if (Array.isArray(value)) return value;
|
|
113
|
+
if (typeof value === 'string') {
|
|
114
|
+
try { return JSON.parse(value); } catch { return []; }
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
// ==================== Logging ====================
|
|
109
120
|
|
|
110
121
|
function rotateLogs() {
|
|
@@ -1000,6 +1011,24 @@ async function markTaskAsCompleted(displayNumber, taskId, comment) {
|
|
|
1000
1011
|
}
|
|
1001
1012
|
}
|
|
1002
1013
|
|
|
1014
|
+
async function hasApprovedConfirmation(displayNumber) {
|
|
1015
|
+
try {
|
|
1016
|
+
const response = await apiRequest(`synced-todos?display_number=${displayNumber}`);
|
|
1017
|
+
if (!response.ok) return false;
|
|
1018
|
+
const data = await response.json();
|
|
1019
|
+
const todos = data.todos || [];
|
|
1020
|
+
if (todos.length === 0) return false;
|
|
1021
|
+
const events = parseJsonField(todos[0].executionEventsJson);
|
|
1022
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1023
|
+
if (events[i].type === 'confirmation_approved') return true;
|
|
1024
|
+
if (events[i].type === 'confirmation_rejected') return false;
|
|
1025
|
+
}
|
|
1026
|
+
return false;
|
|
1027
|
+
} catch {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1003
1032
|
/**
|
|
1004
1033
|
* Auto-heal: detect if a previous execution already completed work for this task.
|
|
1005
1034
|
* Checks for existing branch commits and PRs to avoid redundant re-execution.
|
|
@@ -1386,10 +1415,37 @@ async function executeTask(task) {
|
|
|
1386
1415
|
|
|
1387
1416
|
taskProjectPaths.set(displayNumber, projectPath);
|
|
1388
1417
|
|
|
1418
|
+
// Build attachment context for the prompt (screenshots, links)
|
|
1419
|
+
let attachmentContext = '';
|
|
1420
|
+
try {
|
|
1421
|
+
const links = parseJsonField(task.linkAttachmentsJson || task.linkAttachments || task.link_attachments);
|
|
1422
|
+
const screenshots = parseJsonField(task.screenshotAttachmentsJson || task.screenshotAttachments || task.screenshot_attachments);
|
|
1423
|
+
const contextApp = task.contextApp || task.context_app || null;
|
|
1424
|
+
|
|
1425
|
+
const parts = [];
|
|
1426
|
+
if (contextApp) {
|
|
1427
|
+
parts.push(`Context app: ${contextApp}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (links.length > 0) {
|
|
1430
|
+
parts.push('Links:\n' + links.map(l => ` - ${l.url}${l.title ? ` (${l.title})` : ''}`).join('\n'));
|
|
1431
|
+
}
|
|
1432
|
+
if (screenshots.length > 0) {
|
|
1433
|
+
parts.push('Screenshots:\n' + screenshots.map(s =>
|
|
1434
|
+
` - ${s.imageFilename || s.image_filename}${s.sourceApp ? ` (from ${s.sourceApp})` : ''}`
|
|
1435
|
+
).join('\n'));
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (parts.length > 0) {
|
|
1439
|
+
attachmentContext = '\n\nAttachments:\n' + parts.join('\n');
|
|
1440
|
+
}
|
|
1441
|
+
} catch {
|
|
1442
|
+
// Non-critical — continue without attachment context
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1389
1445
|
// Build prompt
|
|
1390
1446
|
const prompt = `Work on Push task #${displayNumber}:
|
|
1391
1447
|
|
|
1392
|
-
${content}
|
|
1448
|
+
${content}${attachmentContext}
|
|
1393
1449
|
|
|
1394
1450
|
IMPORTANT:
|
|
1395
1451
|
1. If you need to understand the codebase, start by reading the CLAUDE.md file if it exists.
|
|
@@ -1405,6 +1461,7 @@ IMPORTANT:
|
|
|
1405
1461
|
'Bash(git *)',
|
|
1406
1462
|
'Bash(npm *)', 'Bash(npx *)', 'Bash(yarn *)',
|
|
1407
1463
|
'Bash(python *)', 'Bash(python3 *)', 'Bash(pip *)', 'Bash(pip3 *)',
|
|
1464
|
+
'Bash(push-todo *)',
|
|
1408
1465
|
'Task'
|
|
1409
1466
|
].join(',');
|
|
1410
1467
|
|
|
@@ -1614,15 +1671,36 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1614
1671
|
}
|
|
1615
1672
|
}
|
|
1616
1673
|
|
|
1617
|
-
// Auto-complete task
|
|
1674
|
+
// Auto-complete task (configurable, default ON)
|
|
1618
1675
|
const taskId = info.taskId;
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1676
|
+
let autoCompleted = false;
|
|
1677
|
+
|
|
1678
|
+
if (getAutoCompleteEnabled() && taskId) {
|
|
1679
|
+
// Path 1: PR merge (code tasks)
|
|
1680
|
+
if (merged) {
|
|
1681
|
+
const comment = semanticSummary
|
|
1682
|
+
? `${semanticSummary} (${durationStr} on ${machineName})`
|
|
1683
|
+
: `Completed in ${durationStr} on ${machineName}`;
|
|
1684
|
+
autoCompleted = await markTaskAsCompleted(displayNumber, taskId, comment);
|
|
1685
|
+
if (!autoCompleted) {
|
|
1686
|
+
logError(`Task #${displayNumber}: Failed to mark as completed after merge`);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// Path 2: Confirmation approval (content tasks — tweets, emails, etc.)
|
|
1691
|
+
// If user approved a confirmation AND Claude exited cleanly, the task is done.
|
|
1692
|
+
if (!autoCompleted && !merged) {
|
|
1693
|
+
const approved = await hasApprovedConfirmation(displayNumber);
|
|
1694
|
+
if (approved) {
|
|
1695
|
+
log(`Task #${displayNumber}: user-approved confirmation detected, auto-completing`);
|
|
1696
|
+
const comment = semanticSummary
|
|
1697
|
+
? `${semanticSummary} (${durationStr} on ${machineName})`
|
|
1698
|
+
: `Confirmed and completed in ${durationStr} on ${machineName}`;
|
|
1699
|
+
autoCompleted = await markTaskAsCompleted(displayNumber, taskId, comment);
|
|
1700
|
+
if (!autoCompleted) {
|
|
1701
|
+
logError(`Task #${displayNumber}: Failed to mark as completed after confirmation`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1626
1704
|
}
|
|
1627
1705
|
}
|
|
1628
1706
|
|
|
@@ -1631,7 +1709,7 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1631
1709
|
summary,
|
|
1632
1710
|
completedAt: new Date().toISOString(),
|
|
1633
1711
|
duration,
|
|
1634
|
-
status:
|
|
1712
|
+
status: autoCompleted ? 'completed' : 'session_finished',
|
|
1635
1713
|
prUrl,
|
|
1636
1714
|
sessionId
|
|
1637
1715
|
});
|