sisyphi 0.1.3 → 0.1.5
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/README.md +103 -33
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +114 -30
- package/dist/daemon.js.map +1 -1
- package/dist/templates/orchestrator-plugin/scripts/stop-suggest.sh +9 -1
- package/package.json +1 -1
- package/templates/orchestrator-plugin/scripts/stop-suggest.sh +9 -1
package/dist/daemon.js
CHANGED
|
@@ -48,18 +48,15 @@ import { join as join4 } from "path";
|
|
|
48
48
|
|
|
49
49
|
// src/daemon/session-manager.ts
|
|
50
50
|
import { v4 as uuidv4 } from "uuid";
|
|
51
|
-
import { existsSync as existsSync4, readdirSync as readdirSync4 } from "fs";
|
|
51
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, rmSync as rmSync2 } from "fs";
|
|
52
52
|
|
|
53
53
|
// src/daemon/state.ts
|
|
54
|
-
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, renameSync } from "fs";
|
|
55
|
-
import { dirname, join } from "path";
|
|
56
54
|
import { randomUUID } from "crypto";
|
|
55
|
+
import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
56
|
+
import { dirname, join } from "path";
|
|
57
57
|
var PLAN_SEED = `---
|
|
58
58
|
description: >
|
|
59
|
-
Living document of what still needs to happen. Write
|
|
60
|
-
here: phases, next steps, file references, open questions. Remove or collapse
|
|
61
|
-
items as they're completed so this file only reflects outstanding work. The
|
|
62
|
-
orchestrator sees this every cycle \u2014 keep it focused and current.
|
|
59
|
+
Living document of what still needs to happen. Write out ne
|
|
63
60
|
---
|
|
64
61
|
`;
|
|
65
62
|
var LOGS_SEED = `---
|
|
@@ -67,7 +64,7 @@ description: >
|
|
|
67
64
|
Session memory. Record important observations, decisions, and findings here.
|
|
68
65
|
This is your persistent memory across cycles: things you tried, what
|
|
69
66
|
worked/failed, design decisions and their rationale, gotchas discovered during
|
|
70
|
-
implementation.
|
|
67
|
+
implementation.
|
|
71
68
|
---
|
|
72
69
|
`;
|
|
73
70
|
var sessionLocks = /* @__PURE__ */ new Map();
|
|
@@ -388,10 +385,13 @@ function formatStateForOrchestrator(session) {
|
|
|
388
385
|
if (worktreeAgents.length > 0) {
|
|
389
386
|
const wtLines = worktreeAgents.map((a) => {
|
|
390
387
|
if (a.mergeStatus === "conflict") {
|
|
391
|
-
return `- ${a.id}:
|
|
388
|
+
return `- ${a.id}: CONFLICT \u2014 ${a.mergeDetails ?? "unknown"}
|
|
392
389
|
Branch: ${a.branchName}
|
|
393
390
|
Worktree: ${a.worktreePath}`;
|
|
394
391
|
}
|
|
392
|
+
if (a.mergeStatus === "no-changes") {
|
|
393
|
+
return `- ${a.id}: NO CHANGES \u2014 agent did not commit any work to branch ${a.branchName}`;
|
|
394
|
+
}
|
|
395
395
|
const status = a.mergeStatus ?? "pending";
|
|
396
396
|
return `- ${a.id}: ${status} (branch ${a.branchName})`;
|
|
397
397
|
}).join("\n");
|
|
@@ -493,7 +493,7 @@ async function handleOrchestratorYield(sessionId, cwd, nextPrompt) {
|
|
|
493
493
|
const session = getSession(cwd, sessionId);
|
|
494
494
|
const runningAgents = session.agents.filter((a) => a.status === "running");
|
|
495
495
|
if (runningAgents.length === 0) {
|
|
496
|
-
console.
|
|
496
|
+
console.log(`[sisyphus] Orchestrator yielded with no running agents for session ${sessionId}`);
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
499
|
async function handleOrchestratorComplete(sessionId, cwd, report) {
|
|
@@ -539,23 +539,17 @@ function loadWorktreeConfig(cwd) {
|
|
|
539
539
|
return null;
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
|
-
function
|
|
542
|
+
function createWorktreeShell(cwd, sessionId, agentId) {
|
|
543
543
|
const branchName = `sisyphus/${sessionId.slice(0, 8)}/${agentId}`;
|
|
544
|
-
const worktreePath = join3(worktreeBaseDir(cwd), agentId);
|
|
544
|
+
const worktreePath = join3(worktreeBaseDir(cwd), sessionId.slice(0, 8), agentId);
|
|
545
545
|
mkdirSync2(dirname2(worktreePath), { recursive: true });
|
|
546
|
+
execSafe2(`git -C ${shellQuote2(cwd)} worktree prune`);
|
|
547
|
+
if (existsSync2(worktreePath)) {
|
|
548
|
+
execSafe2(`git -C ${shellQuote2(cwd)} worktree remove --force ${shellQuote2(worktreePath)}`);
|
|
549
|
+
}
|
|
550
|
+
execSafe2(`git -C ${shellQuote2(cwd)} branch -D ${shellQuote2(branchName)}`);
|
|
546
551
|
exec2(`git -C ${shellQuote2(cwd)} branch ${shellQuote2(branchName)} HEAD`);
|
|
547
552
|
exec2(`git -C ${shellQuote2(cwd)} worktree add ${shellQuote2(worktreePath)} ${shellQuote2(branchName)}`);
|
|
548
|
-
const symlinks = [".sisyphus", ".claude"];
|
|
549
|
-
for (const entry of symlinks) {
|
|
550
|
-
const src = join3(cwd, entry);
|
|
551
|
-
if (existsSync2(src)) {
|
|
552
|
-
execSafe2(`ln -s ${shellQuote2(src)} ${shellQuote2(join3(worktreePath, entry))}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
const config = loadWorktreeConfig(cwd);
|
|
556
|
-
if (config) {
|
|
557
|
-
bootstrapWorktree(cwd, worktreePath, config);
|
|
558
|
-
}
|
|
559
553
|
return { worktreePath, branchName };
|
|
560
554
|
}
|
|
561
555
|
function bootstrapWorktree(cwd, worktreePath, config) {
|
|
@@ -615,6 +609,8 @@ function mergeWorktrees(cwd, agents) {
|
|
|
615
609
|
(a) => a.worktreePath && a.mergeStatus === "pending"
|
|
616
610
|
);
|
|
617
611
|
const results = [];
|
|
612
|
+
execSafe2(`git -C ${shellQuote2(cwd)} add .sisyphus`);
|
|
613
|
+
execSafe2(`git -C ${shellQuote2(cwd)} commit -m 'sisyphus: snapshot session state before merge'`);
|
|
618
614
|
for (const agent of pending) {
|
|
619
615
|
const branch = resolveWorktreeBranch(cwd, agent.worktreePath);
|
|
620
616
|
if (!branch) {
|
|
@@ -637,8 +633,10 @@ function mergeWorktrees(cwd, agents) {
|
|
|
637
633
|
results.push({ agentId: agent.id, name: agent.name, status: "merged" });
|
|
638
634
|
} catch (err) {
|
|
639
635
|
execSafe2(`git -C ${shellQuote2(cwd)} merge --abort`);
|
|
640
|
-
const
|
|
641
|
-
const
|
|
636
|
+
const errObj = err;
|
|
637
|
+
const stdout = errObj.stdout ? (typeof errObj.stdout === "string" ? errObj.stdout : errObj.stdout.toString("utf-8")).trim() : "";
|
|
638
|
+
const stderr = errObj.stderr ? (typeof errObj.stderr === "string" ? errObj.stderr : errObj.stderr.toString("utf-8")).trim() : "";
|
|
639
|
+
const conflictDetails = stdout || stderr || (err instanceof Error ? err.message : String(err));
|
|
642
640
|
results.push({ agentId: agent.id, name: agent.name, status: "conflict", conflictDetails });
|
|
643
641
|
}
|
|
644
642
|
}
|
|
@@ -687,7 +685,7 @@ Task: {{INSTRUCTION}}`;
|
|
|
687
685
|
if (worktreeContext) {
|
|
688
686
|
worktreeBlock = [
|
|
689
687
|
"## Worktree Context",
|
|
690
|
-
`You are working in
|
|
688
|
+
`You are working in an isolated git worktree on branch \`${worktreeContext.branchName}\`.`,
|
|
691
689
|
`If you start any services that require ports, add ${worktreeContext.offset} to the default port.`
|
|
692
690
|
].join("\n");
|
|
693
691
|
}
|
|
@@ -705,7 +703,7 @@ async function spawnAgent(opts) {
|
|
|
705
703
|
let branchName;
|
|
706
704
|
let worktreeContext;
|
|
707
705
|
if (opts.worktree) {
|
|
708
|
-
const wt =
|
|
706
|
+
const wt = createWorktreeShell(cwd, sessionId, agentId);
|
|
709
707
|
worktreePath = wt.worktreePath;
|
|
710
708
|
branchName = wt.branchName;
|
|
711
709
|
paneCwd = worktreePath;
|
|
@@ -728,7 +726,7 @@ async function spawnAgent(opts) {
|
|
|
728
726
|
].join(" && ");
|
|
729
727
|
const agentFlag = agentType ? ` --agent ${shellQuote3(agentType)}` : "";
|
|
730
728
|
const claudeCmd = `claude --dangerously-skip-permissions --plugin-dir "${pluginPath}"${agentFlag} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote3(instruction)}`;
|
|
731
|
-
|
|
729
|
+
const fullCmd = `${bannerCmd} ${envExports} && ${claudeCmd}`;
|
|
732
730
|
const agent = {
|
|
733
731
|
id: agentId,
|
|
734
732
|
name,
|
|
@@ -743,6 +741,24 @@ async function spawnAgent(opts) {
|
|
|
743
741
|
...worktreePath ? { worktreePath, branchName, mergeStatus: "pending" } : {}
|
|
744
742
|
};
|
|
745
743
|
await addAgent(cwd, sessionId, agent);
|
|
744
|
+
if (opts.worktree && worktreePath) {
|
|
745
|
+
const config = loadWorktreeConfig(cwd);
|
|
746
|
+
if (config) {
|
|
747
|
+
const wtPath = worktreePath;
|
|
748
|
+
setImmediate(() => {
|
|
749
|
+
try {
|
|
750
|
+
bootstrapWorktree(cwd, wtPath, config);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
console.error(`[sisyphus] worktree bootstrap failed for ${agentId}: ${err instanceof Error ? err.message : err}`);
|
|
753
|
+
}
|
|
754
|
+
sendKeys(paneId, fullCmd);
|
|
755
|
+
});
|
|
756
|
+
} else {
|
|
757
|
+
sendKeys(paneId, fullCmd);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
sendKeys(paneId, fullCmd);
|
|
761
|
+
}
|
|
746
762
|
return agent;
|
|
747
763
|
}
|
|
748
764
|
function nextReportNumber(cwd, sessionId, agentId) {
|
|
@@ -914,8 +930,44 @@ async function startSession(task, cwd, tmuxSession, windowId) {
|
|
|
914
930
|
trackSession(sessionId, cwd, tmuxSession);
|
|
915
931
|
await spawnOrchestrator(sessionId, cwd, windowId);
|
|
916
932
|
updateTrackedWindow(sessionId, windowId);
|
|
933
|
+
pruneOldSessions(cwd);
|
|
917
934
|
return session;
|
|
918
935
|
}
|
|
936
|
+
var PRUNE_KEEP_COUNT = 10;
|
|
937
|
+
var PRUNE_KEEP_DAYS = 7;
|
|
938
|
+
function pruneOldSessions(cwd) {
|
|
939
|
+
try {
|
|
940
|
+
const dir = sessionsDir(cwd);
|
|
941
|
+
if (!existsSync4(dir)) return;
|
|
942
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
943
|
+
const candidates = [];
|
|
944
|
+
for (const entry of entries) {
|
|
945
|
+
if (!entry.isDirectory()) continue;
|
|
946
|
+
try {
|
|
947
|
+
const session = getSession(cwd, entry.name);
|
|
948
|
+
if (session.status === "active" || session.status === "paused") continue;
|
|
949
|
+
candidates.push({ id: session.id, createdAt: new Date(session.createdAt).getTime() });
|
|
950
|
+
} catch {
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (candidates.length <= PRUNE_KEEP_COUNT) return;
|
|
954
|
+
candidates.sort((a, b) => b.createdAt - a.createdAt);
|
|
955
|
+
const cutoff = Date.now() - PRUNE_KEEP_DAYS * 24 * 60 * 60 * 1e3;
|
|
956
|
+
const keep = /* @__PURE__ */ new Set();
|
|
957
|
+
for (let i = 0; i < Math.min(PRUNE_KEEP_COUNT, candidates.length); i++) {
|
|
958
|
+
keep.add(candidates[i].id);
|
|
959
|
+
}
|
|
960
|
+
for (const c of candidates) {
|
|
961
|
+
if (c.createdAt >= cutoff) keep.add(c.id);
|
|
962
|
+
}
|
|
963
|
+
for (const c of candidates) {
|
|
964
|
+
if (keep.has(c.id)) continue;
|
|
965
|
+
rmSync2(sessionDir(cwd, c.id), { recursive: true, force: true });
|
|
966
|
+
}
|
|
967
|
+
} catch (err) {
|
|
968
|
+
console.error("[sisyphus] Session pruning failed:", err);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
919
971
|
async function resumeSession(sessionId, cwd, tmuxSession, windowId, message) {
|
|
920
972
|
const session = getSession(cwd, sessionId);
|
|
921
973
|
if (session.status !== "active") {
|
|
@@ -973,14 +1025,17 @@ function listSessions(cwd) {
|
|
|
973
1025
|
}
|
|
974
1026
|
return sessions;
|
|
975
1027
|
}
|
|
1028
|
+
var pendingRespawns = /* @__PURE__ */ new Set();
|
|
976
1029
|
function onAllAgentsDone2(sessionId, cwd, windowId) {
|
|
1030
|
+
if (pendingRespawns.has(sessionId)) return;
|
|
977
1031
|
const session = getSession(cwd, sessionId);
|
|
978
1032
|
if (session.status !== "active") return;
|
|
1033
|
+
pendingRespawns.add(sessionId);
|
|
979
1034
|
const worktreeAgents = session.agents.filter((a) => a.worktreePath && a.mergeStatus === "pending");
|
|
980
1035
|
if (worktreeAgents.length > 0) {
|
|
981
1036
|
const results = mergeWorktrees(cwd, worktreeAgents);
|
|
982
1037
|
for (const result of results) {
|
|
983
|
-
const mergeStatus = result.status
|
|
1038
|
+
const mergeStatus = result.status;
|
|
984
1039
|
updateAgent(cwd, sessionId, result.agentId, {
|
|
985
1040
|
mergeStatus,
|
|
986
1041
|
mergeDetails: result.conflictDetails
|
|
@@ -988,6 +1043,7 @@ function onAllAgentsDone2(sessionId, cwd, windowId) {
|
|
|
988
1043
|
}
|
|
989
1044
|
}
|
|
990
1045
|
setTimeout(() => {
|
|
1046
|
+
pendingRespawns.delete(sessionId);
|
|
991
1047
|
spawnOrchestrator(sessionId, cwd, windowId).then(() => updateTrackedWindow(sessionId, windowId)).catch((err) => console.error(`[sisyphus] Failed to respawn orchestrator for session ${sessionId}:`, err));
|
|
992
1048
|
}, 2e3);
|
|
993
1049
|
}
|
|
@@ -1016,7 +1072,19 @@ async function handleReport(cwd, sessionId, agentId, content) {
|
|
|
1016
1072
|
await handleAgentReport(cwd, sessionId, agentId, content);
|
|
1017
1073
|
}
|
|
1018
1074
|
async function handleYield(sessionId, cwd, nextPrompt) {
|
|
1075
|
+
const pre = getSession(cwd, sessionId);
|
|
1076
|
+
if (pre.status === "paused") {
|
|
1077
|
+
await updateSessionStatus(cwd, sessionId, "active");
|
|
1078
|
+
}
|
|
1019
1079
|
await handleOrchestratorYield(sessionId, cwd, nextPrompt);
|
|
1080
|
+
const session = getSession(cwd, sessionId);
|
|
1081
|
+
const hasRunningAgents = session.agents.some((a) => a.status === "running");
|
|
1082
|
+
if (!hasRunningAgents) {
|
|
1083
|
+
const windowId = getWindowId(sessionId);
|
|
1084
|
+
if (windowId) {
|
|
1085
|
+
onAllAgentsDone2(sessionId, cwd, windowId);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1020
1088
|
}
|
|
1021
1089
|
async function handleComplete(sessionId, cwd, report) {
|
|
1022
1090
|
await handleOrchestratorComplete(sessionId, cwd, report);
|
|
@@ -1415,6 +1483,11 @@ switch (command) {
|
|
|
1415
1483
|
const wait = Date.now() + 500;
|
|
1416
1484
|
while (Date.now() < wait) {
|
|
1417
1485
|
}
|
|
1486
|
+
const respawnedPid = readPid();
|
|
1487
|
+
if (respawnedPid) {
|
|
1488
|
+
console.log(`[sisyphus] Daemon restarted (pid ${respawnedPid}) by process manager`);
|
|
1489
|
+
break;
|
|
1490
|
+
}
|
|
1418
1491
|
startDaemon().catch((err) => {
|
|
1419
1492
|
console.error("[sisyphus] Fatal error:", err);
|
|
1420
1493
|
process.exit(1);
|
|
@@ -1428,9 +1501,20 @@ switch (command) {
|
|
|
1428
1501
|
process.exit(1);
|
|
1429
1502
|
});
|
|
1430
1503
|
break;
|
|
1504
|
+
case "help":
|
|
1505
|
+
case "--help":
|
|
1506
|
+
case "-h":
|
|
1507
|
+
console.log("Usage: sisyphusd [command]");
|
|
1508
|
+
console.log("");
|
|
1509
|
+
console.log("Commands:");
|
|
1510
|
+
console.log(" start Start the daemon (default if no command given)");
|
|
1511
|
+
console.log(" stop Stop the running daemon");
|
|
1512
|
+
console.log(" restart Stop and restart the daemon");
|
|
1513
|
+
console.log(" help Show this help message");
|
|
1514
|
+
break;
|
|
1431
1515
|
default:
|
|
1432
1516
|
console.error(`[sisyphus] Unknown command: ${command}`);
|
|
1433
|
-
console.error("Usage: sisyphusd [start|stop|restart]");
|
|
1517
|
+
console.error("Usage: sisyphusd [start|stop|restart|help]");
|
|
1434
1518
|
process.exit(1);
|
|
1435
1519
|
}
|
|
1436
1520
|
//# sourceMappingURL=daemon.js.map
|