@masslessai/push-todo 4.0.5 → 4.0.7
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/SKILL.md +15 -2
- package/lib/daemon.js +17 -11
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -47,11 +47,24 @@ When this command is invoked:
|
|
|
47
47
|
- The task is queued and waiting for the daemon to pick it up
|
|
48
48
|
- Tell the user: "This task is queued and will be picked up by the daemon shortly."
|
|
49
49
|
- Do NOT start working on this task
|
|
50
|
+
- For both running and queued tasks, remind the user they can monitor or resume later:
|
|
51
|
+
```
|
|
52
|
+
To resume the daemon's session later, open a new terminal and run:
|
|
53
|
+
|
|
54
|
+
push-todo --resume <number>
|
|
55
|
+
```
|
|
50
56
|
|
|
51
57
|
8. **Check for resumable daemon sessions:**
|
|
52
58
|
- If the task output contains `**Session:** Resumable`, the daemon already ran Claude Code on this task
|
|
53
|
-
-
|
|
54
|
-
|
|
59
|
+
- **Tell the user they can resume the daemon's exact Claude session** in a separate terminal:
|
|
60
|
+
```
|
|
61
|
+
To resume the daemon's Claude session, open a new terminal and run:
|
|
62
|
+
|
|
63
|
+
push-todo --resume <number>
|
|
64
|
+
|
|
65
|
+
This cannot run inside Claude Code (sessions can't nest).
|
|
66
|
+
```
|
|
67
|
+
- Then follow the [Auto-Resume from Session Transcript](#auto-resume-from-session-transcript) procedure to load the daemon's work context into THIS session
|
|
55
68
|
- Only if the session transcript cannot be found should you begin working from scratch
|
|
56
69
|
|
|
57
70
|
9. **Load task attachments** before starting work:
|
package/lib/daemon.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* Ported from: plugins/push-todo/scripts/daemon.py
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
16
17
|
import { spawn, execSync, execFileSync } from 'child_process';
|
|
17
18
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync, unlinkSync, statSync, renameSync } from 'fs';
|
|
18
19
|
import { homedir, hostname, platform } from 'os';
|
|
@@ -547,7 +548,7 @@ async function updateTaskStatus(displayNumber, status, extra = {}, todoId = null
|
|
|
547
548
|
}
|
|
548
549
|
}
|
|
549
550
|
|
|
550
|
-
async function claimTask(displayNumber, todoId) {
|
|
551
|
+
async function claimTask(displayNumber, todoId, sessionId) {
|
|
551
552
|
const machineId = getMachineId();
|
|
552
553
|
const machineName = getMachineName();
|
|
553
554
|
|
|
@@ -566,6 +567,7 @@ async function claimTask(displayNumber, todoId) {
|
|
|
566
567
|
machineId,
|
|
567
568
|
machineName,
|
|
568
569
|
branch,
|
|
570
|
+
sessionId, // Pre-assigned session ID for reliable resume
|
|
569
571
|
atomic: true,
|
|
570
572
|
event: {
|
|
571
573
|
type: 'started',
|
|
@@ -1450,8 +1452,11 @@ async function executeTask(task) {
|
|
|
1450
1452
|
log(`Task #${displayNumber}: Project ${gitRemote} -> ${projectPath}`);
|
|
1451
1453
|
}
|
|
1452
1454
|
|
|
1455
|
+
// Pre-assign session ID so we can store it at claim time (not rely on parsing stdout)
|
|
1456
|
+
const preAssignedSessionId = randomUUID();
|
|
1457
|
+
|
|
1453
1458
|
// Atomic task claiming - must await to actually check the result
|
|
1454
|
-
if (!(await claimTask(displayNumber, taskId))) {
|
|
1459
|
+
if (!(await claimTask(displayNumber, taskId, preAssignedSessionId))) {
|
|
1455
1460
|
log(`Task #${displayNumber}: claim failed, skipping`);
|
|
1456
1461
|
return null;
|
|
1457
1462
|
}
|
|
@@ -1537,7 +1542,8 @@ async function executeTask(task) {
|
|
|
1537
1542
|
'-p', prompt,
|
|
1538
1543
|
'--allowedTools', allowedTools,
|
|
1539
1544
|
'--output-format', 'json',
|
|
1540
|
-
'--permission-mode', 'bypassPermissions'
|
|
1545
|
+
'--permission-mode', 'bypassPermissions',
|
|
1546
|
+
'--session-id', preAssignedSessionId
|
|
1541
1547
|
];
|
|
1542
1548
|
|
|
1543
1549
|
try {
|
|
@@ -1557,7 +1563,8 @@ async function executeTask(task) {
|
|
|
1557
1563
|
task,
|
|
1558
1564
|
displayNumber,
|
|
1559
1565
|
startTime: Date.now(),
|
|
1560
|
-
projectPath
|
|
1566
|
+
projectPath,
|
|
1567
|
+
sessionId: preAssignedSessionId
|
|
1561
1568
|
};
|
|
1562
1569
|
|
|
1563
1570
|
runningTasks.set(displayNumber, taskInfo);
|
|
@@ -1652,17 +1659,16 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1652
1659
|
|
|
1653
1660
|
log(`Task #${displayNumber} completed with code ${exitCode} (${duration}s)`);
|
|
1654
1661
|
|
|
1655
|
-
//
|
|
1656
|
-
const
|
|
1657
|
-
const sessionId = extractSessionIdFromStdout(taskInfo.process, buffer);
|
|
1662
|
+
// Session ID: prefer pre-assigned ID (reliable), fall back to stdout extraction
|
|
1663
|
+
const sessionId = taskInfo.sessionId || extractSessionIdFromStdout(taskInfo.process, taskStdoutBuffer.get(displayNumber) || []);
|
|
1658
1664
|
const worktreePath = getWorktreePath(displayNumber, projectPath);
|
|
1659
1665
|
const durationStr = duration < 60 ? `${duration}s` : `${Math.floor(duration / 60)}m ${duration % 60}s`;
|
|
1660
1666
|
const machineName = getMachineName() || 'Mac';
|
|
1661
1667
|
|
|
1662
1668
|
if (sessionId) {
|
|
1663
|
-
log(`Task #${displayNumber} session_id: ${sessionId}`);
|
|
1669
|
+
log(`Task #${displayNumber} session_id: ${sessionId}${taskInfo.sessionId ? ' (pre-assigned)' : ' (from stdout)'}`);
|
|
1664
1670
|
} else {
|
|
1665
|
-
log(`Task #${displayNumber} could not
|
|
1671
|
+
log(`Task #${displayNumber} could not determine session_id`);
|
|
1666
1672
|
}
|
|
1667
1673
|
|
|
1668
1674
|
if (exitCode === 0) {
|
|
@@ -1798,7 +1804,7 @@ async function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1798
1804
|
? `${failureSummary}\nExit code ${exitCode}. Ran for ${durationStr} on ${machineName}.`
|
|
1799
1805
|
: `Exit code ${exitCode}: ${stderr.slice(0, 200)}`;
|
|
1800
1806
|
|
|
1801
|
-
await updateTaskStatus(displayNumber, 'failed', { error: errorMsg }, info.taskId);
|
|
1807
|
+
await updateTaskStatus(displayNumber, 'failed', { error: errorMsg, sessionId }, info.taskId);
|
|
1802
1808
|
|
|
1803
1809
|
if (NOTIFY_ON_FAILURE) {
|
|
1804
1810
|
sendMacNotification(
|
|
@@ -1942,7 +1948,7 @@ async function checkTimeouts() {
|
|
|
1942
1948
|
runningTasks.delete(displayNumber);
|
|
1943
1949
|
|
|
1944
1950
|
const timeoutError = `Task timed out after ${duration}s (limit: ${TASK_TIMEOUT_MS / 1000}s)`;
|
|
1945
|
-
updateTaskStatus(displayNumber, 'failed', { error: timeoutError });
|
|
1951
|
+
updateTaskStatus(displayNumber, 'failed', { error: timeoutError, sessionId: taskInfo.sessionId });
|
|
1946
1952
|
|
|
1947
1953
|
if (NOTIFY_ON_FAILURE) {
|
|
1948
1954
|
sendMacNotification(
|