sisyphi 1.1.16 → 1.1.17
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/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +198 -7
- package/dist/daemon.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -37,13 +37,13 @@ import {
|
|
|
37
37
|
} from "./chunk-GSXF3TCZ.js";
|
|
38
38
|
|
|
39
39
|
// src/daemon/index.ts
|
|
40
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
40
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
|
|
41
41
|
import { execSync as execSync4 } from "child_process";
|
|
42
42
|
import { setTimeout as sleep } from "timers/promises";
|
|
43
43
|
|
|
44
44
|
// src/daemon/server.ts
|
|
45
45
|
import { createServer } from "net";
|
|
46
|
-
import { unlinkSync, existsSync as existsSync7, writeFileSync as writeFileSync5, readFileSync as
|
|
46
|
+
import { unlinkSync, existsSync as existsSync7, writeFileSync as writeFileSync5, readFileSync as readFileSync6, mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
|
|
47
47
|
import { join as join5 } from "path";
|
|
48
48
|
|
|
49
49
|
// src/daemon/session-manager.ts
|
|
@@ -632,6 +632,12 @@ function updatePaneMeta(paneTarget, updates) {
|
|
|
632
632
|
function selectLayout(windowTarget, layout = "even-horizontal") {
|
|
633
633
|
execSafe(`tmux select-layout -t "${windowTarget}" ${layout}`);
|
|
634
634
|
}
|
|
635
|
+
function setWindowOption(windowTarget, option, value) {
|
|
636
|
+
execSafe(`tmux set-option -w -t "${windowTarget}" ${option} ${shellQuote(value)}`);
|
|
637
|
+
}
|
|
638
|
+
function getSessionOption(sessionName, option) {
|
|
639
|
+
return execSafe(`tmux show-options -t "${sessionName}" -v ${option}`);
|
|
640
|
+
}
|
|
635
641
|
function configureSessionDefaults(sessionName, windowId) {
|
|
636
642
|
execSafe(`tmux set -w -t "${windowId}" pane-border-status top`);
|
|
637
643
|
execSafe(`tmux set -w -t "${windowId}" allow-rename off`);
|
|
@@ -1094,6 +1100,7 @@ var respawningSessions = /* @__PURE__ */ new Set();
|
|
|
1094
1100
|
// src/daemon/pane-monitor.ts
|
|
1095
1101
|
var monitorInterval = null;
|
|
1096
1102
|
var onAllAgentsDone = null;
|
|
1103
|
+
var onDotsUpdate = null;
|
|
1097
1104
|
var lastPollTime = 0;
|
|
1098
1105
|
var storedPollIntervalMs = 5e3;
|
|
1099
1106
|
var activeTimers = /* @__PURE__ */ new Map();
|
|
@@ -1160,6 +1167,12 @@ function getTrackedSessionIds() {
|
|
|
1160
1167
|
function setRespawnCallback(cb) {
|
|
1161
1168
|
onAllAgentsDone = cb;
|
|
1162
1169
|
}
|
|
1170
|
+
function setDotsCallback(cb) {
|
|
1171
|
+
onDotsUpdate = cb;
|
|
1172
|
+
}
|
|
1173
|
+
function getTrackedSessionEntries() {
|
|
1174
|
+
return trackedSessions.values();
|
|
1175
|
+
}
|
|
1163
1176
|
function startMonitor(pollIntervalMs = 5e3) {
|
|
1164
1177
|
if (monitorInterval) return;
|
|
1165
1178
|
storedPollIntervalMs = pollIntervalMs;
|
|
@@ -1200,6 +1213,10 @@ async function pollAllSessions() {
|
|
|
1200
1213
|
await pollSession(sessionId, cwd, windowId, increment);
|
|
1201
1214
|
}
|
|
1202
1215
|
}
|
|
1216
|
+
try {
|
|
1217
|
+
onDotsUpdate?.();
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1203
1220
|
}
|
|
1204
1221
|
async function pollSession(sessionId, cwd, windowId, increment) {
|
|
1205
1222
|
let session;
|
|
@@ -1623,6 +1640,145 @@ function sendTerminalNotification(title, message) {
|
|
|
1623
1640
|
});
|
|
1624
1641
|
}
|
|
1625
1642
|
|
|
1643
|
+
// src/daemon/status-dots.ts
|
|
1644
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1645
|
+
var CLAUDE_STATE_DIR = "/tmp/claude-tmux-state";
|
|
1646
|
+
var DOT_MAP = {
|
|
1647
|
+
"orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
|
|
1648
|
+
// yellow — orchestrator thinking
|
|
1649
|
+
"orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
|
|
1650
|
+
// red — needs your input
|
|
1651
|
+
"agents:running": { icon: "\u25C6", color: "#d4ad6a" },
|
|
1652
|
+
// yellow diamond — agents working
|
|
1653
|
+
"between-cycles": { icon: "\u25C6", color: "#5e584e" },
|
|
1654
|
+
// dim diamond — respawning
|
|
1655
|
+
"paused": { icon: "\u25CB", color: "#d47766" },
|
|
1656
|
+
// red hollow — stuck
|
|
1657
|
+
"completed": { icon: "\u25CF", color: "#a9b16e" }
|
|
1658
|
+
// green — done
|
|
1659
|
+
};
|
|
1660
|
+
function renderDots(dots) {
|
|
1661
|
+
const sorted = [...dots].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1662
|
+
return sorted.map((d) => {
|
|
1663
|
+
const { icon, color } = DOT_MAP[d.phase];
|
|
1664
|
+
return `#[fg=${color}]${icon}`;
|
|
1665
|
+
}).join("");
|
|
1666
|
+
}
|
|
1667
|
+
function readClaudeState(paneId) {
|
|
1668
|
+
const numericId = paneId.replace("%", "");
|
|
1669
|
+
try {
|
|
1670
|
+
const content = readFileSync5(`${CLAUDE_STATE_DIR}/${numericId}`, "utf-8").trim();
|
|
1671
|
+
if (content === "idle" || content === "processing" || content === "stopped") {
|
|
1672
|
+
return content;
|
|
1673
|
+
}
|
|
1674
|
+
return null;
|
|
1675
|
+
} catch {
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function detectPhase(session, orchPaneId, livePaneIds) {
|
|
1680
|
+
if (session.status === "completed") return "completed";
|
|
1681
|
+
if (session.status === "paused") return "paused";
|
|
1682
|
+
if (respawningSessions.has(session.id)) return "between-cycles";
|
|
1683
|
+
const orchAlive = orchPaneId != null && livePaneIds.has(orchPaneId);
|
|
1684
|
+
const hasRunningAgents = session.agents.some((a) => a.status === "running");
|
|
1685
|
+
if (orchAlive) {
|
|
1686
|
+
const claudeState = readClaudeState(orchPaneId);
|
|
1687
|
+
if (claudeState === "idle") {
|
|
1688
|
+
return "orchestrator:idle";
|
|
1689
|
+
}
|
|
1690
|
+
return "orchestrator:processing";
|
|
1691
|
+
}
|
|
1692
|
+
if (hasRunningAgents) return "agents:running";
|
|
1693
|
+
return "between-cycles";
|
|
1694
|
+
}
|
|
1695
|
+
var getTrackedEntries = null;
|
|
1696
|
+
function setTrackedEntriesProvider(provider) {
|
|
1697
|
+
getTrackedEntries = provider;
|
|
1698
|
+
}
|
|
1699
|
+
var COMPLETED_TTL_MS = 5 * 60 * 1e3;
|
|
1700
|
+
var completedSessions = /* @__PURE__ */ new Map();
|
|
1701
|
+
function markSessionCompleted(sessionId, createdAt, cwd) {
|
|
1702
|
+
completedSessions.set(sessionId, {
|
|
1703
|
+
createdAt,
|
|
1704
|
+
cwd,
|
|
1705
|
+
expireAt: Date.now() + COMPLETED_TTL_MS
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
function pruneCompleted() {
|
|
1709
|
+
const now = Date.now();
|
|
1710
|
+
for (const [id, entry] of completedSessions) {
|
|
1711
|
+
if (entry.expireAt < now) completedSessions.delete(id);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
var dashboardWindowCache = /* @__PURE__ */ new Map();
|
|
1715
|
+
var CACHE_TTL_MS = 3e4;
|
|
1716
|
+
function getDashboardWindowId(cwd) {
|
|
1717
|
+
const now = Date.now();
|
|
1718
|
+
const cached = dashboardWindowCache.get(cwd);
|
|
1719
|
+
if (cached && now - cached.checkedAt < CACHE_TTL_MS) {
|
|
1720
|
+
return cached.windowId;
|
|
1721
|
+
}
|
|
1722
|
+
const homeSession = findHomeSession(cwd);
|
|
1723
|
+
if (!homeSession) return null;
|
|
1724
|
+
const windowId = getSessionOption(homeSession, "@sisyphus_dashboard");
|
|
1725
|
+
if (!windowId) return null;
|
|
1726
|
+
dashboardWindowCache.set(cwd, { windowId, checkedAt: now });
|
|
1727
|
+
return windowId;
|
|
1728
|
+
}
|
|
1729
|
+
function recomputeDots() {
|
|
1730
|
+
if (!getTrackedEntries) return;
|
|
1731
|
+
pruneCompleted();
|
|
1732
|
+
const byCwd = /* @__PURE__ */ new Map();
|
|
1733
|
+
for (const entry of getTrackedEntries()) {
|
|
1734
|
+
if (!entry.windowId) continue;
|
|
1735
|
+
let group = byCwd.get(entry.cwd);
|
|
1736
|
+
if (!group) {
|
|
1737
|
+
group = [];
|
|
1738
|
+
byCwd.set(entry.cwd, group);
|
|
1739
|
+
}
|
|
1740
|
+
group.push({ sessionId: entry.id, windowId: entry.windowId });
|
|
1741
|
+
}
|
|
1742
|
+
for (const [sessionId, entry] of completedSessions) {
|
|
1743
|
+
if (!byCwd.has(entry.cwd)) {
|
|
1744
|
+
byCwd.set(entry.cwd, []);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
const tmuxSessionMap = /* @__PURE__ */ new Map();
|
|
1748
|
+
for (const entry of getTrackedEntries()) {
|
|
1749
|
+
tmuxSessionMap.set(entry.id, entry.tmuxSession);
|
|
1750
|
+
}
|
|
1751
|
+
for (const [cwd, tracked] of byCwd) {
|
|
1752
|
+
const dots = [];
|
|
1753
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1754
|
+
for (const { sessionId, windowId } of tracked) {
|
|
1755
|
+
seenIds.add(sessionId);
|
|
1756
|
+
try {
|
|
1757
|
+
const session = getSession(cwd, sessionId);
|
|
1758
|
+
const orchPaneId = getOrchestratorPaneId(sessionId);
|
|
1759
|
+
const livePanes = listPanes(windowId);
|
|
1760
|
+
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
1761
|
+
const phase = detectPhase(session, orchPaneId, livePaneIds);
|
|
1762
|
+
dots.push({ phase, createdAt: session.createdAt });
|
|
1763
|
+
const tmuxSessionName = tmuxSessionMap.get(sessionId);
|
|
1764
|
+
if (tmuxSessionName) {
|
|
1765
|
+
setSessionOption(tmuxSessionName, "@sisyphus_phase", phase);
|
|
1766
|
+
}
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
for (const [sessionId, entry] of completedSessions) {
|
|
1771
|
+
if (entry.cwd !== cwd || seenIds.has(sessionId)) continue;
|
|
1772
|
+
dots.push({ phase: "completed", createdAt: entry.createdAt });
|
|
1773
|
+
}
|
|
1774
|
+
const dashboardWindowId = getDashboardWindowId(cwd);
|
|
1775
|
+
if (dashboardWindowId) {
|
|
1776
|
+
const rendered = dots.length > 0 ? " " + renderDots(dots) : "";
|
|
1777
|
+
setWindowOption(dashboardWindowId, "@sisyphus_dots", rendered);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1626
1782
|
// src/daemon/session-manager.ts
|
|
1627
1783
|
var NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
1628
1784
|
function switchToHomeSession(session) {
|
|
@@ -1691,6 +1847,10 @@ async function startSession(task, cwd, context, name) {
|
|
|
1691
1847
|
console.error(`[sisyphus] Name generation failed for session ${sessionId}:`, err);
|
|
1692
1848
|
});
|
|
1693
1849
|
}
|
|
1850
|
+
try {
|
|
1851
|
+
recomputeDots();
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1694
1854
|
return { ...getSession(cwd, sessionId), tmuxSessionName: tmuxName };
|
|
1695
1855
|
}
|
|
1696
1856
|
var PRUNE_KEEP_COUNT = 10;
|
|
@@ -1784,6 +1944,10 @@ async function resumeSession(sessionId, cwd, message) {
|
|
|
1784
1944
|
if (initialPaneId) {
|
|
1785
1945
|
killPane(initialPaneId);
|
|
1786
1946
|
}
|
|
1947
|
+
try {
|
|
1948
|
+
recomputeDots();
|
|
1949
|
+
} catch {
|
|
1950
|
+
}
|
|
1787
1951
|
return getSession(cwd, sessionId);
|
|
1788
1952
|
}
|
|
1789
1953
|
function getSessionStatus(cwd, sessionId) {
|
|
@@ -1874,6 +2038,10 @@ function onAllAgentsDone2(sessionId, cwd, windowId) {
|
|
|
1874
2038
|
}
|
|
1875
2039
|
}
|
|
1876
2040
|
selectLayout(activeWindowId);
|
|
2041
|
+
try {
|
|
2042
|
+
recomputeDots();
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
1877
2045
|
} catch (err) {
|
|
1878
2046
|
console.error(`[sisyphus] Failed to respawn orchestrator for session ${sessionId}:`, err);
|
|
1879
2047
|
} finally {
|
|
@@ -1901,10 +2069,18 @@ async function handleSpawn(sessionId, cwd, agentType, name, instruction, repo) {
|
|
|
1901
2069
|
repo
|
|
1902
2070
|
});
|
|
1903
2071
|
await appendAgentToLastCycle(cwd, sessionId, agent.id);
|
|
2072
|
+
try {
|
|
2073
|
+
recomputeDots();
|
|
2074
|
+
} catch {
|
|
2075
|
+
}
|
|
1904
2076
|
return { agentId: agent.id };
|
|
1905
2077
|
}
|
|
1906
2078
|
async function handleSubmit(cwd, sessionId, agentId, report, windowId) {
|
|
1907
2079
|
const allDone = await handleAgentSubmit(cwd, sessionId, agentId, report);
|
|
2080
|
+
try {
|
|
2081
|
+
recomputeDots();
|
|
2082
|
+
} catch {
|
|
2083
|
+
}
|
|
1908
2084
|
if (allDone) {
|
|
1909
2085
|
onAllAgentsDone2(sessionId, cwd, windowId);
|
|
1910
2086
|
}
|
|
@@ -1920,6 +2096,10 @@ async function handleYield(sessionId, cwd, nextPrompt, mode) {
|
|
|
1920
2096
|
respawningSessions.add(sessionId);
|
|
1921
2097
|
await handleOrchestratorYield(sessionId, cwd, nextPrompt, mode);
|
|
1922
2098
|
orchestratorDone.add(sessionId);
|
|
2099
|
+
try {
|
|
2100
|
+
recomputeDots();
|
|
2101
|
+
} catch {
|
|
2102
|
+
}
|
|
1923
2103
|
const session = getSession(cwd, sessionId);
|
|
1924
2104
|
const hasRunningAgents = session.agents.some((a) => a.status === "running");
|
|
1925
2105
|
if (!hasRunningAgents) {
|
|
@@ -1937,6 +2117,11 @@ async function handleComplete(sessionId, cwd, report) {
|
|
|
1937
2117
|
const session = getSession(cwd, sessionId);
|
|
1938
2118
|
await flushTimers(sessionId);
|
|
1939
2119
|
await handleOrchestratorComplete(sessionId, cwd, report);
|
|
2120
|
+
markSessionCompleted(sessionId, session.createdAt, cwd);
|
|
2121
|
+
try {
|
|
2122
|
+
recomputeDots();
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
1940
2125
|
switchToHomeSession(session);
|
|
1941
2126
|
}
|
|
1942
2127
|
async function handleContinue(sessionId, cwd) {
|
|
@@ -1972,6 +2157,10 @@ async function handleKill(sessionId, cwd) {
|
|
|
1972
2157
|
}
|
|
1973
2158
|
clearAgentCounter(sessionId);
|
|
1974
2159
|
orchestratorDone.delete(sessionId);
|
|
2160
|
+
try {
|
|
2161
|
+
recomputeDots();
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
1975
2164
|
return killedAgents;
|
|
1976
2165
|
}
|
|
1977
2166
|
async function handleRestartAgent(sessionId, cwd, agentId) {
|
|
@@ -2092,7 +2281,7 @@ function loadSessionRegistry() {
|
|
|
2092
2281
|
const p = registryPath();
|
|
2093
2282
|
if (!existsSync7(p)) return {};
|
|
2094
2283
|
try {
|
|
2095
|
-
return JSON.parse(
|
|
2284
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
2096
2285
|
} catch {
|
|
2097
2286
|
return {};
|
|
2098
2287
|
}
|
|
@@ -2408,7 +2597,7 @@ function stopServer() {
|
|
|
2408
2597
|
|
|
2409
2598
|
// src/daemon/updater.ts
|
|
2410
2599
|
import { execSync as execSync3 } from "child_process";
|
|
2411
|
-
import { readFileSync as
|
|
2600
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, lstatSync } from "fs";
|
|
2412
2601
|
import { resolve as resolve4 } from "path";
|
|
2413
2602
|
import { get } from "https";
|
|
2414
2603
|
function isNewer(latest, current) {
|
|
@@ -2425,7 +2614,7 @@ function isNewer(latest, current) {
|
|
|
2425
2614
|
function readPackageVersion() {
|
|
2426
2615
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
2427
2616
|
try {
|
|
2428
|
-
const raw =
|
|
2617
|
+
const raw = readFileSync7(resolve4(import.meta.dirname, rel), "utf-8");
|
|
2429
2618
|
const pkg = JSON.parse(raw);
|
|
2430
2619
|
if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
|
|
2431
2620
|
} catch {
|
|
@@ -2571,7 +2760,7 @@ function isProcessAlive(pid) {
|
|
|
2571
2760
|
function readPid() {
|
|
2572
2761
|
const pidFile = daemonPidPath();
|
|
2573
2762
|
try {
|
|
2574
|
-
const pid = parseInt(
|
|
2763
|
+
const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
|
|
2575
2764
|
return pid && isProcessAlive(pid) ? pid : null;
|
|
2576
2765
|
} catch {
|
|
2577
2766
|
return null;
|
|
@@ -2646,7 +2835,7 @@ async function recoverSessions() {
|
|
|
2646
2835
|
continue;
|
|
2647
2836
|
}
|
|
2648
2837
|
try {
|
|
2649
|
-
const session = JSON.parse(
|
|
2838
|
+
const session = JSON.parse(readFileSync8(stateFile, "utf-8"));
|
|
2650
2839
|
if (session.status === "active" || session.status === "paused") {
|
|
2651
2840
|
registerSessionCwd(sessionId, cwd);
|
|
2652
2841
|
resetAgentCounterFromState(sessionId, session.agents ?? []);
|
|
@@ -2720,6 +2909,8 @@ async function startDaemon() {
|
|
|
2720
2909
|
}
|
|
2721
2910
|
acquirePidLock();
|
|
2722
2911
|
setRespawnCallback(onAllAgentsDone2);
|
|
2912
|
+
setDotsCallback(recomputeDots);
|
|
2913
|
+
setTrackedEntriesProvider(getTrackedSessionEntries);
|
|
2723
2914
|
await startServer();
|
|
2724
2915
|
startMonitor(config.pollIntervalMs);
|
|
2725
2916
|
await recoverSessions();
|