sisyphi 1.0.6 → 1.0.8

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
@@ -509,6 +509,7 @@ function createSession2(sessionName, windowName, cwd) {
509
509
  exec(`tmux new-session -d -s "${sessionName}" -n "${windowName}" -c ${shellQuote(cwd)}`);
510
510
  const windowId = exec(`tmux display-message -t "${sessionName}:${windowName}" -p "#{window_id}"`);
511
511
  const initialPaneId = exec(`tmux display-message -t "${sessionName}:${windowName}" -p "#{pane_id}"`);
512
+ configureSessionDefaults(sessionName, windowId);
512
513
  return { windowId, initialPaneId };
513
514
  }
514
515
  function paneExists(paneTarget) {
@@ -535,7 +536,8 @@ function setPaneTitle(paneTarget, title) {
535
536
  execSafe(`tmux select-pane -t "${paneTarget}" -T ${shellQuote(title)}`);
536
537
  }
537
538
  function setPaneStyle(paneTarget, color) {
538
- const fmt = `#[fg=${color},bold] #{pane_title} #[fg=${color}]#{pane_current_path} #[default]`;
539
+ const gitBranch = `#(cd #{pane_current_path} && git branch --show-current 2>/dev/null || echo 'n/a')`;
540
+ const fmt = `#[fg=${color},bold] #{pane_title} #[fg=${color}]#{pane_current_path} | ${gitBranch} #[default]`;
539
541
  execSafe(`tmux set -p -t "${paneTarget}" pane-border-format ${shellQuote(fmt)}`);
540
542
  execSafe(`tmux set -p -t "${paneTarget}" @pane_color "${color}"`);
541
543
  execSafe(`tmux set -w -t "${paneTarget}" pane-border-style "fg=#{?#{@pane_color},#{@pane_color},default}"`);
@@ -544,6 +546,13 @@ function setPaneStyle(paneTarget, color) {
544
546
  function selectLayout(windowTarget, layout = "even-horizontal") {
545
547
  execSafe(`tmux select-layout -t "${windowTarget}" ${layout}`);
546
548
  }
549
+ function configureSessionDefaults(sessionName, windowId) {
550
+ execSafe(`tmux set -w -t "${windowId}" pane-border-status top`);
551
+ execSafe(`tmux set -w -t "${windowId}" allow-rename off`);
552
+ execSafe(`tmux set -w -t "${windowId}" automatic-rename off`);
553
+ execSafe(`tmux set-hook -t "${sessionName}" after-kill-pane "select-layout even-horizontal"`);
554
+ execSafe(`tmux set-hook -t "${sessionName}" pane-exited "select-layout even-horizontal"`);
555
+ }
547
556
 
548
557
  // src/daemon/pane-registry.ts
549
558
  var paneMap = /* @__PURE__ */ new Map();
@@ -656,31 +665,18 @@ ${lines.join("\n")}
656
665
  const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
657
666
  if (lastCycle && lastCycle.agentsSpawned.length > 0) {
658
667
  const agentMap = new Map(session.agents.map((a) => [a.id, a]));
659
- const agentBlocks = lastCycle.agentsSpawned.map((id) => {
668
+ const agentLines = lastCycle.agentsSpawned.map((id) => {
660
669
  const agent = agentMap.get(id);
661
- if (!agent) return `<agent-${id} status="unknown">
662
- (no agent data)
663
- </agent-${id}>`;
670
+ if (!agent) return `- **${id}**: unknown (no agent data)`;
664
671
  const finalReport = agent.reports.find((r) => r.type === "final");
665
672
  const reportToUse = finalReport ?? agent.reports[agent.reports.length - 1];
666
- let reportContent = "(no reports)";
667
- if (reportToUse) {
668
- try {
669
- reportContent = readFileSync3(reportToUse.filePath, "utf-8");
670
- } catch {
671
- reportContent = `(could not read report: ${reportToUse.filePath})`;
672
- }
673
- }
674
- return `<agent-${id} name="${agent.name}" status="${agent.status}">
675
- ${reportContent}
676
- </agent-${id}>`;
673
+ const reportRef = reportToUse ? `@${reportToUse.filePath}` : "(no reports)";
674
+ return `- **${id}** (${agent.name}) [${agent.status}]: ${reportRef}`;
677
675
  }).join("\n");
678
676
  mostRecentCycleSection = `
679
677
  ### Most Recent Cycle
680
678
 
681
- <last-cycle>
682
- ${agentBlocks}
683
- </last-cycle>
679
+ ${agentLines}
684
680
  `;
685
681
  }
686
682
  const roadmapRef = existsSync4(roadmapFile) ? `@${roadmapFile}` : "(empty)";
@@ -1057,9 +1053,30 @@ function createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig) {
1057
1053
  copyFileSync2(agentConfig.filePath, `${base}/agents/${shortName}.md`);
1058
1054
  }
1059
1055
  const srcHooks = resolve3(import.meta.dirname, "../templates/agent-plugin/hooks");
1060
- for (const f of ["hooks.json", "require-submit.sh", "intercept-send-message.sh"]) {
1056
+ for (const f of ["require-submit.sh", "intercept-send-message.sh"]) {
1061
1057
  copyFileSync2(`${srcHooks}/${f}`, `${base}/hooks/${f}`);
1062
1058
  }
1059
+ const hooksConfig = {
1060
+ PreToolUse: [
1061
+ { matcher: "SendMessage", hooks: [{ type: "command", command: "bash ${CLAUDE_PLUGIN_ROOT}/hooks/intercept-send-message.sh" }] }
1062
+ ],
1063
+ Stop: [
1064
+ { hooks: [{ type: "command", command: "bash ${CLAUDE_PLUGIN_ROOT}/hooks/require-submit.sh" }] }
1065
+ ]
1066
+ };
1067
+ const normalizedType = agentType?.replace(/^sisyphus:/, "") ?? "";
1068
+ if (normalizedType === "plan") {
1069
+ hooksConfig.UserPromptSubmit = [
1070
+ { hooks: [{ type: "command", command: "bash ${CLAUDE_PLUGIN_ROOT}/hooks/plan-user-prompt.sh" }] }
1071
+ ];
1072
+ copyFileSync2(`${srcHooks}/plan-user-prompt.sh`, `${base}/hooks/plan-user-prompt.sh`);
1073
+ } else if (normalizedType === "spec-draft") {
1074
+ hooksConfig.UserPromptSubmit = [
1075
+ { hooks: [{ type: "command", command: "bash ${CLAUDE_PLUGIN_ROOT}/hooks/spec-user-prompt.sh" }] }
1076
+ ];
1077
+ copyFileSync2(`${srcHooks}/spec-user-prompt.sh`, `${base}/hooks/spec-user-prompt.sh`);
1078
+ }
1079
+ writeFileSync4(`${base}/hooks/hooks.json`, JSON.stringify({ hooks: hooksConfig }, null, 2), "utf-8");
1063
1080
  return base;
1064
1081
  }
1065
1082
  function setupAgentPane(opts) {
@@ -1303,14 +1320,17 @@ async function handleAgentSubmit(cwd, sessionId, agentId, report) {
1303
1320
  });
1304
1321
  const session = getSession(cwd, sessionId);
1305
1322
  const agent = session.agents.find((a) => a.id === agentId);
1323
+ const allDone = allAgentsDone(session);
1306
1324
  if (agent?.paneId) {
1307
1325
  unregisterAgentPane(sessionId, agentId);
1308
- try {
1309
- killPane(agent.paneId);
1310
- } catch {
1326
+ if (!allDone) {
1327
+ try {
1328
+ killPane(agent.paneId);
1329
+ } catch {
1330
+ }
1311
1331
  }
1312
1332
  }
1313
- return allAgentsDone(getSession(cwd, sessionId));
1333
+ return allDone;
1314
1334
  }
1315
1335
  async function handleAgentKilled(cwd, sessionId, agentId, reason) {
1316
1336
  unregisterAgentPane(sessionId, agentId);
@@ -1500,6 +1520,17 @@ function pruneOldSessions(cwd) {
1500
1520
  console.error("[sisyphus] Session pruning failed:", err);
1501
1521
  }
1502
1522
  }
1523
+ async function reopenWindow(sessionId, cwd) {
1524
+ const session = getSession(cwd, sessionId);
1525
+ const tmuxName = session.tmuxSessionName ?? `sisyphus-${session.name ?? sessionId.slice(0, 8)}`;
1526
+ if (sessionExists(tmuxName) && session.tmuxWindowId) {
1527
+ return { tmuxSessionName: tmuxName, tmuxWindowId: session.tmuxWindowId };
1528
+ }
1529
+ const created = createSession2(tmuxName, "main", cwd);
1530
+ setSessionOption(tmuxName, "@sisyphus_cwd", cwd.replace(/\/+$/, ""));
1531
+ await updateSessionTmux(cwd, sessionId, tmuxName, created.windowId);
1532
+ return { tmuxSessionName: tmuxName, tmuxWindowId: created.windowId };
1533
+ }
1503
1534
  async function resumeSession(sessionId, cwd, message) {
1504
1535
  const session = getSession(cwd, sessionId);
1505
1536
  const tmuxName = session.tmuxSessionName ?? `sisyphus-${sessionId.slice(0, 8)}`;
@@ -2018,6 +2049,23 @@ async function handleRequest(req) {
2018
2049
  const result = await handleRollback(req.sessionId, tracking.cwd, req.toCycle);
2019
2050
  return { ok: true, data: result };
2020
2051
  }
2052
+ case "reopen-window": {
2053
+ let tracking = sessionTrackingMap.get(req.sessionId);
2054
+ if (!tracking) {
2055
+ const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
2056
+ if (existsSync8(stateFile)) {
2057
+ tracking = { cwd: req.cwd, messageCounter: 0 };
2058
+ sessionTrackingMap.set(req.sessionId, tracking);
2059
+ persistSessionRegistry();
2060
+ } else {
2061
+ return unknownSessionError(req.sessionId);
2062
+ }
2063
+ }
2064
+ const result = await reopenWindow(req.sessionId, tracking.cwd);
2065
+ tracking.tmuxSession = result.tmuxSessionName;
2066
+ tracking.windowId = result.tmuxWindowId;
2067
+ return { ok: true, data: result };
2068
+ }
2021
2069
  case "delete": {
2022
2070
  const activeTracking = sessionTrackingMap.get(req.sessionId);
2023
2071
  if (activeTracking) {
@@ -2138,7 +2186,7 @@ function stopServer() {
2138
2186
 
2139
2187
  // src/daemon/updater.ts
2140
2188
  import { execSync as execSync3 } from "child_process";
2141
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
2189
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, lstatSync } from "fs";
2142
2190
  import { resolve as resolve4 } from "path";
2143
2191
  import { get } from "https";
2144
2192
  function isNewer(latest, current) {
@@ -2228,8 +2276,19 @@ function clearUpdating() {
2228
2276
  } catch {
2229
2277
  }
2230
2278
  }
2279
+ function isLinkedInstall() {
2280
+ try {
2281
+ const nodeDir = resolve4(process.execPath, "..");
2282
+ const globalPrefix = execSync3("npm prefix -g", { timeout: 5e3, encoding: "utf-8", env: { ...process.env, PATH: `${nodeDir}:${process.env.PATH ?? ""}` } }).trim();
2283
+ const globalPkgDir = resolve4(globalPrefix, "lib", "node_modules", "sisyphi");
2284
+ return lstatSync(globalPkgDir).isSymbolicLink();
2285
+ } catch {
2286
+ return false;
2287
+ }
2288
+ }
2231
2289
  async function checkAndApply() {
2232
2290
  clearUpdating();
2291
+ if (isLinkedInstall()) return;
2233
2292
  try {
2234
2293
  const update = await checkForUpdate();
2235
2294
  if (!update) return;