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/README.md +37 -180
- package/dist/cli.js +40 -28
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +166 -92
- package/dist/daemon.js.map +1 -1
- package/dist/templates/agent-plugin/agents/review-plan.md +94 -68
- package/dist/templates/agent-plugin/agents/spec-draft.md +27 -51
- package/dist/templates/agent-plugin/hooks/hooks.json +3 -0
- package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +11 -0
- package/dist/templates/agent-suffix.md +7 -9
- package/dist/templates/orchestrator-plugin/hooks/hooks.json +0 -10
- package/dist/templates/orchestrator-plugin/scripts/block-task.sh +8 -1
- package/dist/templates/orchestrator.md +22 -18
- package/package.json +1 -1
- package/templates/agent-plugin/agents/review-plan.md +94 -68
- package/templates/agent-plugin/agents/spec-draft.md +27 -51
- package/templates/agent-plugin/hooks/hooks.json +3 -0
- package/templates/agent-plugin/hooks/intercept-send-message.sh +11 -0
- package/templates/agent-suffix.md +7 -9
- package/templates/orchestrator-plugin/hooks/hooks.json +0 -10
- package/templates/orchestrator-plugin/scripts/block-task.sh +8 -1
- package/templates/orchestrator.md +22 -18
- package/dist/templates/orchestrator-plugin/scripts/stop-suggest.sh +0 -12
- package/templates/orchestrator-plugin/scripts/stop-suggest.sh +0 -12
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
336
|
+
return readFileSync3(projectPath, "utf-8");
|
|
380
337
|
}
|
|
381
338
|
const bundledPath = resolve(import.meta.dirname, "../templates/orchestrator.md");
|
|
382
|
-
return
|
|
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, `
|
|
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
|
|
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
|
|
550
|
-
import { dirname as dirname2, join as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
553
|
+
const dest = join2(worktreePath, entry);
|
|
593
554
|
mkdirSync2(dirname2(dest), { recursive: true });
|
|
594
|
-
execSafe2(`cp -r ${shellQuote2(
|
|
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 =
|
|
560
|
+
const dest = join2(worktreePath, entry);
|
|
600
561
|
mkdirSync2(dirname2(dest), { recursive: true });
|
|
601
|
-
const src = shellQuote2(
|
|
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 =
|
|
571
|
+
const dest = join2(worktreePath, entry);
|
|
611
572
|
mkdirSync2(dirname2(dest), { recursive: true });
|
|
612
|
-
execSafe2(`ln -s ${shellQuote2(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
1681
|
+
if (!existsSync7(stateFile)) {
|
|
1608
1682
|
continue;
|
|
1609
1683
|
}
|
|
1610
1684
|
try {
|