sisyphi 1.0.5 → 1.0.7
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/chunk-6G226ZK7.js +11 -0
- package/dist/chunk-6G226ZK7.js.map +1 -0
- package/dist/{chunk-YGBGKMTF.js → chunk-JXKUI4P6.js} +28 -1
- package/dist/{chunk-YGBGKMTF.js.map → chunk-JXKUI4P6.js.map} +1 -1
- package/dist/chunk-LWWRGQWM.js +87 -0
- package/dist/chunk-LWWRGQWM.js.map +1 -0
- package/dist/{chunk-DBR33QHM.js → chunk-T7ETTIQK.js} +81 -2
- package/dist/chunk-T7ETTIQK.js.map +1 -0
- package/dist/cli.js +241 -133
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +397 -349
- package/dist/daemon.js.map +1 -1
- package/dist/{paths-FYYSBD27.js → paths-NUUALUVP.js} +2 -2
- package/dist/templates/agent-plugin/hooks/plan-user-prompt.sh +19 -0
- package/dist/templates/agent-plugin/hooks/spec-user-prompt.sh +22 -0
- package/dist/templates/orchestrator-planning.md +8 -0
- package/dist/tui.js +850 -841
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/agent-plugin/hooks/plan-user-prompt.sh +19 -0
- package/templates/agent-plugin/hooks/spec-user-prompt.sh +22 -0
- package/templates/orchestrator-planning.md +8 -0
- package/dist/chunk-DBR33QHM.js.map +0 -1
- package/dist/chunk-KQBSC5KY.js +0 -31
- package/dist/chunk-KQBSC5KY.js.map +0 -1
- /package/dist/{paths-FYYSBD27.js.map → paths-NUUALUVP.js.map} +0 -0
package/dist/daemon.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
EXEC_ENV,
|
|
4
|
+
exec,
|
|
5
|
+
execEnv,
|
|
6
|
+
execSafe,
|
|
3
7
|
loadConfig
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LWWRGQWM.js";
|
|
9
|
+
import {
|
|
10
|
+
shellQuote
|
|
11
|
+
} from "./chunk-6G226ZK7.js";
|
|
5
12
|
import {
|
|
6
13
|
contextDir,
|
|
7
14
|
cycleLogPath,
|
|
@@ -25,21 +32,21 @@ import {
|
|
|
25
32
|
statePath,
|
|
26
33
|
worktreeBaseDir,
|
|
27
34
|
worktreeConfigPath
|
|
28
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-JXKUI4P6.js";
|
|
29
36
|
|
|
30
37
|
// src/daemon/index.ts
|
|
31
|
-
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as
|
|
38
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
|
|
32
39
|
import { execSync as execSync4 } from "child_process";
|
|
33
40
|
import { setTimeout as sleep } from "timers/promises";
|
|
34
41
|
|
|
35
42
|
// src/daemon/server.ts
|
|
36
43
|
import { createServer } from "net";
|
|
37
|
-
import { unlinkSync, existsSync as
|
|
44
|
+
import { unlinkSync, existsSync as existsSync8, writeFileSync as writeFileSync5, readFileSync as readFileSync6, mkdirSync as mkdirSync4, rmSync as rmSync4 } from "fs";
|
|
38
45
|
import { join as join5 } from "path";
|
|
39
46
|
|
|
40
47
|
// src/daemon/session-manager.ts
|
|
41
48
|
import { v4 as uuidv4 } from "uuid";
|
|
42
|
-
import { existsSync as
|
|
49
|
+
import { existsSync as existsSync7, readdirSync as readdirSync6, rmSync as rmSync3 } from "fs";
|
|
43
50
|
|
|
44
51
|
// src/daemon/state.ts
|
|
45
52
|
import { randomUUID } from "crypto";
|
|
@@ -57,16 +64,16 @@ Agents save exploration findings, architectural notes, and reference material he
|
|
|
57
64
|
var sessionLocks = /* @__PURE__ */ new Map();
|
|
58
65
|
async function withSessionLock(sessionId, fn) {
|
|
59
66
|
const prev = sessionLocks.get(sessionId) ?? Promise.resolve();
|
|
60
|
-
let
|
|
67
|
+
let resolve5;
|
|
61
68
|
const next = new Promise((r) => {
|
|
62
|
-
|
|
69
|
+
resolve5 = r;
|
|
63
70
|
});
|
|
64
71
|
sessionLocks.set(sessionId, next);
|
|
65
72
|
await prev;
|
|
66
73
|
try {
|
|
67
74
|
return fn();
|
|
68
75
|
} finally {
|
|
69
|
-
|
|
76
|
+
resolve5();
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
function atomicWrite(filePath, data) {
|
|
@@ -293,8 +300,35 @@ function deleteSnapshotsAfter(cwd, sessionId, afterCycle) {
|
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
// src/daemon/orchestrator.ts
|
|
296
|
-
import { existsSync as
|
|
297
|
-
import {
|
|
303
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
304
|
+
import { execSync } from "child_process";
|
|
305
|
+
import { resolve as resolve2, join as join3 } from "path";
|
|
306
|
+
|
|
307
|
+
// src/daemon/spawn-helpers.ts
|
|
308
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
309
|
+
import { resolve } from "path";
|
|
310
|
+
function resolveCliBin() {
|
|
311
|
+
return resolve(import.meta.dirname, "cli.js");
|
|
312
|
+
}
|
|
313
|
+
function resolveNpmBinDir() {
|
|
314
|
+
return resolve(import.meta.dirname, "../../.bin");
|
|
315
|
+
}
|
|
316
|
+
function resolveBannerCmd() {
|
|
317
|
+
const bannerPath = resolve(import.meta.dirname, "../templates/banner.txt");
|
|
318
|
+
return existsSync2(bannerPath) ? `cat '${bannerPath}'` : null;
|
|
319
|
+
}
|
|
320
|
+
function buildEnvExports(statements) {
|
|
321
|
+
return statements.join(" && ");
|
|
322
|
+
}
|
|
323
|
+
function buildNotifyCmd(paneId) {
|
|
324
|
+
const cliBin = resolveCliBin();
|
|
325
|
+
return `node "${cliBin}" notify pane-exited --pane-id ${paneId}`;
|
|
326
|
+
}
|
|
327
|
+
function writeRunScript(dir, name, lines) {
|
|
328
|
+
const scriptPath = `${dir}/${name}.sh`;
|
|
329
|
+
writeFileSync2(scriptPath, lines.join("\n"), { mode: 493 });
|
|
330
|
+
return scriptPath;
|
|
331
|
+
}
|
|
298
332
|
|
|
299
333
|
// src/daemon/colors.ts
|
|
300
334
|
var ORCHESTRATOR_COLOR = "yellow";
|
|
@@ -318,7 +352,7 @@ function resetColors(sessionId) {
|
|
|
318
352
|
}
|
|
319
353
|
|
|
320
354
|
// src/daemon/frontmatter.ts
|
|
321
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
355
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
322
356
|
import { homedir } from "os";
|
|
323
357
|
import { join as join2, basename } from "path";
|
|
324
358
|
function detectProvider(model) {
|
|
@@ -386,7 +420,7 @@ function resolveAgentTypePath(agentType, pluginDir, cwd) {
|
|
|
386
420
|
searchPaths.push(join2(pluginDir, "agents", `${name}.md`));
|
|
387
421
|
}
|
|
388
422
|
for (const path of searchPaths) {
|
|
389
|
-
if (
|
|
423
|
+
if (existsSync3(path)) return path;
|
|
390
424
|
}
|
|
391
425
|
return null;
|
|
392
426
|
}
|
|
@@ -452,21 +486,6 @@ function resolveAgentConfig(agentType, pluginDir, cwd) {
|
|
|
452
486
|
}
|
|
453
487
|
|
|
454
488
|
// src/daemon/tmux.ts
|
|
455
|
-
import { execSync } from "child_process";
|
|
456
|
-
var EXEC_ENV = {
|
|
457
|
-
...process.env,
|
|
458
|
-
PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env["PATH"] ?? "/usr/bin:/bin"}`
|
|
459
|
-
};
|
|
460
|
-
function exec(cmd) {
|
|
461
|
-
return execSync(cmd, { encoding: "utf-8", env: EXEC_ENV }).trim();
|
|
462
|
-
}
|
|
463
|
-
function execSafe(cmd) {
|
|
464
|
-
try {
|
|
465
|
-
return execSync(cmd, { encoding: "utf-8", env: EXEC_ENV, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
466
|
-
} catch {
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
489
|
function createPane(windowTarget, cwd, position = "right") {
|
|
471
490
|
const cwdFlag = cwd ? ` -c ${shellQuote(cwd)}` : "";
|
|
472
491
|
const panes = listPanes(windowTarget);
|
|
@@ -490,6 +509,7 @@ function createSession2(sessionName, windowName, cwd) {
|
|
|
490
509
|
exec(`tmux new-session -d -s "${sessionName}" -n "${windowName}" -c ${shellQuote(cwd)}`);
|
|
491
510
|
const windowId = exec(`tmux display-message -t "${sessionName}:${windowName}" -p "#{window_id}"`);
|
|
492
511
|
const initialPaneId = exec(`tmux display-message -t "${sessionName}:${windowName}" -p "#{pane_id}"`);
|
|
512
|
+
configureSessionDefaults(sessionName, windowId);
|
|
493
513
|
return { windowId, initialPaneId };
|
|
494
514
|
}
|
|
495
515
|
function paneExists(paneTarget) {
|
|
@@ -516,7 +536,8 @@ function setPaneTitle(paneTarget, title) {
|
|
|
516
536
|
execSafe(`tmux select-pane -t "${paneTarget}" -T ${shellQuote(title)}`);
|
|
517
537
|
}
|
|
518
538
|
function setPaneStyle(paneTarget, color) {
|
|
519
|
-
const
|
|
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]`;
|
|
520
541
|
execSafe(`tmux set -p -t "${paneTarget}" pane-border-format ${shellQuote(fmt)}`);
|
|
521
542
|
execSafe(`tmux set -p -t "${paneTarget}" @pane_color "${color}"`);
|
|
522
543
|
execSafe(`tmux set -w -t "${paneTarget}" pane-border-style "fg=#{?#{@pane_color},#{@pane_color},default}"`);
|
|
@@ -525,8 +546,12 @@ function setPaneStyle(paneTarget, color) {
|
|
|
525
546
|
function selectLayout(windowTarget, layout = "even-horizontal") {
|
|
526
547
|
execSafe(`tmux select-layout -t "${windowTarget}" ${layout}`);
|
|
527
548
|
}
|
|
528
|
-
function
|
|
529
|
-
|
|
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"`);
|
|
530
555
|
}
|
|
531
556
|
|
|
532
557
|
// src/daemon/pane-registry.ts
|
|
@@ -573,16 +598,16 @@ function setOrchestratorPaneId(sessionId, paneId) {
|
|
|
573
598
|
}
|
|
574
599
|
function loadOrchestratorPrompt(cwd, mode) {
|
|
575
600
|
const projectPath = projectOrchestratorPromptPath(cwd);
|
|
576
|
-
if (
|
|
601
|
+
if (existsSync4(projectPath)) {
|
|
577
602
|
return readFileSync3(projectPath, "utf-8");
|
|
578
603
|
}
|
|
579
|
-
const basePath =
|
|
604
|
+
const basePath = resolve2(import.meta.dirname, "../templates/orchestrator-base.md");
|
|
580
605
|
const base = readFileSync3(basePath, "utf-8");
|
|
581
606
|
if (mode === "implementation") {
|
|
582
|
-
const implPath =
|
|
607
|
+
const implPath = resolve2(import.meta.dirname, "../templates/orchestrator-impl.md");
|
|
583
608
|
return base + "\n\n" + readFileSync3(implPath, "utf-8");
|
|
584
609
|
}
|
|
585
|
-
const planningPath =
|
|
610
|
+
const planningPath = resolve2(import.meta.dirname, "../templates/orchestrator-planning.md");
|
|
586
611
|
return base + "\n\n" + readFileSync3(planningPath, "utf-8");
|
|
587
612
|
}
|
|
588
613
|
function formatStateForOrchestrator(session) {
|
|
@@ -601,7 +626,7 @@ ${session.context}
|
|
|
601
626
|
}
|
|
602
627
|
} else {
|
|
603
628
|
let ctxFiles = [];
|
|
604
|
-
if (
|
|
629
|
+
if (existsSync4(ctxDir)) {
|
|
605
630
|
ctxFiles = readdirSync3(ctxDir).filter((f) => f !== "CLAUDE.md");
|
|
606
631
|
}
|
|
607
632
|
if (ctxFiles.length > 0) {
|
|
@@ -640,37 +665,24 @@ ${lines.join("\n")}
|
|
|
640
665
|
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
641
666
|
if (lastCycle && lastCycle.agentsSpawned.length > 0) {
|
|
642
667
|
const agentMap = new Map(session.agents.map((a) => [a.id, a]));
|
|
643
|
-
const
|
|
668
|
+
const agentLines = lastCycle.agentsSpawned.map((id) => {
|
|
644
669
|
const agent = agentMap.get(id);
|
|
645
|
-
if (!agent) return
|
|
646
|
-
(no agent data)
|
|
647
|
-
</agent-${id}>`;
|
|
670
|
+
if (!agent) return `- **${id}**: unknown (no agent data)`;
|
|
648
671
|
const finalReport = agent.reports.find((r) => r.type === "final");
|
|
649
672
|
const reportToUse = finalReport ?? agent.reports[agent.reports.length - 1];
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
try {
|
|
653
|
-
reportContent = readFileSync3(reportToUse.filePath, "utf-8");
|
|
654
|
-
} catch {
|
|
655
|
-
reportContent = `(could not read report: ${reportToUse.filePath})`;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return `<agent-${id} name="${agent.name}" status="${agent.status}">
|
|
659
|
-
${reportContent}
|
|
660
|
-
</agent-${id}>`;
|
|
673
|
+
const reportRef = reportToUse ? `@${reportToUse.filePath}` : "(no reports)";
|
|
674
|
+
return `- **${id}** (${agent.name}) [${agent.status}]: ${reportRef}`;
|
|
661
675
|
}).join("\n");
|
|
662
676
|
mostRecentCycleSection = `
|
|
663
677
|
### Most Recent Cycle
|
|
664
678
|
|
|
665
|
-
|
|
666
|
-
${agentBlocks}
|
|
667
|
-
</last-cycle>
|
|
679
|
+
${agentLines}
|
|
668
680
|
`;
|
|
669
681
|
}
|
|
670
|
-
const roadmapRef =
|
|
682
|
+
const roadmapRef = existsSync4(roadmapFile) ? `@${roadmapFile}` : "(empty)";
|
|
671
683
|
const worktreeAgents = session.agents.filter((a) => a.worktreePath);
|
|
672
684
|
let worktreeSection = "";
|
|
673
|
-
if (worktreeAgents.length > 0 ||
|
|
685
|
+
if (worktreeAgents.length > 0 || existsSync4(worktreeConfigPath(session.cwd))) {
|
|
674
686
|
let wtLines = "";
|
|
675
687
|
if (worktreeAgents.length > 0) {
|
|
676
688
|
wtLines = "\n" + worktreeAgents.map((a) => {
|
|
@@ -686,7 +698,7 @@ ${agentBlocks}
|
|
|
686
698
|
return `- ${a.id}: ${status} (branch ${a.branchName})`;
|
|
687
699
|
}).join("\n");
|
|
688
700
|
}
|
|
689
|
-
const worktreeHint =
|
|
701
|
+
const worktreeHint = existsSync4(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.";
|
|
690
702
|
worktreeSection = `
|
|
691
703
|
|
|
692
704
|
## Git Worktrees
|
|
@@ -694,7 +706,7 @@ ${agentBlocks}
|
|
|
694
706
|
${worktreeHint}${wtLines}`;
|
|
695
707
|
}
|
|
696
708
|
const goalFile = goalPath(session.cwd, session.id);
|
|
697
|
-
const goalContent =
|
|
709
|
+
const goalContent = existsSync4(goalFile) ? readFileSync3(goalFile, "utf-8").trim() : session.task;
|
|
698
710
|
return `## Goal
|
|
699
711
|
|
|
700
712
|
${goalContent}
|
|
@@ -709,12 +721,17 @@ ${roadmapRef}
|
|
|
709
721
|
${worktreeSection}`;
|
|
710
722
|
}
|
|
711
723
|
async function spawnOrchestrator(sessionId, cwd, windowId, message) {
|
|
724
|
+
try {
|
|
725
|
+
execSync("which claude", { stdio: "pipe", env: EXEC_ENV });
|
|
726
|
+
} catch {
|
|
727
|
+
throw new Error("Claude CLI not found on PATH. Run `sisyphus doctor` to diagnose.");
|
|
728
|
+
}
|
|
712
729
|
const session = getSession(cwd, sessionId);
|
|
713
730
|
const lastCycle = [...session.orchestratorCycles].reverse().find((c) => c.completedAt);
|
|
714
731
|
const mode = lastCycle?.mode ?? "planning";
|
|
715
732
|
const basePrompt = loadOrchestratorPrompt(cwd, mode);
|
|
716
733
|
const formattedState = formatStateForOrchestrator(session);
|
|
717
|
-
const agentPluginPath =
|
|
734
|
+
const agentPluginPath = resolve2(import.meta.dirname, "../templates/agent-plugin");
|
|
718
735
|
const agentTypes = discoverAgentTypes(agentPluginPath, session.cwd);
|
|
719
736
|
agentTypes.push(
|
|
720
737
|
{ qualifiedName: "Explore", source: "bundled", model: "haiku", description: "Fast codebase exploration \u2014 find files, search code, answer questions about architecture. Use for research and context gathering." }
|
|
@@ -727,16 +744,15 @@ async function spawnOrchestrator(sessionId, cwd, windowId, message) {
|
|
|
727
744
|
const systemPrompt = basePrompt.replace("{{AGENT_TYPES}}", agentTypeLines);
|
|
728
745
|
const cycleNum = session.orchestratorCycles.length + 1;
|
|
729
746
|
const promptFilePath = `${promptsDir(cwd, sessionId)}/orchestrator-system-${cycleNum}.md`;
|
|
730
|
-
|
|
747
|
+
writeFileSync3(promptFilePath, systemPrompt, "utf-8");
|
|
731
748
|
sessionWindowMap.set(sessionId, windowId);
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
const envExports = [
|
|
749
|
+
const npmBinDir = resolveNpmBinDir();
|
|
750
|
+
const envExports = buildEnvExports([
|
|
735
751
|
`export SISYPHUS_SESSION_ID='${sessionId}'`,
|
|
736
752
|
`export SISYPHUS_AGENT_ID='orchestrator'`,
|
|
737
753
|
`export SISYPHUS_CWD='${cwd}'`,
|
|
738
754
|
`export PATH="${npmBinDir}:$PATH"`
|
|
739
|
-
]
|
|
755
|
+
]);
|
|
740
756
|
let userPrompt = formattedState;
|
|
741
757
|
if (message) {
|
|
742
758
|
userPrompt += `
|
|
@@ -754,12 +770,12 @@ The user resumed this session with new instructions: ${message}`;
|
|
|
754
770
|
${continuationText}`;
|
|
755
771
|
}
|
|
756
772
|
const userPromptFilePath = `${promptsDir(cwd, sessionId)}/orchestrator-user-${cycleNum}.md`;
|
|
757
|
-
|
|
773
|
+
writeFileSync3(userPromptFilePath, userPrompt, "utf-8");
|
|
758
774
|
if (session.messages && session.messages.length > 0) {
|
|
759
775
|
await drainMessages(cwd, sessionId, session.messages.length);
|
|
760
776
|
}
|
|
761
|
-
const pluginPath =
|
|
762
|
-
const settingsPath =
|
|
777
|
+
const pluginPath = resolve2(import.meta.dirname, "../templates/orchestrator-plugin");
|
|
778
|
+
const settingsPath = resolve2(import.meta.dirname, "../templates/orchestrator-settings.json");
|
|
763
779
|
const config = loadConfig(cwd);
|
|
764
780
|
const effort = config.orchestratorEffort ?? "high";
|
|
765
781
|
const claudeCmd = `claude --dangerously-skip-permissions --disallowed-tools "Task,Agent" --effort ${effort} --settings "${settingsPath}" --plugin-dir "${pluginPath}" --name "sisyphus:orch-${sessionId.slice(0, 8)}-cycle-${cycleNum}" --system-prompt "$(cat '${promptFilePath}')" "$(cat '${userPromptFilePath}')"`;
|
|
@@ -768,18 +784,15 @@ ${continuationText}`;
|
|
|
768
784
|
registerPane(paneId, sessionId, "orchestrator");
|
|
769
785
|
setPaneTitle(paneId, `Sisyphus`);
|
|
770
786
|
setPaneStyle(paneId, ORCHESTRATOR_COLOR);
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
const
|
|
774
|
-
const scriptLines = [
|
|
787
|
+
const bannerCmd = resolveBannerCmd();
|
|
788
|
+
const notifyCmd = buildNotifyCmd(paneId);
|
|
789
|
+
const scriptPath = writeRunScript(promptsDir(cwd, sessionId), `orchestrator-run-${cycleNum}`, [
|
|
775
790
|
"#!/usr/bin/env bash",
|
|
776
791
|
...bannerCmd ? [bannerCmd] : [],
|
|
777
792
|
envExports,
|
|
778
|
-
|
|
793
|
+
claudeCmd,
|
|
779
794
|
notifyCmd
|
|
780
|
-
];
|
|
781
|
-
const scriptPath = `${promptsDir(cwd, sessionId)}/orchestrator-run-${cycleNum}.sh`;
|
|
782
|
-
writeFileSync2(scriptPath, scriptLines.join("\n"), { mode: 493 });
|
|
795
|
+
]);
|
|
783
796
|
sendKeys(paneId, `bash '${scriptPath}'`);
|
|
784
797
|
await addOrchestratorCycle(cwd, sessionId, {
|
|
785
798
|
cycle: cycleNum,
|
|
@@ -823,30 +836,13 @@ function cleanupSessionMaps(sessionId) {
|
|
|
823
836
|
}
|
|
824
837
|
|
|
825
838
|
// src/daemon/agent.ts
|
|
826
|
-
import { readFileSync as readFileSync5, writeFileSync as
|
|
827
|
-
import {
|
|
839
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, copyFileSync as copyFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync5 } from "fs";
|
|
840
|
+
import { execSync as execSync2 } from "child_process";
|
|
841
|
+
import { resolve as resolve3 } from "path";
|
|
828
842
|
|
|
829
843
|
// src/daemon/worktree.ts
|
|
830
|
-
import {
|
|
831
|
-
import {
|
|
832
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
833
|
-
var EXEC_ENV2 = {
|
|
834
|
-
...process.env,
|
|
835
|
-
PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env["PATH"] ?? "/usr/bin:/bin"}`
|
|
836
|
-
};
|
|
837
|
-
function exec2(cmd, cwd) {
|
|
838
|
-
return execSync2(cmd, { encoding: "utf-8", env: EXEC_ENV2, cwd }).trim();
|
|
839
|
-
}
|
|
840
|
-
function execSafe2(cmd, cwd) {
|
|
841
|
-
try {
|
|
842
|
-
return exec2(cmd, cwd);
|
|
843
|
-
} catch {
|
|
844
|
-
return null;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
function shellQuote2(s) {
|
|
848
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
849
|
-
}
|
|
844
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync4, rmSync as rmSync2 } from "fs";
|
|
845
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
850
846
|
function loadWorktreeConfig(cwd) {
|
|
851
847
|
try {
|
|
852
848
|
const content = readFileSync4(worktreeConfigPath(cwd), "utf-8");
|
|
@@ -858,52 +854,52 @@ function loadWorktreeConfig(cwd) {
|
|
|
858
854
|
function createWorktreeShell(cwd, sessionId, agentId) {
|
|
859
855
|
const branchName = `sisyphus/${sessionId.slice(0, 8)}/${agentId}`;
|
|
860
856
|
const worktreePath = join4(worktreeBaseDir(cwd), sessionId.slice(0, 8), agentId);
|
|
861
|
-
mkdirSync2(
|
|
862
|
-
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
857
|
+
mkdirSync2(dirname2(worktreePath), { recursive: true });
|
|
858
|
+
execSafe(`git -C ${shellQuote(cwd)} worktree prune`);
|
|
859
|
+
if (existsSync5(worktreePath)) {
|
|
860
|
+
execSafe(`git -C ${shellQuote(cwd)} worktree remove --force ${shellQuote(worktreePath)}`);
|
|
861
|
+
}
|
|
862
|
+
execSafe(`git -C ${shellQuote(cwd)} branch -D ${shellQuote(branchName)}`);
|
|
863
|
+
exec(`git -C ${shellQuote(cwd)} branch ${shellQuote(branchName)} HEAD`);
|
|
864
|
+
exec(`git -C ${shellQuote(cwd)} worktree add ${shellQuote(worktreePath)} ${shellQuote(branchName)}`);
|
|
869
865
|
return { worktreePath, branchName };
|
|
870
866
|
}
|
|
871
867
|
function bootstrapWorktree(cwd, worktreePath, config) {
|
|
872
868
|
if (config.copy) {
|
|
873
869
|
for (const entry of config.copy) {
|
|
874
870
|
const dest = join4(worktreePath, entry);
|
|
875
|
-
mkdirSync2(
|
|
876
|
-
|
|
871
|
+
mkdirSync2(dirname2(dest), { recursive: true });
|
|
872
|
+
execSafe(`cp -r ${shellQuote(join4(cwd, entry))} ${shellQuote(dest)}`);
|
|
877
873
|
}
|
|
878
874
|
}
|
|
879
875
|
if (config.clone) {
|
|
880
876
|
for (const entry of config.clone) {
|
|
881
877
|
const dest = join4(worktreePath, entry);
|
|
882
|
-
mkdirSync2(
|
|
883
|
-
const src =
|
|
884
|
-
const dstQ =
|
|
885
|
-
if (
|
|
886
|
-
|
|
878
|
+
mkdirSync2(dirname2(dest), { recursive: true });
|
|
879
|
+
const src = shellQuote(join4(cwd, entry));
|
|
880
|
+
const dstQ = shellQuote(dest);
|
|
881
|
+
if (execSafe(`cp -Rc ${src} ${dstQ}`) === null) {
|
|
882
|
+
execSafe(`cp -r ${src} ${dstQ}`);
|
|
887
883
|
}
|
|
888
884
|
}
|
|
889
885
|
}
|
|
890
886
|
if (config.symlink) {
|
|
891
887
|
for (const entry of config.symlink) {
|
|
892
888
|
const dest = join4(worktreePath, entry);
|
|
893
|
-
mkdirSync2(
|
|
894
|
-
|
|
889
|
+
mkdirSync2(dirname2(dest), { recursive: true });
|
|
890
|
+
execSafe(`ln -s ${shellQuote(join4(cwd, entry))} ${shellQuote(dest)}`);
|
|
895
891
|
}
|
|
896
892
|
}
|
|
897
893
|
if (config.init) {
|
|
898
894
|
try {
|
|
899
|
-
|
|
895
|
+
exec(config.init, worktreePath);
|
|
900
896
|
} catch (err) {
|
|
901
897
|
console.error(`[sisyphus] worktree init command failed: ${err instanceof Error ? err.message : err}`);
|
|
902
898
|
}
|
|
903
899
|
}
|
|
904
900
|
}
|
|
905
901
|
function resolveWorktreeBranch(cwd, worktreePath) {
|
|
906
|
-
const output =
|
|
902
|
+
const output = execSafe(`git -C ${shellQuote(cwd)} worktree list --porcelain`);
|
|
907
903
|
if (!output) return null;
|
|
908
904
|
const lines = output.split("\n");
|
|
909
905
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -925,30 +921,30 @@ function mergeWorktrees(cwd, agents) {
|
|
|
925
921
|
(a) => a.worktreePath && a.mergeStatus === "pending"
|
|
926
922
|
);
|
|
927
923
|
const results = [];
|
|
928
|
-
|
|
929
|
-
|
|
924
|
+
execSafe(`git -C ${shellQuote(cwd)} add .sisyphus`);
|
|
925
|
+
execSafe(`git -C ${shellQuote(cwd)} commit -m 'sisyphus: snapshot session state before merge'`);
|
|
930
926
|
for (const agent of pending) {
|
|
931
927
|
const branch = resolveWorktreeBranch(cwd, agent.worktreePath);
|
|
932
928
|
if (!branch) {
|
|
933
929
|
results.push({ agentId: agent.id, name: agent.name, status: "no-changes" });
|
|
934
|
-
|
|
930
|
+
execSafe(`git -C ${shellQuote(cwd)} worktree remove ${shellQuote(agent.worktreePath)} --force`);
|
|
935
931
|
continue;
|
|
936
932
|
}
|
|
937
|
-
const aheadLog =
|
|
933
|
+
const aheadLog = execSafe(`git -C ${shellQuote(cwd)} log HEAD..${shellQuote(branch)} --oneline`);
|
|
938
934
|
if (!aheadLog) {
|
|
939
935
|
results.push({ agentId: agent.id, name: agent.name, status: "no-changes" });
|
|
940
936
|
cleanupWorktree(cwd, agent.worktreePath, branch);
|
|
941
937
|
continue;
|
|
942
938
|
}
|
|
943
939
|
const mergeMsg = `sisyphus: merge ${agent.id} (${agent.name})`;
|
|
944
|
-
const mergeCmd = `git -C ${
|
|
940
|
+
const mergeCmd = `git -C ${shellQuote(cwd)} merge --no-ff ${shellQuote(branch)} -m ${shellQuote(mergeMsg)}`;
|
|
945
941
|
try {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
942
|
+
exec(mergeCmd);
|
|
943
|
+
execSafe(`git -C ${shellQuote(cwd)} worktree remove ${shellQuote(agent.worktreePath)}`);
|
|
944
|
+
execSafe(`git -C ${shellQuote(cwd)} branch -d ${shellQuote(branch)}`);
|
|
949
945
|
results.push({ agentId: agent.id, name: agent.name, status: "merged" });
|
|
950
946
|
} catch (err) {
|
|
951
|
-
|
|
947
|
+
execSafe(`git -C ${shellQuote(cwd)} merge --abort`);
|
|
952
948
|
const errObj = err;
|
|
953
949
|
const stdout = errObj.stdout ? (typeof errObj.stdout === "string" ? errObj.stdout : errObj.stdout.toString("utf-8")).trim() : "";
|
|
954
950
|
const stderr = errObj.stderr ? (typeof errObj.stderr === "string" ? errObj.stderr : errObj.stderr.toString("utf-8")).trim() : "";
|
|
@@ -959,9 +955,9 @@ function mergeWorktrees(cwd, agents) {
|
|
|
959
955
|
return results;
|
|
960
956
|
}
|
|
961
957
|
function cleanupWorktree(cwd, worktreePath, branchName) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const baseDir =
|
|
958
|
+
execSafe(`git -C ${shellQuote(cwd)} worktree remove ${shellQuote(worktreePath)} --force`);
|
|
959
|
+
execSafe(`git -C ${shellQuote(cwd)} branch -D ${shellQuote(branchName)}`);
|
|
960
|
+
const baseDir = dirname2(worktreePath);
|
|
965
961
|
try {
|
|
966
962
|
const entries = readdirSync4(baseDir);
|
|
967
963
|
if (entries.length === 0) {
|
|
@@ -1023,7 +1019,7 @@ function clearAgentCounter(sessionId) {
|
|
|
1023
1019
|
agentCounters.delete(sessionId);
|
|
1024
1020
|
}
|
|
1025
1021
|
function renderAgentSuffix(sessionId, instruction, worktreeContext) {
|
|
1026
|
-
const templatePath =
|
|
1022
|
+
const templatePath = resolve3(import.meta.dirname, "../templates/agent-suffix.md");
|
|
1027
1023
|
let template;
|
|
1028
1024
|
try {
|
|
1029
1025
|
template = readFileSync5(templatePath, "utf-8");
|
|
@@ -1047,7 +1043,7 @@ function createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig) {
|
|
|
1047
1043
|
mkdirSync3(`${base}/.claude-plugin`, { recursive: true });
|
|
1048
1044
|
mkdirSync3(`${base}/agents`, { recursive: true });
|
|
1049
1045
|
mkdirSync3(`${base}/hooks`, { recursive: true });
|
|
1050
|
-
|
|
1046
|
+
writeFileSync4(
|
|
1051
1047
|
`${base}/.claude-plugin/plugin.json`,
|
|
1052
1048
|
JSON.stringify({ name: `sisyphus-agent-${agentId}`, version: "1.0.0" }),
|
|
1053
1049
|
"utf-8"
|
|
@@ -1056,34 +1052,35 @@ function createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig) {
|
|
|
1056
1052
|
const shortName = agentType.replace(/^sisyphus:/, "");
|
|
1057
1053
|
copyFileSync2(agentConfig.filePath, `${base}/agents/${shortName}.md`);
|
|
1058
1054
|
}
|
|
1059
|
-
const srcHooks =
|
|
1060
|
-
for (const f of ["
|
|
1055
|
+
const srcHooks = resolve3(import.meta.dirname, "../templates/agent-plugin/hooks");
|
|
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", hook: { type: "command", command: "bash hooks/intercept-send-message.sh" } }
|
|
1062
|
+
],
|
|
1063
|
+
Stop: [
|
|
1064
|
+
{ hook: { type: "command", command: "bash hooks/require-submit.sh" } }
|
|
1065
|
+
]
|
|
1066
|
+
};
|
|
1067
|
+
const normalizedType = agentType?.replace(/^sisyphus:/, "") ?? "";
|
|
1068
|
+
if (normalizedType === "plan") {
|
|
1069
|
+
hooksConfig.UserPromptSubmit = [
|
|
1070
|
+
{ hook: { type: "command", command: "bash 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
|
+
{ hook: { type: "command", command: "bash 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
|
-
|
|
1066
|
-
const { sessionId, cwd, agentType, name, instruction, windowId } = opts;
|
|
1067
|
-
const count = (agentCounters.get(sessionId) ?? 0) + 1;
|
|
1068
|
-
agentCounters.set(sessionId, count);
|
|
1069
|
-
const agentId = `agent-${String(count).padStart(3, "0")}`;
|
|
1070
|
-
const bundledPluginPath = resolve2(import.meta.dirname, "../templates/agent-plugin");
|
|
1071
|
-
const agentConfig = resolveAgentConfig(agentType, bundledPluginPath, cwd);
|
|
1072
|
-
const provider = detectProvider(agentConfig?.frontmatter.model);
|
|
1073
|
-
const color = (agentConfig?.frontmatter.color ? normalizeTmuxColor(agentConfig.frontmatter.color) : null) ?? getNextColor(sessionId);
|
|
1074
|
-
let paneCwd = cwd;
|
|
1075
|
-
let worktreePath;
|
|
1076
|
-
let branchName;
|
|
1077
|
-
let worktreeContext;
|
|
1078
|
-
if (opts.worktree) {
|
|
1079
|
-
const wt = createWorktreeShell(cwd, sessionId, agentId);
|
|
1080
|
-
worktreePath = wt.worktreePath;
|
|
1081
|
-
branchName = wt.branchName;
|
|
1082
|
-
paneCwd = worktreePath;
|
|
1083
|
-
const session = getSession(cwd, sessionId);
|
|
1084
|
-
const portOffset = countWorktreeAgents(session.agents) + 1;
|
|
1085
|
-
worktreeContext = { offset: portOffset, total: portOffset, branchName };
|
|
1086
|
-
}
|
|
1082
|
+
function setupAgentPane(opts) {
|
|
1083
|
+
const { sessionId, cwd, agentId, agentType, name, instruction, windowId, color, provider, agentConfig, worktreeContext, paneCwd } = opts;
|
|
1087
1084
|
const paneId = createPane(windowId, paneCwd);
|
|
1088
1085
|
registerPane(paneId, sessionId, "agent", agentId);
|
|
1089
1086
|
const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
|
|
@@ -1092,50 +1089,88 @@ async function spawnAgent(opts) {
|
|
|
1092
1089
|
setPaneStyle(paneId, color);
|
|
1093
1090
|
const suffix = renderAgentSuffix(sessionId, instruction, worktreeContext);
|
|
1094
1091
|
const suffixFilePath = `${promptsDir(cwd, sessionId)}/${agentId}-system.md`;
|
|
1095
|
-
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
const npmBinDir = resolve2(import.meta.dirname, "../../.bin");
|
|
1100
|
-
const envExports = [
|
|
1092
|
+
writeFileSync4(suffixFilePath, suffix, "utf-8");
|
|
1093
|
+
const bannerCmd = resolveBannerCmd();
|
|
1094
|
+
const npmBinDir = resolveNpmBinDir();
|
|
1095
|
+
const envExports = buildEnvExports([
|
|
1101
1096
|
`export SISYPHUS_SESSION_ID='${sessionId}'`,
|
|
1102
1097
|
`export SISYPHUS_AGENT_ID='${agentId}'`,
|
|
1103
1098
|
`export SISYPHUS_CWD='${cwd}'`,
|
|
1104
1099
|
...worktreeContext ? [`export SISYPHUS_PORT_OFFSET='${worktreeContext.offset}'`] : [],
|
|
1105
1100
|
`export PATH="${npmBinDir}:$PATH"`
|
|
1106
|
-
]
|
|
1107
|
-
const notifyCmd =
|
|
1101
|
+
]);
|
|
1102
|
+
const notifyCmd = buildNotifyCmd(paneId);
|
|
1108
1103
|
let mainCmd;
|
|
1109
1104
|
if (provider === "openai") {
|
|
1110
1105
|
const codexPromptPath = `${promptsDir(cwd, sessionId)}/${agentId}-codex-prompt.md`;
|
|
1111
1106
|
const parts = [];
|
|
1112
|
-
if (agentConfig?.body)
|
|
1113
|
-
parts.push(agentConfig.body);
|
|
1114
|
-
}
|
|
1107
|
+
if (agentConfig?.body) parts.push(agentConfig.body);
|
|
1115
1108
|
parts.push(suffix);
|
|
1116
1109
|
parts.push(`## Task
|
|
1117
1110
|
|
|
1118
1111
|
${instruction}`);
|
|
1119
|
-
|
|
1112
|
+
writeFileSync4(codexPromptPath, parts.join("\n\n"), "utf-8");
|
|
1120
1113
|
const model = agentConfig?.frontmatter.model ?? "codex-mini";
|
|
1121
|
-
mainCmd = `codex -m ${
|
|
1114
|
+
mainCmd = `codex -m ${shellQuote(model)} --dangerously-bypass-approvals-and-sandbox "$(cat '${codexPromptPath}')"`;
|
|
1122
1115
|
} else {
|
|
1123
|
-
const agentFlag = agentType && agentType !== "worker" ? ` --agent ${
|
|
1116
|
+
const agentFlag = agentType && agentType !== "worker" ? ` --agent ${shellQuote(agentType)}` : "";
|
|
1124
1117
|
const config = loadConfig(cwd);
|
|
1125
1118
|
const effort = agentConfig?.frontmatter.effort ?? config.agentEffort ?? "medium";
|
|
1126
1119
|
const pluginPath = createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig);
|
|
1127
|
-
mainCmd = `claude --dangerously-skip-permissions --effort ${effort} --plugin-dir "${pluginPath}"${agentFlag} --name ${
|
|
1120
|
+
mainCmd = `claude --dangerously-skip-permissions --effort ${effort} --plugin-dir "${pluginPath}"${agentFlag} --name ${shellQuote(`sisyphus:${name}`)} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote(instruction)}`;
|
|
1128
1121
|
}
|
|
1129
|
-
const
|
|
1122
|
+
const scriptPath = writeRunScript(promptsDir(cwd, sessionId), `${agentId}-run`, [
|
|
1130
1123
|
"#!/usr/bin/env bash",
|
|
1131
|
-
...bannerCmd ? [bannerCmd
|
|
1124
|
+
...bannerCmd ? [bannerCmd] : [],
|
|
1132
1125
|
envExports,
|
|
1133
1126
|
mainCmd,
|
|
1134
1127
|
notifyCmd
|
|
1135
|
-
];
|
|
1136
|
-
const scriptPath = `${promptsDir(cwd, sessionId)}/${agentId}-run.sh`;
|
|
1137
|
-
writeFileSync3(scriptPath, scriptLines.join("\n"), { mode: 493 });
|
|
1128
|
+
]);
|
|
1138
1129
|
const fullCmd = `bash '${scriptPath}'`;
|
|
1130
|
+
return { paneId, fullCmd };
|
|
1131
|
+
}
|
|
1132
|
+
async function spawnAgent(opts) {
|
|
1133
|
+
const { sessionId, cwd, agentType, name, instruction, windowId } = opts;
|
|
1134
|
+
const count = (agentCounters.get(sessionId) ?? 0) + 1;
|
|
1135
|
+
agentCounters.set(sessionId, count);
|
|
1136
|
+
const agentId = `agent-${String(count).padStart(3, "0")}`;
|
|
1137
|
+
const bundledPluginPath = resolve3(import.meta.dirname, "../templates/agent-plugin");
|
|
1138
|
+
const agentConfig = resolveAgentConfig(agentType, bundledPluginPath, cwd);
|
|
1139
|
+
const provider = detectProvider(agentConfig?.frontmatter.model);
|
|
1140
|
+
const color = (agentConfig?.frontmatter.color ? normalizeTmuxColor(agentConfig.frontmatter.color) : null) ?? getNextColor(sessionId);
|
|
1141
|
+
const cliToCheck = provider === "openai" ? "codex" : "claude";
|
|
1142
|
+
try {
|
|
1143
|
+
execSync2(`which ${cliToCheck}`, { stdio: "pipe", env: execEnv() });
|
|
1144
|
+
} catch {
|
|
1145
|
+
throw new Error(`${cliToCheck} CLI not found on PATH. Run \`sisyphus doctor\` to diagnose.`);
|
|
1146
|
+
}
|
|
1147
|
+
let paneCwd = cwd;
|
|
1148
|
+
let worktreePath;
|
|
1149
|
+
let branchName;
|
|
1150
|
+
let worktreeContext;
|
|
1151
|
+
if (opts.worktree) {
|
|
1152
|
+
const wt = createWorktreeShell(cwd, sessionId, agentId);
|
|
1153
|
+
worktreePath = wt.worktreePath;
|
|
1154
|
+
branchName = wt.branchName;
|
|
1155
|
+
paneCwd = worktreePath;
|
|
1156
|
+
const session = getSession(cwd, sessionId);
|
|
1157
|
+
const portOffset = countWorktreeAgents(session.agents) + 1;
|
|
1158
|
+
worktreeContext = { offset: portOffset, total: portOffset, branchName };
|
|
1159
|
+
}
|
|
1160
|
+
const { paneId, fullCmd } = setupAgentPane({
|
|
1161
|
+
sessionId,
|
|
1162
|
+
cwd,
|
|
1163
|
+
agentId,
|
|
1164
|
+
agentType,
|
|
1165
|
+
name,
|
|
1166
|
+
instruction,
|
|
1167
|
+
windowId,
|
|
1168
|
+
color,
|
|
1169
|
+
provider,
|
|
1170
|
+
agentConfig,
|
|
1171
|
+
worktreeContext,
|
|
1172
|
+
paneCwd
|
|
1173
|
+
});
|
|
1139
1174
|
const agent = {
|
|
1140
1175
|
id: agentId,
|
|
1141
1176
|
name,
|
|
@@ -1187,7 +1222,7 @@ async function restartAgent(sessionId, cwd, agentId, windowId) {
|
|
|
1187
1222
|
});
|
|
1188
1223
|
}
|
|
1189
1224
|
const { instruction, agentType, name, color } = agent;
|
|
1190
|
-
const bundledPluginPath =
|
|
1225
|
+
const bundledPluginPath = resolve3(import.meta.dirname, "../templates/agent-plugin");
|
|
1191
1226
|
const agentConfig = resolveAgentConfig(agentType, bundledPluginPath, cwd);
|
|
1192
1227
|
const provider = detectProvider(agentConfig?.frontmatter.model);
|
|
1193
1228
|
let paneCwd = cwd;
|
|
@@ -1208,56 +1243,20 @@ async function restartAgent(sessionId, cwd, agentId, windowId) {
|
|
|
1208
1243
|
}
|
|
1209
1244
|
unregisterAgentPane(sessionId, agentId);
|
|
1210
1245
|
}
|
|
1211
|
-
const paneId =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
`export SISYPHUS_SESSION_ID='${sessionId}'`,
|
|
1226
|
-
`export SISYPHUS_AGENT_ID='${agentId}'`,
|
|
1227
|
-
`export SISYPHUS_CWD='${cwd}'`,
|
|
1228
|
-
...worktreeContext ? [`export SISYPHUS_PORT_OFFSET='${worktreeContext.offset}'`] : [],
|
|
1229
|
-
`export PATH="${npmBinDir}:$PATH"`
|
|
1230
|
-
].join(" && ");
|
|
1231
|
-
const notifyCmd = `node "${cliBin}" notify pane-exited --pane-id ${paneId}`;
|
|
1232
|
-
let mainCmd;
|
|
1233
|
-
if (provider === "openai") {
|
|
1234
|
-
const codexPromptPath = `${promptsDir(cwd, sessionId)}/${agentId}-codex-prompt.md`;
|
|
1235
|
-
const parts = [];
|
|
1236
|
-
if (agentConfig?.body) parts.push(agentConfig.body);
|
|
1237
|
-
parts.push(suffix);
|
|
1238
|
-
parts.push(`## Task
|
|
1239
|
-
|
|
1240
|
-
${instruction}`);
|
|
1241
|
-
writeFileSync3(codexPromptPath, parts.join("\n\n"), "utf-8");
|
|
1242
|
-
const model = agentConfig?.frontmatter.model ?? "codex-mini";
|
|
1243
|
-
mainCmd = `codex -m ${shellQuote3(model)} --dangerously-bypass-approvals-and-sandbox "$(cat '${codexPromptPath}')"`;
|
|
1244
|
-
} else {
|
|
1245
|
-
const agentFlag = agentType && agentType !== "worker" ? ` --agent ${shellQuote3(agentType)}` : "";
|
|
1246
|
-
const config = loadConfig(cwd);
|
|
1247
|
-
const effort = agentConfig?.frontmatter.effort ?? config.agentEffort ?? "medium";
|
|
1248
|
-
const pluginPath = createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig);
|
|
1249
|
-
mainCmd = `claude --dangerously-skip-permissions --effort ${effort} --plugin-dir "${pluginPath}"${agentFlag} --name ${shellQuote3(`sisyphus:${name}`)} --append-system-prompt "$(cat '${suffixFilePath}')" ${shellQuote3(instruction)}`;
|
|
1250
|
-
}
|
|
1251
|
-
const scriptLines = [
|
|
1252
|
-
"#!/usr/bin/env bash",
|
|
1253
|
-
...bannerCmd ? [bannerCmd.replace(/ &&$/, "")] : [],
|
|
1254
|
-
envExports,
|
|
1255
|
-
mainCmd,
|
|
1256
|
-
notifyCmd
|
|
1257
|
-
];
|
|
1258
|
-
const scriptPath = `${promptsDir(cwd, sessionId)}/${agentId}-run.sh`;
|
|
1259
|
-
writeFileSync3(scriptPath, scriptLines.join("\n"), { mode: 493 });
|
|
1260
|
-
const fullCmd = `bash '${scriptPath}'`;
|
|
1246
|
+
const { paneId, fullCmd } = setupAgentPane({
|
|
1247
|
+
sessionId,
|
|
1248
|
+
cwd,
|
|
1249
|
+
agentId,
|
|
1250
|
+
agentType,
|
|
1251
|
+
name,
|
|
1252
|
+
instruction,
|
|
1253
|
+
windowId,
|
|
1254
|
+
color,
|
|
1255
|
+
provider,
|
|
1256
|
+
agentConfig,
|
|
1257
|
+
worktreeContext,
|
|
1258
|
+
paneCwd
|
|
1259
|
+
});
|
|
1261
1260
|
await updateAgent(cwd, sessionId, agentId, {
|
|
1262
1261
|
status: "running",
|
|
1263
1262
|
paneId,
|
|
@@ -1282,7 +1281,7 @@ async function handleAgentReport(cwd, sessionId, agentId, content) {
|
|
|
1282
1281
|
mkdirSync3(dir, { recursive: true });
|
|
1283
1282
|
const num = nextReportNumber(cwd, sessionId, agentId);
|
|
1284
1283
|
const filePath = reportFilePath(cwd, sessionId, agentId, num);
|
|
1285
|
-
|
|
1284
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
1286
1285
|
const entry = {
|
|
1287
1286
|
type: "update",
|
|
1288
1287
|
filePath,
|
|
@@ -1301,7 +1300,7 @@ async function handleAgentSubmit(cwd, sessionId, agentId, report) {
|
|
|
1301
1300
|
const dir = reportsDir(cwd, sessionId);
|
|
1302
1301
|
mkdirSync3(dir, { recursive: true });
|
|
1303
1302
|
const filePath = reportFilePath(cwd, sessionId, agentId, "final");
|
|
1304
|
-
|
|
1303
|
+
writeFileSync4(filePath, report, "utf-8");
|
|
1305
1304
|
const entry = {
|
|
1306
1305
|
type: "final",
|
|
1307
1306
|
filePath,
|
|
@@ -1344,9 +1343,6 @@ function allAgentsDone(session) {
|
|
|
1344
1343
|
const running = session.agents.filter((a) => a.status === "running");
|
|
1345
1344
|
return running.length === 0 && session.agents.length > 0;
|
|
1346
1345
|
}
|
|
1347
|
-
function shellQuote3(s) {
|
|
1348
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
1349
|
-
}
|
|
1350
1346
|
|
|
1351
1347
|
// src/daemon/pane-monitor.ts
|
|
1352
1348
|
var monitorInterval = null;
|
|
@@ -1491,7 +1487,7 @@ var PRUNE_KEEP_DAYS = 7;
|
|
|
1491
1487
|
function pruneOldSessions(cwd) {
|
|
1492
1488
|
try {
|
|
1493
1489
|
const dir = sessionsDir(cwd);
|
|
1494
|
-
if (!
|
|
1490
|
+
if (!existsSync7(dir)) return;
|
|
1495
1491
|
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
1496
1492
|
const candidates = [];
|
|
1497
1493
|
for (const entry of entries) {
|
|
@@ -1521,6 +1517,17 @@ function pruneOldSessions(cwd) {
|
|
|
1521
1517
|
console.error("[sisyphus] Session pruning failed:", err);
|
|
1522
1518
|
}
|
|
1523
1519
|
}
|
|
1520
|
+
async function reopenWindow(sessionId, cwd) {
|
|
1521
|
+
const session = getSession(cwd, sessionId);
|
|
1522
|
+
const tmuxName = session.tmuxSessionName ?? `sisyphus-${session.name ?? sessionId.slice(0, 8)}`;
|
|
1523
|
+
if (sessionExists(tmuxName) && session.tmuxWindowId) {
|
|
1524
|
+
return { tmuxSessionName: tmuxName, tmuxWindowId: session.tmuxWindowId };
|
|
1525
|
+
}
|
|
1526
|
+
const created = createSession2(tmuxName, "main", cwd);
|
|
1527
|
+
setSessionOption(tmuxName, "@sisyphus_cwd", cwd.replace(/\/+$/, ""));
|
|
1528
|
+
await updateSessionTmux(cwd, sessionId, tmuxName, created.windowId);
|
|
1529
|
+
return { tmuxSessionName: tmuxName, tmuxWindowId: created.windowId };
|
|
1530
|
+
}
|
|
1524
1531
|
async function resumeSession(sessionId, cwd, message) {
|
|
1525
1532
|
const session = getSession(cwd, sessionId);
|
|
1526
1533
|
const tmuxName = session.tmuxSessionName ?? `sisyphus-${sessionId.slice(0, 8)}`;
|
|
@@ -1573,7 +1580,7 @@ function getSessionStatus(cwd, sessionId) {
|
|
|
1573
1580
|
}
|
|
1574
1581
|
function listSessions(cwd) {
|
|
1575
1582
|
const dir = sessionsDir(cwd);
|
|
1576
|
-
if (!
|
|
1583
|
+
if (!existsSync7(dir)) return [];
|
|
1577
1584
|
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
1578
1585
|
const sessions = [];
|
|
1579
1586
|
for (const entry of entries) {
|
|
@@ -1853,10 +1860,7 @@ async function handlePaneExited(paneId, cwd, sessionId, role, agentId) {
|
|
|
1853
1860
|
|
|
1854
1861
|
// src/daemon/server.ts
|
|
1855
1862
|
var server = null;
|
|
1856
|
-
var
|
|
1857
|
-
var sessionMessageCounters = /* @__PURE__ */ new Map();
|
|
1858
|
-
var sessionTmuxMap = /* @__PURE__ */ new Map();
|
|
1859
|
-
var sessionWindowMap2 = /* @__PURE__ */ new Map();
|
|
1863
|
+
var sessionTrackingMap = /* @__PURE__ */ new Map();
|
|
1860
1864
|
function registryPath() {
|
|
1861
1865
|
return join5(globalDir(), "session-registry.json");
|
|
1862
1866
|
}
|
|
@@ -1864,14 +1868,14 @@ function persistSessionRegistry() {
|
|
|
1864
1868
|
const dir = globalDir();
|
|
1865
1869
|
mkdirSync4(dir, { recursive: true });
|
|
1866
1870
|
const registry = {};
|
|
1867
|
-
for (const [id,
|
|
1868
|
-
registry[id] = cwd;
|
|
1871
|
+
for (const [id, tracking] of sessionTrackingMap) {
|
|
1872
|
+
registry[id] = tracking.cwd;
|
|
1869
1873
|
}
|
|
1870
|
-
|
|
1874
|
+
writeFileSync5(registryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
1871
1875
|
}
|
|
1872
1876
|
function loadSessionRegistry() {
|
|
1873
1877
|
const p = registryPath();
|
|
1874
|
-
if (!
|
|
1878
|
+
if (!existsSync8(p)) return {};
|
|
1875
1879
|
try {
|
|
1876
1880
|
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
1877
1881
|
} catch {
|
|
@@ -1879,65 +1883,81 @@ function loadSessionRegistry() {
|
|
|
1879
1883
|
}
|
|
1880
1884
|
}
|
|
1881
1885
|
function registerSessionCwd(sessionId, cwd) {
|
|
1882
|
-
|
|
1886
|
+
const existing = sessionTrackingMap.get(sessionId);
|
|
1887
|
+
if (existing) {
|
|
1888
|
+
existing.cwd = cwd;
|
|
1889
|
+
} else {
|
|
1890
|
+
sessionTrackingMap.set(sessionId, { cwd, messageCounter: 0 });
|
|
1891
|
+
}
|
|
1883
1892
|
persistSessionRegistry();
|
|
1884
1893
|
}
|
|
1885
1894
|
function registerSessionTmux(sessionId, tmuxSession, windowId) {
|
|
1886
|
-
|
|
1887
|
-
|
|
1895
|
+
const existing = sessionTrackingMap.get(sessionId);
|
|
1896
|
+
if (existing) {
|
|
1897
|
+
existing.tmuxSession = tmuxSession;
|
|
1898
|
+
existing.windowId = windowId;
|
|
1899
|
+
} else {
|
|
1900
|
+
sessionTrackingMap.set(sessionId, { cwd: "", tmuxSession, windowId, messageCounter: 0 });
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function unknownSessionError(sessionId) {
|
|
1904
|
+
return { ok: false, error: `Unknown session: ${sessionId}. Run \`sisyphus list --all\` to see available sessions.` };
|
|
1888
1905
|
}
|
|
1889
1906
|
async function handleRequest(req) {
|
|
1890
1907
|
try {
|
|
1891
1908
|
switch (req.type) {
|
|
1892
1909
|
case "start": {
|
|
1893
1910
|
const session = await startSession(req.task, req.cwd, req.context, req.name);
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1911
|
+
sessionTrackingMap.set(session.id, {
|
|
1912
|
+
cwd: req.cwd,
|
|
1913
|
+
tmuxSession: session.tmuxSessionName,
|
|
1914
|
+
windowId: session.tmuxWindowId,
|
|
1915
|
+
messageCounter: 0
|
|
1916
|
+
});
|
|
1917
|
+
persistSessionRegistry();
|
|
1897
1918
|
return { ok: true, data: { sessionId: session.id, tmuxSessionName: session.tmuxSessionName } };
|
|
1898
1919
|
}
|
|
1899
1920
|
case "spawn": {
|
|
1900
|
-
const
|
|
1901
|
-
if (!
|
|
1902
|
-
const result = await handleSpawn(req.sessionId, cwd, req.agentType, req.name, req.instruction, req.worktree);
|
|
1921
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1922
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1923
|
+
const result = await handleSpawn(req.sessionId, tracking.cwd, req.agentType, req.name, req.instruction, req.worktree);
|
|
1903
1924
|
return { ok: true, data: { agentId: result.agentId } };
|
|
1904
1925
|
}
|
|
1905
1926
|
case "submit": {
|
|
1906
|
-
const
|
|
1907
|
-
if (!
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
await handleSubmit(cwd, req.sessionId, req.agentId, req.report, windowId);
|
|
1927
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1928
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1929
|
+
if (!tracking.windowId) return { ok: false, error: `No tmux window found for session: ${req.sessionId}` };
|
|
1930
|
+
await handleSubmit(tracking.cwd, req.sessionId, req.agentId, req.report, tracking.windowId);
|
|
1911
1931
|
return { ok: true };
|
|
1912
1932
|
}
|
|
1913
1933
|
case "report": {
|
|
1914
|
-
const
|
|
1915
|
-
if (!
|
|
1916
|
-
await handleReport(cwd, req.sessionId, req.agentId, req.content);
|
|
1934
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1935
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1936
|
+
await handleReport(tracking.cwd, req.sessionId, req.agentId, req.content);
|
|
1917
1937
|
return { ok: true };
|
|
1918
1938
|
}
|
|
1919
1939
|
case "yield": {
|
|
1920
|
-
const
|
|
1921
|
-
if (!
|
|
1922
|
-
await handleYield(req.sessionId, cwd, req.nextPrompt, req.mode);
|
|
1940
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1941
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1942
|
+
await handleYield(req.sessionId, tracking.cwd, req.nextPrompt, req.mode);
|
|
1923
1943
|
return { ok: true };
|
|
1924
1944
|
}
|
|
1925
1945
|
case "complete": {
|
|
1926
|
-
const
|
|
1927
|
-
if (!
|
|
1928
|
-
await handleComplete(req.sessionId, cwd, req.report);
|
|
1946
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1947
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1948
|
+
await handleComplete(req.sessionId, tracking.cwd, req.report);
|
|
1929
1949
|
return { ok: true };
|
|
1930
1950
|
}
|
|
1931
1951
|
case "continue": {
|
|
1932
|
-
const
|
|
1933
|
-
if (!
|
|
1934
|
-
await handleContinue(req.sessionId, cwd);
|
|
1952
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
1953
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
1954
|
+
await handleContinue(req.sessionId, tracking.cwd);
|
|
1935
1955
|
return { ok: true };
|
|
1936
1956
|
}
|
|
1937
1957
|
case "status": {
|
|
1938
1958
|
if (req.sessionId) {
|
|
1939
|
-
const cwd =
|
|
1940
|
-
if (!cwd) return
|
|
1959
|
+
const cwd = sessionTrackingMap.get(req.sessionId)?.cwd ?? req.cwd;
|
|
1960
|
+
if (!cwd) return unknownSessionError(req.sessionId);
|
|
1941
1961
|
const session = getSessionStatus(cwd, req.sessionId);
|
|
1942
1962
|
return { ok: true, data: { session } };
|
|
1943
1963
|
}
|
|
@@ -1947,21 +1967,21 @@ async function handleRequest(req) {
|
|
|
1947
1967
|
const allSessions = [];
|
|
1948
1968
|
if (req.all) {
|
|
1949
1969
|
const seenCwds = /* @__PURE__ */ new Set();
|
|
1950
|
-
for (const
|
|
1951
|
-
if (seenCwds.has(cwd)) continue;
|
|
1952
|
-
seenCwds.add(cwd);
|
|
1953
|
-
const sessions = listSessions(cwd);
|
|
1954
|
-
allSessions.push(...sessions.map((s) => ({ ...s, cwd })));
|
|
1970
|
+
for (const tracking of sessionTrackingMap.values()) {
|
|
1971
|
+
if (seenCwds.has(tracking.cwd)) continue;
|
|
1972
|
+
seenCwds.add(tracking.cwd);
|
|
1973
|
+
const sessions = listSessions(tracking.cwd);
|
|
1974
|
+
allSessions.push(...sessions.map((s) => ({ ...s, cwd: tracking.cwd })));
|
|
1955
1975
|
}
|
|
1956
1976
|
} else {
|
|
1957
1977
|
const sessions = listSessions(req.cwd);
|
|
1958
1978
|
allSessions.push(...sessions.map((s) => ({ ...s, cwd: req.cwd })));
|
|
1959
1979
|
let totalCount = allSessions.length;
|
|
1960
1980
|
const seenCwds = /* @__PURE__ */ new Set([req.cwd]);
|
|
1961
|
-
for (const
|
|
1962
|
-
if (seenCwds.has(cwd)) continue;
|
|
1963
|
-
seenCwds.add(cwd);
|
|
1964
|
-
totalCount += listSessions(cwd).length;
|
|
1981
|
+
for (const tracking of sessionTrackingMap.values()) {
|
|
1982
|
+
if (seenCwds.has(tracking.cwd)) continue;
|
|
1983
|
+
seenCwds.add(tracking.cwd);
|
|
1984
|
+
totalCount += listSessions(tracking.cwd).length;
|
|
1965
1985
|
}
|
|
1966
1986
|
if (totalCount > allSessions.length) {
|
|
1967
1987
|
return { ok: true, data: { sessions: allSessions, totalCount, filtered: true } };
|
|
@@ -1970,114 +1990,126 @@ async function handleRequest(req) {
|
|
|
1970
1990
|
return { ok: true, data: { sessions: allSessions } };
|
|
1971
1991
|
}
|
|
1972
1992
|
case "resume": {
|
|
1973
|
-
let
|
|
1974
|
-
if (!
|
|
1993
|
+
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
1994
|
+
if (!tracking) {
|
|
1975
1995
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
1976
|
-
if (
|
|
1977
|
-
|
|
1978
|
-
|
|
1996
|
+
if (existsSync8(stateFile)) {
|
|
1997
|
+
tracking = { cwd: req.cwd, messageCounter: 0 };
|
|
1998
|
+
sessionTrackingMap.set(req.sessionId, tracking);
|
|
1999
|
+
persistSessionRegistry();
|
|
1979
2000
|
} else {
|
|
1980
|
-
return { ok: false, error: `Unknown session: ${req.sessionId}. No state.json found at ${stateFile}
|
|
2001
|
+
return { ok: false, error: `Unknown session: ${req.sessionId}. No state.json found at ${stateFile}. Run \`sisyphus list --all\` to see available sessions.` };
|
|
1981
2002
|
}
|
|
1982
2003
|
}
|
|
1983
|
-
const session = await resumeSession(req.sessionId, cwd, req.message);
|
|
1984
|
-
if (session.tmuxSessionName)
|
|
1985
|
-
if (session.tmuxWindowId)
|
|
2004
|
+
const session = await resumeSession(req.sessionId, tracking.cwd, req.message);
|
|
2005
|
+
if (session.tmuxSessionName) tracking.tmuxSession = session.tmuxSessionName;
|
|
2006
|
+
if (session.tmuxWindowId) tracking.windowId = session.tmuxWindowId;
|
|
1986
2007
|
return { ok: true, data: { sessionId: session.id, status: session.status, tmuxSessionName: session.tmuxSessionName } };
|
|
1987
2008
|
}
|
|
1988
2009
|
case "register_claude_session": {
|
|
1989
|
-
const
|
|
1990
|
-
if (!
|
|
1991
|
-
await handleRegisterClaudeSession(cwd, req.sessionId, req.agentId, req.claudeSessionId);
|
|
2010
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2011
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2012
|
+
await handleRegisterClaudeSession(tracking.cwd, req.sessionId, req.agentId, req.claudeSessionId);
|
|
1992
2013
|
return { ok: true };
|
|
1993
2014
|
}
|
|
1994
2015
|
case "kill": {
|
|
1995
|
-
const
|
|
1996
|
-
if (!
|
|
1997
|
-
const killedAgents = await handleKill(req.sessionId, cwd);
|
|
1998
|
-
|
|
1999
|
-
sessionTmuxMap.delete(req.sessionId);
|
|
2000
|
-
sessionWindowMap2.delete(req.sessionId);
|
|
2016
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2017
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2018
|
+
const killedAgents = await handleKill(req.sessionId, tracking.cwd);
|
|
2019
|
+
sessionTrackingMap.delete(req.sessionId);
|
|
2001
2020
|
persistSessionRegistry();
|
|
2002
2021
|
return { ok: true, data: { killedAgents, sessionId: req.sessionId } };
|
|
2003
2022
|
}
|
|
2004
2023
|
case "kill-agent": {
|
|
2005
|
-
const
|
|
2006
|
-
if (!
|
|
2007
|
-
await handleKillAgent(req.sessionId, cwd, req.agentId);
|
|
2024
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2025
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2026
|
+
await handleKillAgent(req.sessionId, tracking.cwd, req.agentId);
|
|
2008
2027
|
return { ok: true, data: { agentId: req.agentId } };
|
|
2009
2028
|
}
|
|
2010
2029
|
case "restart-agent": {
|
|
2011
|
-
const
|
|
2012
|
-
if (!
|
|
2013
|
-
await handleRestartAgent(req.sessionId, cwd, req.agentId);
|
|
2030
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2031
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2032
|
+
await handleRestartAgent(req.sessionId, tracking.cwd, req.agentId);
|
|
2014
2033
|
return { ok: true, data: { agentId: req.agentId } };
|
|
2015
2034
|
}
|
|
2016
2035
|
case "rollback": {
|
|
2017
|
-
let
|
|
2018
|
-
if (!
|
|
2036
|
+
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
2037
|
+
if (!tracking) {
|
|
2038
|
+
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
2039
|
+
if (existsSync8(stateFile)) {
|
|
2040
|
+
registerSessionCwd(req.sessionId, req.cwd);
|
|
2041
|
+
tracking = sessionTrackingMap.get(req.sessionId);
|
|
2042
|
+
} else {
|
|
2043
|
+
return unknownSessionError(req.sessionId);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const result = await handleRollback(req.sessionId, tracking.cwd, req.toCycle);
|
|
2047
|
+
return { ok: true, data: result };
|
|
2048
|
+
}
|
|
2049
|
+
case "reopen-window": {
|
|
2050
|
+
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
2051
|
+
if (!tracking) {
|
|
2019
2052
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
2020
|
-
if (
|
|
2021
|
-
|
|
2022
|
-
|
|
2053
|
+
if (existsSync8(stateFile)) {
|
|
2054
|
+
tracking = { cwd: req.cwd, messageCounter: 0 };
|
|
2055
|
+
sessionTrackingMap.set(req.sessionId, tracking);
|
|
2056
|
+
persistSessionRegistry();
|
|
2023
2057
|
} else {
|
|
2024
|
-
return
|
|
2058
|
+
return unknownSessionError(req.sessionId);
|
|
2025
2059
|
}
|
|
2026
2060
|
}
|
|
2027
|
-
const result = await
|
|
2061
|
+
const result = await reopenWindow(req.sessionId, tracking.cwd);
|
|
2062
|
+
tracking.tmuxSession = result.tmuxSessionName;
|
|
2063
|
+
tracking.windowId = result.tmuxWindowId;
|
|
2028
2064
|
return { ok: true, data: result };
|
|
2029
2065
|
}
|
|
2030
2066
|
case "delete": {
|
|
2031
|
-
const
|
|
2032
|
-
if (
|
|
2067
|
+
const activeTracking = sessionTrackingMap.get(req.sessionId);
|
|
2068
|
+
if (activeTracking) {
|
|
2033
2069
|
try {
|
|
2034
|
-
await handleKill(req.sessionId,
|
|
2070
|
+
await handleKill(req.sessionId, activeTracking.cwd);
|
|
2035
2071
|
} catch {
|
|
2036
2072
|
}
|
|
2037
|
-
|
|
2038
|
-
sessionTmuxMap.delete(req.sessionId);
|
|
2039
|
-
sessionWindowMap2.delete(req.sessionId);
|
|
2040
|
-
sessionMessageCounters.delete(req.sessionId);
|
|
2073
|
+
sessionTrackingMap.delete(req.sessionId);
|
|
2041
2074
|
persistSessionRegistry();
|
|
2042
2075
|
}
|
|
2043
|
-
const { sessionDir: sessionDir2 } = await import("./paths-
|
|
2076
|
+
const { sessionDir: sessionDir2 } = await import("./paths-NUUALUVP.js");
|
|
2044
2077
|
rmSync4(sessionDir2(req.cwd, req.sessionId), { recursive: true, force: true });
|
|
2045
2078
|
return { ok: true };
|
|
2046
2079
|
}
|
|
2047
2080
|
case "pane-exited": {
|
|
2048
2081
|
const entry = lookupPane(req.paneId);
|
|
2049
2082
|
if (!entry) return { ok: true };
|
|
2050
|
-
const
|
|
2051
|
-
if (!
|
|
2083
|
+
const tracking = sessionTrackingMap.get(entry.sessionId);
|
|
2084
|
+
if (!tracking) {
|
|
2052
2085
|
unregisterPane(req.paneId);
|
|
2053
2086
|
return { ok: true };
|
|
2054
2087
|
}
|
|
2055
2088
|
unregisterPane(req.paneId);
|
|
2056
|
-
await handlePaneExited(req.paneId, cwd, entry.sessionId, entry.role, entry.agentId);
|
|
2089
|
+
await handlePaneExited(req.paneId, tracking.cwd, entry.sessionId, entry.role, entry.agentId);
|
|
2057
2090
|
return { ok: true };
|
|
2058
2091
|
}
|
|
2059
2092
|
case "update-task": {
|
|
2060
|
-
const
|
|
2061
|
-
if (!
|
|
2062
|
-
await updateTask(cwd, req.sessionId, req.task);
|
|
2093
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2094
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2095
|
+
await updateTask(tracking.cwd, req.sessionId, req.task);
|
|
2063
2096
|
return { ok: true };
|
|
2064
2097
|
}
|
|
2065
2098
|
case "message": {
|
|
2066
|
-
const
|
|
2067
|
-
if (!
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
const id = `msg-${String(counter).padStart(3, "0")}`;
|
|
2099
|
+
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
2100
|
+
if (!tracking) return unknownSessionError(req.sessionId);
|
|
2101
|
+
tracking.messageCounter += 1;
|
|
2102
|
+
const id = `msg-${String(tracking.messageCounter).padStart(3, "0")}`;
|
|
2071
2103
|
const source = req.source ?? { type: "user" };
|
|
2072
2104
|
const summary = req.content.length > 200 ? req.content.slice(0, 200) + "..." : req.content;
|
|
2073
2105
|
let filePath;
|
|
2074
2106
|
if (req.content.length > 200) {
|
|
2075
|
-
const dir = messagesDir(cwd, req.sessionId);
|
|
2107
|
+
const dir = messagesDir(tracking.cwd, req.sessionId);
|
|
2076
2108
|
mkdirSync4(dir, { recursive: true });
|
|
2077
2109
|
filePath = join5(dir, `${id}.md`);
|
|
2078
|
-
|
|
2110
|
+
writeFileSync5(filePath, req.content, "utf-8");
|
|
2079
2111
|
}
|
|
2080
|
-
await appendMessage(cwd, req.sessionId, {
|
|
2112
|
+
await appendMessage(tracking.cwd, req.sessionId, {
|
|
2081
2113
|
id,
|
|
2082
2114
|
source,
|
|
2083
2115
|
content: req.content,
|
|
@@ -2096,9 +2128,9 @@ async function handleRequest(req) {
|
|
|
2096
2128
|
}
|
|
2097
2129
|
}
|
|
2098
2130
|
function startServer() {
|
|
2099
|
-
return new Promise((
|
|
2131
|
+
return new Promise((resolve5, reject) => {
|
|
2100
2132
|
const sock = socketPath();
|
|
2101
|
-
if (
|
|
2133
|
+
if (existsSync8(sock)) {
|
|
2102
2134
|
unlinkSync(sock);
|
|
2103
2135
|
}
|
|
2104
2136
|
server = createServer((conn) => {
|
|
@@ -2128,31 +2160,31 @@ function startServer() {
|
|
|
2128
2160
|
server.on("error", reject);
|
|
2129
2161
|
server.listen(sock, () => {
|
|
2130
2162
|
console.log(`[sisyphus] Daemon listening on ${sock}`);
|
|
2131
|
-
|
|
2163
|
+
resolve5(server);
|
|
2132
2164
|
});
|
|
2133
2165
|
});
|
|
2134
2166
|
}
|
|
2135
2167
|
function stopServer() {
|
|
2136
|
-
return new Promise((
|
|
2168
|
+
return new Promise((resolve5) => {
|
|
2137
2169
|
if (!server) {
|
|
2138
|
-
|
|
2170
|
+
resolve5();
|
|
2139
2171
|
return;
|
|
2140
2172
|
}
|
|
2141
2173
|
server.close(() => {
|
|
2142
2174
|
const sock = socketPath();
|
|
2143
|
-
if (
|
|
2175
|
+
if (existsSync8(sock)) {
|
|
2144
2176
|
unlinkSync(sock);
|
|
2145
2177
|
}
|
|
2146
2178
|
server = null;
|
|
2147
|
-
|
|
2179
|
+
resolve5();
|
|
2148
2180
|
});
|
|
2149
2181
|
});
|
|
2150
2182
|
}
|
|
2151
2183
|
|
|
2152
2184
|
// src/daemon/updater.ts
|
|
2153
2185
|
import { execSync as execSync3 } from "child_process";
|
|
2154
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
2155
|
-
import { resolve as
|
|
2186
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, lstatSync } from "fs";
|
|
2187
|
+
import { resolve as resolve4 } from "path";
|
|
2156
2188
|
import { get } from "https";
|
|
2157
2189
|
function isNewer(latest, current) {
|
|
2158
2190
|
const a = latest.split(".").map(Number);
|
|
@@ -2168,7 +2200,7 @@ function isNewer(latest, current) {
|
|
|
2168
2200
|
function readPackageVersion() {
|
|
2169
2201
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
2170
2202
|
try {
|
|
2171
|
-
const raw = readFileSync7(
|
|
2203
|
+
const raw = readFileSync7(resolve4(import.meta.dirname, rel), "utf-8");
|
|
2172
2204
|
const pkg = JSON.parse(raw);
|
|
2173
2205
|
if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
|
|
2174
2206
|
} catch {
|
|
@@ -2178,9 +2210,9 @@ function readPackageVersion() {
|
|
|
2178
2210
|
}
|
|
2179
2211
|
var currentVersion = readPackageVersion();
|
|
2180
2212
|
function checkForUpdate() {
|
|
2181
|
-
return new Promise((
|
|
2213
|
+
return new Promise((resolve5) => {
|
|
2182
2214
|
const timeout = setTimeout(() => {
|
|
2183
|
-
|
|
2215
|
+
resolve5(null);
|
|
2184
2216
|
}, 5e3);
|
|
2185
2217
|
const req = get("https://registry.npmjs.org/sisyphi/latest", (res) => {
|
|
2186
2218
|
let data = "";
|
|
@@ -2192,24 +2224,24 @@ function checkForUpdate() {
|
|
|
2192
2224
|
try {
|
|
2193
2225
|
const { version: latest } = JSON.parse(data);
|
|
2194
2226
|
if (latest && isNewer(latest, currentVersion)) {
|
|
2195
|
-
|
|
2227
|
+
resolve5({ current: currentVersion, latest });
|
|
2196
2228
|
} else {
|
|
2197
|
-
|
|
2229
|
+
resolve5(null);
|
|
2198
2230
|
}
|
|
2199
2231
|
} catch {
|
|
2200
|
-
|
|
2232
|
+
resolve5(null);
|
|
2201
2233
|
}
|
|
2202
2234
|
});
|
|
2203
2235
|
});
|
|
2204
2236
|
req.on("error", () => {
|
|
2205
2237
|
clearTimeout(timeout);
|
|
2206
|
-
|
|
2238
|
+
resolve5(null);
|
|
2207
2239
|
});
|
|
2208
2240
|
});
|
|
2209
2241
|
}
|
|
2210
2242
|
function applyUpdate(expectedVersion) {
|
|
2211
2243
|
try {
|
|
2212
|
-
const nodeDir =
|
|
2244
|
+
const nodeDir = resolve4(process.execPath, "..");
|
|
2213
2245
|
const env = { ...process.env, PATH: `${nodeDir}:${process.env.PATH ?? ""}` };
|
|
2214
2246
|
execSync3("npm install -g sisyphi", { timeout: 15e3, stdio: "pipe", env });
|
|
2215
2247
|
const result = execSync3("npm ls -g sisyphi --json --depth=0", {
|
|
@@ -2231,7 +2263,7 @@ function applyUpdate(expectedVersion) {
|
|
|
2231
2263
|
}
|
|
2232
2264
|
function markUpdating(version) {
|
|
2233
2265
|
try {
|
|
2234
|
-
|
|
2266
|
+
writeFileSync6(daemonUpdatingPath(), version, "utf-8");
|
|
2235
2267
|
} catch {
|
|
2236
2268
|
}
|
|
2237
2269
|
}
|
|
@@ -2241,8 +2273,19 @@ function clearUpdating() {
|
|
|
2241
2273
|
} catch {
|
|
2242
2274
|
}
|
|
2243
2275
|
}
|
|
2276
|
+
function isLinkedInstall() {
|
|
2277
|
+
try {
|
|
2278
|
+
const nodeDir = resolve4(process.execPath, "..");
|
|
2279
|
+
const globalPrefix = execSync3("npm prefix -g", { timeout: 5e3, encoding: "utf-8", env: { ...process.env, PATH: `${nodeDir}:${process.env.PATH ?? ""}` } }).trim();
|
|
2280
|
+
const globalPkgDir = resolve4(globalPrefix, "lib", "node_modules", "sisyphi");
|
|
2281
|
+
return lstatSync(globalPkgDir).isSymbolicLink();
|
|
2282
|
+
} catch {
|
|
2283
|
+
return false;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2244
2286
|
async function checkAndApply() {
|
|
2245
2287
|
clearUpdating();
|
|
2288
|
+
if (isLinkedInstall()) return;
|
|
2246
2289
|
try {
|
|
2247
2290
|
const update = await checkForUpdate();
|
|
2248
2291
|
if (!update) return;
|
|
@@ -2261,6 +2304,11 @@ async function checkAndApply() {
|
|
|
2261
2304
|
}
|
|
2262
2305
|
|
|
2263
2306
|
// src/daemon/index.ts
|
|
2307
|
+
var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
2308
|
+
if (nodeVersion < 22) {
|
|
2309
|
+
console.error(`[sisyphus] Node.js v22+ required (current: v${process.versions.node})`);
|
|
2310
|
+
process.exit(1);
|
|
2311
|
+
}
|
|
2264
2312
|
var ts = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2265
2313
|
var origLog = console.log.bind(console);
|
|
2266
2314
|
var origError = console.error.bind(console);
|
|
@@ -2292,7 +2340,7 @@ function acquirePidLock() {
|
|
|
2292
2340
|
console.error(`[sisyphus] Daemon already running (pid ${pid}). Use 'sisyphusd restart' or 'sisyphusd stop' first.`);
|
|
2293
2341
|
process.exit(0);
|
|
2294
2342
|
}
|
|
2295
|
-
|
|
2343
|
+
writeFileSync7(daemonPidPath(), String(process.pid), "utf-8");
|
|
2296
2344
|
}
|
|
2297
2345
|
function isLaunchdManaged() {
|
|
2298
2346
|
try {
|
|
@@ -2351,7 +2399,7 @@ async function recoverSessions() {
|
|
|
2351
2399
|
let recovered = 0;
|
|
2352
2400
|
for (const [sessionId, cwd] of entries) {
|
|
2353
2401
|
const stateFile = statePath(cwd, sessionId);
|
|
2354
|
-
if (!
|
|
2402
|
+
if (!existsSync9(stateFile)) {
|
|
2355
2403
|
continue;
|
|
2356
2404
|
}
|
|
2357
2405
|
try {
|