sisyphi 0.1.5 → 0.1.6

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
@@ -20,12 +20,13 @@ import {
20
20
  } from "./chunk-N2BPQOO2.js";
21
21
 
22
22
  // src/daemon/index.ts
23
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, existsSync as existsSync6 } from "fs";
23
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, existsSync as existsSync6 } from "fs";
24
+ import { setTimeout as sleep } from "timers/promises";
24
25
 
25
26
  // src/shared/config.ts
26
27
  import { readFileSync } from "fs";
27
28
  var DEFAULT_CONFIG = {
28
- pollIntervalMs: 1e3
29
+ pollIntervalMs: 5e3
29
30
  };
30
31
  function readJsonFile(filePath) {
31
32
  try {
@@ -43,7 +44,7 @@ function loadConfig(cwd) {
43
44
 
44
45
  // src/daemon/server.ts
45
46
  import { createServer } from "net";
46
- import { unlinkSync, existsSync as existsSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync4 } from "fs";
47
+ import { unlinkSync, existsSync as existsSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync8, mkdirSync as mkdirSync4 } from "fs";
47
48
  import { join as join4 } from "path";
48
49
 
49
50
  // src/daemon/session-manager.ts
@@ -70,16 +71,16 @@ description: >
70
71
  var sessionLocks = /* @__PURE__ */ new Map();
71
72
  async function withSessionLock(sessionId, fn) {
72
73
  const prev = sessionLocks.get(sessionId) ?? Promise.resolve();
73
- let resolve3;
74
+ let resolve4;
74
75
  const next = new Promise((r) => {
75
- resolve3 = r;
76
+ resolve4 = r;
76
77
  });
77
78
  sessionLocks.set(sessionId, next);
78
79
  await prev;
79
80
  try {
80
81
  return fn();
81
82
  } finally {
82
- resolve3();
83
+ resolve4();
83
84
  }
84
85
  }
85
86
  function atomicWrite(filePath, data) {
@@ -328,6 +329,33 @@ function shellQuote(s) {
328
329
  return `'${s.replace(/'/g, "'\\''")}'`;
329
330
  }
330
331
 
332
+ // src/daemon/pane-registry.ts
333
+ var paneMap = /* @__PURE__ */ new Map();
334
+ function registerPane(paneId, sessionId, role, agentId) {
335
+ paneMap.set(paneId, { sessionId, role, agentId });
336
+ }
337
+ function unregisterPane(paneId) {
338
+ paneMap.delete(paneId);
339
+ }
340
+ function unregisterAgentPane(sessionId, agentId) {
341
+ for (const [paneId, entry] of paneMap) {
342
+ if (entry.sessionId === sessionId && entry.agentId === agentId) {
343
+ paneMap.delete(paneId);
344
+ return;
345
+ }
346
+ }
347
+ }
348
+ function unregisterSessionPanes(sessionId) {
349
+ for (const [paneId, entry] of paneMap) {
350
+ if (entry.sessionId === sessionId) {
351
+ paneMap.delete(paneId);
352
+ }
353
+ }
354
+ }
355
+ function lookupPane(paneId) {
356
+ return paneMap.get(paneId);
357
+ }
358
+
331
359
  // src/daemon/orchestrator.ts
332
360
  var sessionWindowMap = /* @__PURE__ */ new Map();
333
361
  var sessionOrchestratorPane = /* @__PURE__ */ new Map();
@@ -462,11 +490,13 @@ Review the current session and delegate the next cycle of work.`;
462
490
  const claudeCmd = `claude --dangerously-skip-permissions --settings "${settingsPath}" --plugin-dir "${pluginPath}" --append-system-prompt "$(cat '${promptFilePath}')" "$(cat '${userPromptFilePath}')"`;
463
491
  const paneId = createPane(windowId, cwd);
464
492
  sessionOrchestratorPane.set(sessionId, paneId);
493
+ registerPane(paneId, sessionId, "orchestrator");
465
494
  setPaneTitle(paneId, `orchestrator (${sessionId.slice(0, 8)})`);
466
495
  setPaneStyle(paneId, ORCHESTRATOR_COLOR);
467
496
  const bannerPath = resolve(import.meta.dirname, "../templates/banner.txt");
468
497
  const bannerCmd = existsSync(bannerPath) ? `cat '${bannerPath}' &&` : "";
469
- sendKeys(paneId, `${bannerCmd} ${envExports} && ${claudeCmd}`);
498
+ const notifyCmd = `sisyphus notify pane-exited --pane-id ${paneId}`;
499
+ sendKeys(paneId, `${bannerCmd} ${envExports} && ${claudeCmd}; ${notifyCmd}`);
470
500
  await addOrchestratorCycle(cwd, sessionId, {
471
501
  cycle: cycleNum,
472
502
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -485,6 +515,7 @@ async function handleOrchestratorYield(sessionId, cwd, nextPrompt) {
485
515
  const paneId = resolveOrchestratorPane(sessionId, cwd);
486
516
  if (paneId) {
487
517
  killPane(paneId);
518
+ unregisterPane(paneId);
488
519
  sessionOrchestratorPane.delete(sessionId);
489
520
  }
490
521
  const windowId = sessionWindowMap.get(sessionId);
@@ -504,6 +535,7 @@ async function handleOrchestratorComplete(sessionId, cwd, report) {
504
535
  function cleanupSessionMaps(sessionId) {
505
536
  sessionOrchestratorPane.delete(sessionId);
506
537
  sessionWindowMap.delete(sessionId);
538
+ unregisterSessionPanes(sessionId);
507
539
  }
508
540
 
509
541
  // src/daemon/agent.ts
@@ -712,6 +744,7 @@ async function spawnAgent(opts) {
712
744
  worktreeContext = { offset: portOffset, total: portOffset, branchName };
713
745
  }
714
746
  const paneId = createPane(windowId, paneCwd);
747
+ registerPane(paneId, sessionId, "agent", agentId);
715
748
  setPaneTitle(paneId, `${name} (${agentId})`);
716
749
  setPaneStyle(paneId, color);
717
750
  const suffix = renderAgentSuffix(sessionId, instruction, worktreeContext);
@@ -726,7 +759,8 @@ async function spawnAgent(opts) {
726
759
  ].join(" && ");
727
760
  const agentFlag = agentType ? ` --agent ${shellQuote3(agentType)}` : "";
728
761
  const claudeCmd = `claude --dangerously-skip-permissions --plugin-dir "${pluginPath}"${agentFlag} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote3(instruction)}`;
729
- const fullCmd = `${bannerCmd} ${envExports} && ${claudeCmd}`;
762
+ const notifyCmd = `sisyphus notify pane-exited --pane-id ${paneId}`;
763
+ const fullCmd = `${bannerCmd} ${envExports} && ${claudeCmd}; ${notifyCmd}`;
730
764
  const agent = {
731
765
  id: agentId,
732
766
  name,
@@ -804,6 +838,7 @@ async function handleAgentSubmit(cwd, sessionId, agentId, report) {
804
838
  const agentArr = session.agents;
805
839
  const agent = agentArr.slice().reverse().find((a) => a.id === agentId);
806
840
  if (agent) {
841
+ unregisterPane(agent.paneId);
807
842
  killPane(agent.paneId);
808
843
  }
809
844
  const windowId = getWindowId(sessionId);
@@ -811,6 +846,7 @@ async function handleAgentSubmit(cwd, sessionId, agentId, report) {
811
846
  return allAgentsDone(session);
812
847
  }
813
848
  async function handleAgentKilled(cwd, sessionId, agentId, reason) {
849
+ unregisterAgentPane(sessionId, agentId);
814
850
  await updateAgent(cwd, sessionId, agentId, {
815
851
  status: "killed",
816
852
  killedReason: reason,
@@ -833,7 +869,7 @@ var onAllAgentsDone = null;
833
869
  function setRespawnCallback(cb) {
834
870
  onAllAgentsDone = cb;
835
871
  }
836
- function startMonitor(pollIntervalMs = 1e3) {
872
+ function startMonitor(pollIntervalMs = 5e3) {
837
873
  if (monitorInterval) return;
838
874
  monitorInterval = setInterval(() => {
839
875
  pollAllSessions().catch((err) => {
@@ -922,8 +958,85 @@ async function pollSession(sessionId, cwd, windowId) {
922
958
  }
923
959
  }
924
960
 
961
+ // src/daemon/updater.ts
962
+ import { execSync as execSync3 } from "child_process";
963
+ import { readFileSync as readFileSync7 } from "fs";
964
+ import { resolve as resolve3 } from "path";
965
+ import { get } from "https";
966
+ function readPackageVersion() {
967
+ for (const rel of ["../package.json", "../../package.json"]) {
968
+ try {
969
+ const raw = readFileSync7(resolve3(import.meta.dirname, rel), "utf-8");
970
+ const pkg = JSON.parse(raw);
971
+ if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
972
+ } catch {
973
+ }
974
+ }
975
+ return "0.0.0";
976
+ }
977
+ var currentVersion = readPackageVersion();
978
+ function checkForUpdate() {
979
+ return new Promise((resolve4) => {
980
+ const timeout = setTimeout(() => {
981
+ resolve4(null);
982
+ }, 5e3);
983
+ const req = get("https://registry.npmjs.org/sisyphi/latest", (res) => {
984
+ let data = "";
985
+ res.on("data", (chunk) => {
986
+ data += chunk.toString();
987
+ });
988
+ res.on("end", () => {
989
+ clearTimeout(timeout);
990
+ try {
991
+ const { version: latest } = JSON.parse(data);
992
+ if (latest && latest !== currentVersion) {
993
+ resolve4({ current: currentVersion, latest });
994
+ } else {
995
+ resolve4(null);
996
+ }
997
+ } catch {
998
+ resolve4(null);
999
+ }
1000
+ });
1001
+ });
1002
+ req.on("error", () => {
1003
+ clearTimeout(timeout);
1004
+ resolve4(null);
1005
+ });
1006
+ });
1007
+ }
1008
+ function applyUpdate() {
1009
+ try {
1010
+ const nodeDir = resolve3(process.execPath, "..");
1011
+ const env = { ...process.env, PATH: `${nodeDir}:${process.env.PATH ?? ""}` };
1012
+ execSync3("npm install -g sisyphi", { timeout: 15e3, stdio: "pipe", env });
1013
+ return true;
1014
+ } catch (err) {
1015
+ console.error("[sisyphus] Auto-update failed:", err);
1016
+ return false;
1017
+ }
1018
+ }
1019
+ async function checkAndApply() {
1020
+ try {
1021
+ const update = await checkForUpdate();
1022
+ if (!update) return;
1023
+ console.log(`[sisyphus] Update available: ${update.current} \u2192 ${update.latest}`);
1024
+ const success = applyUpdate();
1025
+ if (success) {
1026
+ console.log(`[sisyphus] Updated to ${update.latest}, restarting daemon...`);
1027
+ process.exit(0);
1028
+ }
1029
+ } catch (err) {
1030
+ console.error("[sisyphus] Auto-update check failed:", err);
1031
+ }
1032
+ }
1033
+
925
1034
  // src/daemon/session-manager.ts
926
1035
  async function startSession(task, cwd, tmuxSession, windowId) {
1036
+ const config = loadConfig(cwd);
1037
+ if (config.autoUpdate !== false) {
1038
+ await checkAndApply();
1039
+ }
927
1040
  const sessionId = uuidv4();
928
1041
  const session = createSession(sessionId, task, cwd);
929
1042
  await updateSessionTmux(cwd, sessionId, tmuxSession, windowId);
@@ -1042,10 +1155,10 @@ function onAllAgentsDone2(sessionId, cwd, windowId) {
1042
1155
  }).catch((err) => console.error(`[sisyphus] Failed to update merge status for ${result.agentId}:`, err));
1043
1156
  }
1044
1157
  }
1045
- setTimeout(() => {
1158
+ setImmediate(() => {
1046
1159
  pendingRespawns.delete(sessionId);
1047
1160
  spawnOrchestrator(sessionId, cwd, windowId).then(() => updateTrackedWindow(sessionId, windowId)).catch((err) => console.error(`[sisyphus] Failed to respawn orchestrator for session ${sessionId}:`, err));
1048
- }, 2e3);
1161
+ });
1049
1162
  }
1050
1163
  async function handleSpawn(sessionId, cwd, agentType, name, instruction, worktree) {
1051
1164
  const windowId = getWindowId(sessionId);
@@ -1117,12 +1230,40 @@ async function handleKill(sessionId, cwd) {
1117
1230
  }
1118
1231
  await updateSessionStatus(cwd, sessionId, "completed");
1119
1232
  untrackSession(sessionId);
1233
+ unregisterSessionPanes(sessionId);
1120
1234
  if (windowId) {
1121
1235
  killWindow(windowId);
1122
1236
  }
1123
1237
  clearAgentCounter(sessionId);
1124
1238
  return killedAgents;
1125
1239
  }
1240
+ async function handlePaneExited(paneId, cwd, sessionId, role, agentId) {
1241
+ const session = getSession(cwd, sessionId);
1242
+ if (session.status !== "active") return;
1243
+ if (role === "agent" && agentId) {
1244
+ const agent = session.agents.find((a) => a.id === agentId);
1245
+ if (!agent || agent.status !== "running") return;
1246
+ const allDone = await handleAgentKilled(cwd, sessionId, agentId, "pane exited");
1247
+ if (allDone) {
1248
+ const windowId = getWindowId(sessionId);
1249
+ if (windowId) {
1250
+ onAllAgentsDone2(sessionId, cwd, windowId);
1251
+ }
1252
+ }
1253
+ } else if (role === "orchestrator") {
1254
+ const hasRunningAgents = session.agents.some((a) => a.status === "running");
1255
+ if (!hasRunningAgents && session.agents.length > 0) {
1256
+ const windowId = getWindowId(sessionId);
1257
+ if (windowId) {
1258
+ console.log(`[sisyphus] Orchestrator pane exited for session ${sessionId}, all agents done \u2014 triggering respawn`);
1259
+ onAllAgentsDone2(sessionId, cwd, windowId);
1260
+ }
1261
+ } else if (!hasRunningAgents) {
1262
+ await updateSessionStatus(cwd, sessionId, "paused");
1263
+ console.log(`[sisyphus] Session ${sessionId} paused: orchestrator pane exited with no agents`);
1264
+ }
1265
+ }
1266
+ }
1126
1267
 
1127
1268
  // src/daemon/server.ts
1128
1269
  var server = null;
@@ -1145,7 +1286,7 @@ function loadSessionRegistry() {
1145
1286
  const p = registryPath();
1146
1287
  if (!existsSync5(p)) return {};
1147
1288
  try {
1148
- return JSON.parse(readFileSync7(p, "utf-8"));
1289
+ return JSON.parse(readFileSync8(p, "utf-8"));
1149
1290
  } catch {
1150
1291
  return {};
1151
1292
  }
@@ -1267,6 +1408,18 @@ async function handleRequest(req) {
1267
1408
  persistSessionRegistry();
1268
1409
  return { ok: true, data: { killedAgents, sessionId: req.sessionId } };
1269
1410
  }
1411
+ case "pane-exited": {
1412
+ const entry = lookupPane(req.paneId);
1413
+ if (!entry) return { ok: true };
1414
+ const cwd = sessionCwdMap.get(entry.sessionId);
1415
+ if (!cwd) {
1416
+ unregisterPane(req.paneId);
1417
+ return { ok: true };
1418
+ }
1419
+ unregisterPane(req.paneId);
1420
+ await handlePaneExited(req.paneId, cwd, entry.sessionId, entry.role, entry.agentId);
1421
+ return { ok: true };
1422
+ }
1270
1423
  default:
1271
1424
  return { ok: false, error: `Unknown request type: ${req.type}` };
1272
1425
  }
@@ -1276,7 +1429,7 @@ async function handleRequest(req) {
1276
1429
  }
1277
1430
  }
1278
1431
  function startServer() {
1279
- return new Promise((resolve3, reject) => {
1432
+ return new Promise((resolve4, reject) => {
1280
1433
  const sock = socketPath();
1281
1434
  if (existsSync5(sock)) {
1282
1435
  unlinkSync(sock);
@@ -1308,14 +1461,14 @@ function startServer() {
1308
1461
  server.on("error", reject);
1309
1462
  server.listen(sock, () => {
1310
1463
  console.log(`[sisyphus] Daemon listening on ${sock}`);
1311
- resolve3(server);
1464
+ resolve4(server);
1312
1465
  });
1313
1466
  });
1314
1467
  }
1315
1468
  function stopServer() {
1316
- return new Promise((resolve3) => {
1469
+ return new Promise((resolve4) => {
1317
1470
  if (!server) {
1318
- resolve3();
1471
+ resolve4();
1319
1472
  return;
1320
1473
  }
1321
1474
  server.close(() => {
@@ -1324,7 +1477,7 @@ function stopServer() {
1324
1477
  unlinkSync(sock);
1325
1478
  }
1326
1479
  server = null;
1327
- resolve3();
1480
+ resolve4();
1328
1481
  });
1329
1482
  });
1330
1483
  }
@@ -1344,7 +1497,7 @@ function isProcessAlive(pid) {
1344
1497
  function readPid() {
1345
1498
  const pidFile = daemonPidPath();
1346
1499
  try {
1347
- const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
1500
+ const pid = parseInt(readFileSync9(pidFile, "utf-8").trim(), 10);
1348
1501
  return pid && isProcessAlive(pid) ? pid : null;
1349
1502
  } catch {
1350
1503
  return null;
@@ -1411,7 +1564,7 @@ async function recoverSessions() {
1411
1564
  continue;
1412
1565
  }
1413
1566
  try {
1414
- const session = JSON.parse(readFileSync8(stateFile, "utf-8"));
1567
+ const session = JSON.parse(readFileSync9(stateFile, "utf-8"));
1415
1568
  if (session.status === "active" || session.status === "paused") {
1416
1569
  registerSessionCwd(sessionId, cwd);
1417
1570
  resetAgentCounterFromState(sessionId, session.agents ?? []);
@@ -1425,6 +1578,18 @@ async function recoverSessions() {
1425
1578
  const lastIncompleteCycle = [...session.orchestratorCycles].reverse().find((c) => !c.completedAt && c.paneId);
1426
1579
  if (lastIncompleteCycle?.paneId) {
1427
1580
  setOrchestratorPaneId(sessionId, lastIncompleteCycle.paneId);
1581
+ const livePaneIds = new Set(livePanes.map((p) => p.paneId));
1582
+ if (livePaneIds.has(lastIncompleteCycle.paneId)) {
1583
+ registerPane(lastIncompleteCycle.paneId, sessionId, "orchestrator");
1584
+ }
1585
+ }
1586
+ for (const agent of session.agents) {
1587
+ if (agent.status === "running" && agent.paneId) {
1588
+ const livePaneIds = new Set(livePanes.map((p) => p.paneId));
1589
+ if (livePaneIds.has(agent.paneId)) {
1590
+ registerPane(agent.paneId, sessionId, "agent", agent.id);
1591
+ }
1592
+ }
1428
1593
  }
1429
1594
  console.log(`[sisyphus] Reconnected session ${sessionId} to tmux window ${session.tmuxWindowId}`);
1430
1595
  if (session.status === "active" && session.agents.length > 0) {
@@ -1474,47 +1639,44 @@ async function startDaemon() {
1474
1639
  process.on("SIGINT", shutdown);
1475
1640
  }
1476
1641
  var command = process.argv[2];
1477
- switch (command) {
1478
- case "stop":
1479
- stopDaemon();
1480
- break;
1481
- case "restart": {
1482
- stopDaemon();
1483
- const wait = Date.now() + 500;
1484
- while (Date.now() < wait) {
1485
- }
1486
- const respawnedPid = readPid();
1487
- if (respawnedPid) {
1488
- console.log(`[sisyphus] Daemon restarted (pid ${respawnedPid}) by process manager`);
1642
+ (async () => {
1643
+ switch (command) {
1644
+ case "stop":
1645
+ stopDaemon();
1646
+ break;
1647
+ case "restart": {
1648
+ stopDaemon();
1649
+ await sleep(500);
1650
+ const respawnedPid = readPid();
1651
+ if (respawnedPid) {
1652
+ console.log(`[sisyphus] Daemon restarted (pid ${respawnedPid}) by process manager`);
1653
+ break;
1654
+ }
1655
+ await startDaemon();
1489
1656
  break;
1490
1657
  }
1491
- startDaemon().catch((err) => {
1492
- console.error("[sisyphus] Fatal error:", err);
1658
+ case "start":
1659
+ case void 0:
1660
+ await startDaemon();
1661
+ break;
1662
+ case "help":
1663
+ case "--help":
1664
+ case "-h":
1665
+ console.log("Usage: sisyphusd [command]");
1666
+ console.log("");
1667
+ console.log("Commands:");
1668
+ console.log(" start Start the daemon (default if no command given)");
1669
+ console.log(" stop Stop the running daemon");
1670
+ console.log(" restart Stop and restart the daemon");
1671
+ console.log(" help Show this help message");
1672
+ break;
1673
+ default:
1674
+ console.error(`[sisyphus] Unknown command: ${command}`);
1675
+ console.error("Usage: sisyphusd [start|stop|restart|help]");
1493
1676
  process.exit(1);
1494
- });
1495
- break;
1496
1677
  }
1497
- case "start":
1498
- case void 0:
1499
- startDaemon().catch((err) => {
1500
- console.error("[sisyphus] Fatal error:", err);
1501
- process.exit(1);
1502
- });
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;
1515
- default:
1516
- console.error(`[sisyphus] Unknown command: ${command}`);
1517
- console.error("Usage: sisyphusd [start|stop|restart|help]");
1518
- process.exit(1);
1519
- }
1678
+ })().catch((err) => {
1679
+ console.error("[sisyphus] Fatal error:", err);
1680
+ process.exit(1);
1681
+ });
1520
1682
  //# sourceMappingURL=daemon.js.map