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/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 readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
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 readFileSync5, mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
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(readFileSync5(p, "utf-8"));
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 readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, lstatSync } from "fs";
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 = readFileSync6(resolve4(import.meta.dirname, rel), "utf-8");
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(readFileSync7(pidFile, "utf-8").trim(), 10);
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(readFileSync7(stateFile, "utf-8"));
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();