sisyphi 0.1.17 → 0.1.21

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
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-LTAW6OWS.js";
22
22
 
23
23
  // src/daemon/index.ts
24
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3, existsSync as existsSync6 } from "fs";
24
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3, existsSync as existsSync7 } from "fs";
25
25
  import { execSync as execSync4 } from "child_process";
26
26
  import { setTimeout as sleep } from "timers/promises";
27
27
 
@@ -46,12 +46,12 @@ function loadConfig(cwd) {
46
46
 
47
47
  // src/daemon/server.ts
48
48
  import { createServer } from "net";
49
- import { unlinkSync, existsSync as existsSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync4 } from "fs";
49
+ import { unlinkSync, existsSync as existsSync6, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync4 } from "fs";
50
50
  import { join as join4 } from "path";
51
51
 
52
52
  // src/daemon/session-manager.ts
53
53
  import { v4 as uuidv4 } from "uuid";
54
- import { existsSync as existsSync4, readdirSync as readdirSync4, rmSync as rmSync2 } from "fs";
54
+ import { existsSync as existsSync5, readdirSync as readdirSync4, rmSync as rmSync2 } from "fs";
55
55
 
56
56
  // src/daemon/state.ts
57
57
  import { randomUUID } from "crypto";
@@ -70,6 +70,10 @@ description: >
70
70
  implementation.
71
71
  ---
72
72
  `;
73
+ var CONTEXT_CLAUDE_MD = `# context/
74
+
75
+ Agents save exploration findings, architectural notes, and reference material here for use across cycles.
76
+ `;
73
77
  var sessionLocks = /* @__PURE__ */ new Map();
74
78
  async function withSessionLock(sessionId, fn) {
75
79
  const prev = sessionLocks.get(sessionId) ?? Promise.resolve();
@@ -91,16 +95,18 @@ function atomicWrite(filePath, data) {
91
95
  writeFileSync(tmpPath, data, "utf-8");
92
96
  renameSync(tmpPath, filePath);
93
97
  }
94
- function createSession(id, task, cwd) {
98
+ function createSession(id, task, cwd, context) {
95
99
  const dir = sessionDir(cwd, id);
96
100
  mkdirSync(dir, { recursive: true });
97
101
  mkdirSync(contextDir(cwd, id), { recursive: true });
98
102
  mkdirSync(promptsDir(cwd, id), { recursive: true });
99
103
  writeFileSync(planPath(cwd, id), PLAN_SEED, "utf-8");
100
104
  writeFileSync(logsPath(cwd, id), LOGS_SEED, "utf-8");
105
+ writeFileSync(join(contextDir(cwd, id), "CLAUDE.md"), CONTEXT_CLAUDE_MD, "utf-8");
101
106
  const session = {
102
107
  id,
103
108
  task,
109
+ ...context ? { context } : {},
104
110
  cwd,
105
111
  status: "active",
106
112
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -198,13 +204,10 @@ async function completeOrchestratorCycle(cwd, sessionId, nextPrompt) {
198
204
  }
199
205
 
200
206
  // src/daemon/orchestrator.ts
201
- import { existsSync, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
207
+ import { existsSync, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
202
208
  import { resolve } from "path";
203
209
 
204
210
  // src/daemon/colors.ts
205
- import { readFileSync as readFileSync3 } from "fs";
206
- import { homedir } from "os";
207
- import { join as join2 } from "path";
208
211
  var ORCHESTRATOR_COLOR = "yellow";
209
212
  var AGENT_PALETTE = ["blue", "green", "magenta", "cyan", "red", "white"];
210
213
  var TMUX_COLOR_MAP = {
@@ -224,56 +227,6 @@ function getNextColor(sessionId) {
224
227
  function resetColors(sessionId) {
225
228
  sessionColorIndex.delete(sessionId);
226
229
  }
227
- function extractFrontmatterColor(content) {
228
- const match = content.match(/^---\n([\s\S]*?)\n---/);
229
- if (!match) return null;
230
- const colorMatch = match[1].match(/^color:\s*(.+)$/m);
231
- return colorMatch ? colorMatch[1].trim() : null;
232
- }
233
- function findPluginInstallPath(namespace) {
234
- try {
235
- const registryPath2 = join2(homedir(), ".claude", "plugins", "installed_plugins.json");
236
- const registry = JSON.parse(readFileSync3(registryPath2, "utf-8"));
237
- for (const key of Object.keys(registry)) {
238
- if (key.startsWith(`${namespace}@`)) {
239
- return registry[key].installPath ?? null;
240
- }
241
- }
242
- } catch {
243
- }
244
- return null;
245
- }
246
- function resolveAgentTypeColor(agentType, pluginDir, cwd) {
247
- if (!agentType) return null;
248
- let namespace;
249
- let name;
250
- if (agentType.includes(":")) {
251
- [namespace, name] = agentType.split(":", 2);
252
- } else {
253
- name = agentType;
254
- }
255
- const searchPaths = [];
256
- if (namespace) {
257
- searchPaths.push(join2(pluginDir, "agents", `${name}.md`));
258
- const installPath = findPluginInstallPath(namespace);
259
- if (installPath) {
260
- searchPaths.push(join2(installPath, "agents", `${name}.md`));
261
- }
262
- } else {
263
- searchPaths.push(join2(cwd, ".claude", "agents", `${name}.md`));
264
- searchPaths.push(join2(homedir(), ".claude", "agents", `${name}.md`));
265
- searchPaths.push(join2(pluginDir, "agents", `${name}.md`));
266
- }
267
- for (const path of searchPaths) {
268
- try {
269
- const content = readFileSync3(path, "utf-8");
270
- const color = extractFrontmatterColor(content);
271
- if (color) return normalizeTmuxColor(color);
272
- } catch {
273
- }
274
- }
275
- return null;
276
- }
277
230
 
278
231
  // src/daemon/tmux.ts
279
232
  import { execSync } from "child_process";
@@ -291,9 +244,13 @@ function execSafe(cmd) {
291
244
  return null;
292
245
  }
293
246
  }
294
- function createPane(windowTarget, cwd) {
247
+ function createPane(windowTarget, cwd, position = "right") {
295
248
  const cwdFlag = cwd ? ` -c ${shellQuote(cwd)}` : "";
296
- const paneId = exec(`tmux split-window -h -t "${windowTarget}"${cwdFlag} -P -F "#{pane_id}"`);
249
+ const panes = listPanes(windowTarget);
250
+ const target = position === "left" ? panes[0]?.paneId : panes[panes.length - 1]?.paneId;
251
+ const targetFlag = target ? ` -t "${target}"` : ` -t "${windowTarget}"`;
252
+ const beforeFlag = position === "left" ? "b" : "";
253
+ const paneId = exec(`tmux split-window -h${beforeFlag}${targetFlag}${cwdFlag} -P -F "#{pane_id}"`);
297
254
  execSafe(`tmux select-layout -t "${windowTarget}" even-horizontal`);
298
255
  return paneId;
299
256
  }
@@ -376,10 +333,10 @@ function setOrchestratorPaneId(sessionId, paneId) {
376
333
  function loadOrchestratorPrompt(cwd) {
377
334
  const projectPath = projectOrchestratorPromptPath(cwd);
378
335
  if (existsSync(projectPath)) {
379
- return readFileSync4(projectPath, "utf-8");
336
+ return readFileSync3(projectPath, "utf-8");
380
337
  }
381
338
  const bundledPath = resolve(import.meta.dirname, "../templates/orchestrator.md");
382
- return readFileSync4(bundledPath, "utf-8");
339
+ return readFileSync3(bundledPath, "utf-8");
383
340
  }
384
341
  function formatStateForOrchestrator(session) {
385
342
  const shortId = session.id.slice(0, 8);
@@ -431,11 +388,15 @@ function formatStateForOrchestrator(session) {
431
388
  ${wtLines}`;
432
389
  }
433
390
  const worktreeHint = existsSync(worktreeConfigPath(session.cwd)) ? "Worktree config active (`.sisyphus/worktree.json`). Use `--worktree` flag with `sisyphus spawn` to isolate agents in their own worktrees. Recommended for feature work, especially with potential file overlap." : "No worktree configuration found. If this session involves parallel work where agents may edit overlapping files, use the `git-management` skill to set up `.sisyphus/worktree.json` and enable worktree isolation.";
391
+ const contextSection = session.context ? `
392
+ ## Background Context
393
+ ${session.context}
394
+ ` : "";
434
395
  return `<state>
435
396
  session: ${shortId} (cycle ${cycleNum})
436
397
  task: ${session.task}
437
398
  status: ${session.status}
438
-
399
+ ${contextSection}
439
400
  ## Plan
440
401
  ${planRef}
441
402
 
@@ -490,10 +451,10 @@ Review the current session and delegate the next cycle of work.`;
490
451
  const pluginPath = resolve(import.meta.dirname, "../templates/orchestrator-plugin");
491
452
  const settingsPath = resolve(import.meta.dirname, "../templates/orchestrator-settings.json");
492
453
  const claudeCmd = `claude --dangerously-skip-permissions --settings "${settingsPath}" --plugin-dir "${pluginPath}" --append-system-prompt "$(cat '${promptFilePath}')" "$(cat '${userPromptFilePath}')"`;
493
- const paneId = createPane(windowId, cwd);
454
+ const paneId = createPane(windowId, cwd, "left");
494
455
  sessionOrchestratorPane.set(sessionId, paneId);
495
456
  registerPane(paneId, sessionId, "orchestrator");
496
- setPaneTitle(paneId, `orchestrator (${sessionId.slice(0, 8)})`);
457
+ setPaneTitle(paneId, `Sisyphus`);
497
458
  setPaneStyle(paneId, ORCHESTRATOR_COLOR);
498
459
  const bannerPath = resolve(import.meta.dirname, "../templates/banner.txt");
499
460
  const bannerCmd = existsSync(bannerPath) ? `cat '${bannerPath}' &&` : "";
@@ -541,13 +502,13 @@ function cleanupSessionMaps(sessionId) {
541
502
  }
542
503
 
543
504
  // src/daemon/agent.ts
544
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3, existsSync as existsSync3 } from "fs";
505
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
545
506
  import { resolve as resolve2 } from "path";
546
507
 
547
508
  // src/daemon/worktree.ts
548
509
  import { execSync as execSync2 } from "child_process";
549
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync } from "fs";
550
- import { dirname as dirname2, join as join3 } from "path";
510
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync2, rmSync } from "fs";
511
+ import { dirname as dirname2, join as join2 } from "path";
551
512
  var EXEC_ENV2 = {
552
513
  ...process.env,
553
514
  PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env["PATH"] ?? "/usr/bin:/bin"}`
@@ -567,7 +528,7 @@ function shellQuote2(s) {
567
528
  }
568
529
  function loadWorktreeConfig(cwd) {
569
530
  try {
570
- const content = readFileSync5(worktreeConfigPath(cwd), "utf-8");
531
+ const content = readFileSync4(worktreeConfigPath(cwd), "utf-8");
571
532
  return JSON.parse(content);
572
533
  } catch {
573
534
  return null;
@@ -575,7 +536,7 @@ function loadWorktreeConfig(cwd) {
575
536
  }
576
537
  function createWorktreeShell(cwd, sessionId, agentId) {
577
538
  const branchName = `sisyphus/${sessionId.slice(0, 8)}/${agentId}`;
578
- const worktreePath = join3(worktreeBaseDir(cwd), sessionId.slice(0, 8), agentId);
539
+ const worktreePath = join2(worktreeBaseDir(cwd), sessionId.slice(0, 8), agentId);
579
540
  mkdirSync2(dirname2(worktreePath), { recursive: true });
580
541
  execSafe2(`git -C ${shellQuote2(cwd)} worktree prune`);
581
542
  if (existsSync2(worktreePath)) {
@@ -589,16 +550,16 @@ function createWorktreeShell(cwd, sessionId, agentId) {
589
550
  function bootstrapWorktree(cwd, worktreePath, config) {
590
551
  if (config.copy) {
591
552
  for (const entry of config.copy) {
592
- const dest = join3(worktreePath, entry);
553
+ const dest = join2(worktreePath, entry);
593
554
  mkdirSync2(dirname2(dest), { recursive: true });
594
- execSafe2(`cp -r ${shellQuote2(join3(cwd, entry))} ${shellQuote2(dest)}`);
555
+ execSafe2(`cp -r ${shellQuote2(join2(cwd, entry))} ${shellQuote2(dest)}`);
595
556
  }
596
557
  }
597
558
  if (config.clone) {
598
559
  for (const entry of config.clone) {
599
- const dest = join3(worktreePath, entry);
560
+ const dest = join2(worktreePath, entry);
600
561
  mkdirSync2(dirname2(dest), { recursive: true });
601
- const src = shellQuote2(join3(cwd, entry));
562
+ const src = shellQuote2(join2(cwd, entry));
602
563
  const dstQ = shellQuote2(dest);
603
564
  if (execSafe2(`cp -Rc ${src} ${dstQ}`) === null) {
604
565
  execSafe2(`cp -r ${src} ${dstQ}`);
@@ -607,9 +568,9 @@ function bootstrapWorktree(cwd, worktreePath, config) {
607
568
  }
608
569
  if (config.symlink) {
609
570
  for (const entry of config.symlink) {
610
- const dest = join3(worktreePath, entry);
571
+ const dest = join2(worktreePath, entry);
611
572
  mkdirSync2(dirname2(dest), { recursive: true });
612
- execSafe2(`ln -s ${shellQuote2(join3(cwd, entry))} ${shellQuote2(dest)}`);
573
+ execSafe2(`ln -s ${shellQuote2(join2(cwd, entry))} ${shellQuote2(dest)}`);
613
574
  }
614
575
  }
615
576
  if (config.init) {
@@ -692,6 +653,93 @@ function countWorktreeAgents(agents) {
692
653
  return agents.filter((a) => a.worktreePath && a.status === "running").length;
693
654
  }
694
655
 
656
+ // src/daemon/frontmatter.ts
657
+ import { readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
658
+ import { homedir } from "os";
659
+ import { join as join3 } from "path";
660
+ function detectProvider(model) {
661
+ if (!model) return "anthropic";
662
+ if (/^(gpt-|codex-)/.test(model)) return "openai";
663
+ return "anthropic";
664
+ }
665
+ function parseAgentFrontmatter(content) {
666
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
667
+ if (!match) return {};
668
+ const block = match[1];
669
+ const fm = {};
670
+ const str = (key) => {
671
+ const m = block.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
672
+ return m ? m[1].trim() : void 0;
673
+ };
674
+ fm.name = str("name");
675
+ fm.model = str("model");
676
+ fm.color = str("color");
677
+ fm.description = str("description");
678
+ fm.permissionMode = str("permissionMode");
679
+ const skillsMatch = block.match(/^skills:\s*\n((?:\s+-\s+.+\n?)*)/m);
680
+ if (skillsMatch) {
681
+ fm.skills = skillsMatch[1].split("\n").map((line) => line.replace(/^\s+-\s+/, "").trim()).filter(Boolean);
682
+ }
683
+ return fm;
684
+ }
685
+ function extractAgentBody(content) {
686
+ const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)$/);
687
+ return match ? match[1].trim() : content.trim();
688
+ }
689
+ function findPluginInstallPath(namespace) {
690
+ try {
691
+ const registryPath2 = join3(homedir(), ".claude", "plugins", "installed_plugins.json");
692
+ const registry = JSON.parse(readFileSync5(registryPath2, "utf-8"));
693
+ for (const key of Object.keys(registry)) {
694
+ if (key.startsWith(`${namespace}@`)) {
695
+ return registry[key].installPath ?? null;
696
+ }
697
+ }
698
+ } catch {
699
+ }
700
+ return null;
701
+ }
702
+ function resolveAgentTypePath(agentType, pluginDir, cwd) {
703
+ if (!agentType) return null;
704
+ let namespace;
705
+ let name;
706
+ if (agentType.includes(":")) {
707
+ [namespace, name] = agentType.split(":", 2);
708
+ } else {
709
+ name = agentType;
710
+ }
711
+ const searchPaths = [];
712
+ if (namespace) {
713
+ searchPaths.push(join3(pluginDir, "agents", `${name}.md`));
714
+ const installPath = findPluginInstallPath(namespace);
715
+ if (installPath) {
716
+ searchPaths.push(join3(installPath, "agents", `${name}.md`));
717
+ }
718
+ } else {
719
+ searchPaths.push(join3(cwd, ".claude", "agents", `${name}.md`));
720
+ searchPaths.push(join3(homedir(), ".claude", "agents", `${name}.md`));
721
+ searchPaths.push(join3(pluginDir, "agents", `${name}.md`));
722
+ }
723
+ for (const path of searchPaths) {
724
+ if (existsSync3(path)) return path;
725
+ }
726
+ return null;
727
+ }
728
+ function resolveAgentConfig(agentType, pluginDir, cwd) {
729
+ const filePath = resolveAgentTypePath(agentType, pluginDir, cwd);
730
+ if (!filePath) return null;
731
+ try {
732
+ const content = readFileSync5(filePath, "utf-8");
733
+ return {
734
+ frontmatter: parseAgentFrontmatter(content),
735
+ body: extractAgentBody(content),
736
+ filePath
737
+ };
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+
695
743
  // src/daemon/agent.ts
696
744
  var agentCounters = /* @__PURE__ */ new Map();
697
745
  function resetAgentCounterFromState(sessionId, agents) {
@@ -731,7 +779,9 @@ async function spawnAgent(opts) {
731
779
  agentCounters.set(sessionId, count);
732
780
  const agentId = `agent-${String(count).padStart(3, "0")}`;
733
781
  const pluginPath = resolve2(import.meta.dirname, "../templates/agent-plugin");
734
- const color = resolveAgentTypeColor(agentType, pluginPath, cwd) ?? getNextColor(sessionId);
782
+ const agentConfig = resolveAgentConfig(agentType, pluginPath, cwd);
783
+ const provider = detectProvider(agentConfig?.frontmatter.model);
784
+ const color = (agentConfig?.frontmatter.color ? normalizeTmuxColor(agentConfig.frontmatter.color) : null) ?? getNextColor(sessionId);
735
785
  let paneCwd = cwd;
736
786
  let worktreePath;
737
787
  let branchName;
@@ -747,26 +797,45 @@ async function spawnAgent(opts) {
747
797
  }
748
798
  const paneId = createPane(windowId, paneCwd);
749
799
  registerPane(paneId, sessionId, "agent", agentId);
750
- setPaneTitle(paneId, `${name} (${agentId})`);
800
+ const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
801
+ const paneLabel = shortType ? `${name}-${shortType}` : name;
802
+ setPaneTitle(paneId, `${paneLabel} (${agentId})`);
751
803
  setPaneStyle(paneId, color);
752
804
  const suffix = renderAgentSuffix(sessionId, instruction, worktreeContext);
753
805
  const suffixFilePath = `${promptsDir(cwd, sessionId)}/${agentId}-system.md`;
754
806
  writeFileSync3(suffixFilePath, suffix, "utf-8");
755
807
  const bannerPath = resolve2(import.meta.dirname, "../templates/banner.txt");
756
- const bannerCmd = existsSync3(bannerPath) ? `cat '${bannerPath}' &&` : "";
808
+ const bannerCmd = existsSync4(bannerPath) ? `cat '${bannerPath}' &&` : "";
757
809
  const envExports = [
758
810
  `export SISYPHUS_SESSION_ID='${sessionId}'`,
759
811
  `export SISYPHUS_AGENT_ID='${agentId}'`,
760
812
  ...worktreeContext ? [`export SISYPHUS_PORT_OFFSET='${worktreeContext.offset}'`] : []
761
813
  ].join(" && ");
762
- const agentFlag = agentType ? ` --agent ${shellQuote3(agentType)}` : "";
763
- const claudeCmd = `claude --dangerously-skip-permissions --plugin-dir "${pluginPath}"${agentFlag} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote3(instruction)}`;
764
814
  const notifyCmd = `sisyphus notify pane-exited --pane-id ${paneId}`;
765
- const fullCmd = `${bannerCmd} ${envExports} && ${claudeCmd}; ${notifyCmd}`;
815
+ let mainCmd;
816
+ if (provider === "openai") {
817
+ const codexPromptPath = `${promptsDir(cwd, sessionId)}/${agentId}-codex-prompt.md`;
818
+ const parts = [];
819
+ if (agentConfig?.body) {
820
+ parts.push(agentConfig.body);
821
+ }
822
+ parts.push(suffix);
823
+ parts.push(`## Task
824
+
825
+ ${instruction}`);
826
+ writeFileSync3(codexPromptPath, parts.join("\n\n"), "utf-8");
827
+ const model = agentConfig?.frontmatter.model ?? "codex-mini";
828
+ mainCmd = `codex -m ${shellQuote3(model)} --dangerously-bypass-approvals-and-sandbox "$(cat '${codexPromptPath}')"`;
829
+ } else {
830
+ const agentFlag = agentType && agentType !== "worker" ? ` --agent ${shellQuote3(agentType)}` : "";
831
+ mainCmd = `claude --dangerously-skip-permissions --plugin-dir "${pluginPath}"${agentFlag} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote3(instruction)}`;
832
+ }
833
+ const fullCmd = `${bannerCmd} ${envExports} && ${mainCmd}; ${notifyCmd}`;
766
834
  const agent = {
767
835
  id: agentId,
768
836
  name,
769
837
  agentType,
838
+ provider,
770
839
  color,
771
840
  instruction,
772
841
  status: "running",
@@ -961,9 +1030,9 @@ async function pollSession(sessionId, cwd, windowId) {
961
1030
  }
962
1031
 
963
1032
  // src/daemon/session-manager.ts
964
- async function startSession(task, cwd, tmuxSession, windowId) {
1033
+ async function startSession(task, cwd, tmuxSession, windowId, context) {
965
1034
  const sessionId = uuidv4();
966
- const session = createSession(sessionId, task, cwd);
1035
+ const session = createSession(sessionId, task, cwd, context);
967
1036
  await updateSessionTmux(cwd, sessionId, tmuxSession, windowId);
968
1037
  trackSession(sessionId, cwd, tmuxSession);
969
1038
  await spawnOrchestrator(sessionId, cwd, windowId);
@@ -976,7 +1045,7 @@ var PRUNE_KEEP_DAYS = 7;
976
1045
  function pruneOldSessions(cwd) {
977
1046
  try {
978
1047
  const dir = sessionsDir(cwd);
979
- if (!existsSync4(dir)) return;
1048
+ if (!existsSync5(dir)) return;
980
1049
  const entries = readdirSync4(dir, { withFileTypes: true });
981
1050
  const candidates = [];
982
1051
  for (const entry of entries) {
@@ -1043,7 +1112,7 @@ function getSessionStatus(cwd, sessionId) {
1043
1112
  }
1044
1113
  function listSessions(cwd) {
1045
1114
  const dir = sessionsDir(cwd);
1046
- if (!existsSync4(dir)) return [];
1115
+ if (!existsSync5(dir)) return [];
1047
1116
  const entries = readdirSync4(dir, { withFileTypes: true });
1048
1117
  const sessions = [];
1049
1118
  for (const entry of entries) {
@@ -1088,6 +1157,11 @@ function onAllAgentsDone2(sessionId, cwd, windowId) {
1088
1157
  async function handleSpawn(sessionId, cwd, agentType, name, instruction, worktree) {
1089
1158
  const windowId = getWindowId(sessionId);
1090
1159
  if (!windowId) throw new Error(`No tmux window found for session ${sessionId}`);
1160
+ const session = getSession(cwd, sessionId);
1161
+ if (session.status === "completed") {
1162
+ await updateSessionStatus(cwd, sessionId, "active");
1163
+ trackSession(sessionId, cwd, session.tmuxSessionName);
1164
+ }
1091
1165
  const agent = await spawnAgent({
1092
1166
  sessionId,
1093
1167
  cwd,
@@ -1209,7 +1283,7 @@ function persistSessionRegistry() {
1209
1283
  }
1210
1284
  function loadSessionRegistry() {
1211
1285
  const p = registryPath();
1212
- if (!existsSync5(p)) return {};
1286
+ if (!existsSync6(p)) return {};
1213
1287
  try {
1214
1288
  return JSON.parse(readFileSync7(p, "utf-8"));
1215
1289
  } catch {
@@ -1228,7 +1302,7 @@ async function handleRequest(req) {
1228
1302
  try {
1229
1303
  switch (req.type) {
1230
1304
  case "start": {
1231
- const session = await startSession(req.task, req.cwd, req.tmuxSession, req.tmuxWindow);
1305
+ const session = await startSession(req.task, req.cwd, req.tmuxSession, req.tmuxWindow, req.context);
1232
1306
  registerSessionCwd(session.id, req.cwd);
1233
1307
  sessionTmuxMap.set(session.id, req.tmuxSession);
1234
1308
  sessionWindowMap2.set(session.id, req.tmuxWindow);
@@ -1305,7 +1379,7 @@ async function handleRequest(req) {
1305
1379
  let cwd = sessionCwdMap.get(req.sessionId);
1306
1380
  if (!cwd) {
1307
1381
  const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
1308
- if (existsSync5(stateFile)) {
1382
+ if (existsSync6(stateFile)) {
1309
1383
  cwd = req.cwd;
1310
1384
  registerSessionCwd(req.sessionId, cwd);
1311
1385
  } else {
@@ -1356,7 +1430,7 @@ async function handleRequest(req) {
1356
1430
  function startServer() {
1357
1431
  return new Promise((resolve4, reject) => {
1358
1432
  const sock = socketPath();
1359
- if (existsSync5(sock)) {
1433
+ if (existsSync6(sock)) {
1360
1434
  unlinkSync(sock);
1361
1435
  }
1362
1436
  server = createServer((conn) => {
@@ -1398,7 +1472,7 @@ function stopServer() {
1398
1472
  }
1399
1473
  server.close(() => {
1400
1474
  const sock = socketPath();
1401
- if (existsSync5(sock)) {
1475
+ if (existsSync6(sock)) {
1402
1476
  unlinkSync(sock);
1403
1477
  }
1404
1478
  server = null;
@@ -1604,7 +1678,7 @@ async function recoverSessions() {
1604
1678
  let recovered = 0;
1605
1679
  for (const [sessionId, cwd] of entries) {
1606
1680
  const stateFile = statePath(cwd, sessionId);
1607
- if (!existsSync6(stateFile)) {
1681
+ if (!existsSync7(stateFile)) {
1608
1682
  continue;
1609
1683
  }
1610
1684
  try {