sisyphi 1.1.31 → 1.1.33

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 CHANGED
@@ -160,6 +160,7 @@ Key bindings — navigate mode:
160
160
  | `e` | Edit context file |
161
161
  | `S` | Session info |
162
162
  | `F` | Filter / search |
163
+ | `D` | Toggle DANGEROUS mode for the selected session (auto-accepts the default for every ask) |
163
164
  | `c` | Open companion overlay |
164
165
  | `/` | Search sessions |
165
166
  | `q` | Quit |
@@ -96,6 +96,9 @@ runcmd:
96
96
  # 6. Sisyphus install (root → /usr/bin/sisyphusd symlink).
97
97
  - npm i -g sisyphi@${sisyphus_version}
98
98
 
99
+ # 6b. Claude Code CLI (sisyphus drives it for agent sessions).
100
+ - npm i -g @anthropic-ai/claude-code
101
+
99
102
  # 7. Daemon as systemd user service.
100
103
  - sudo -u sisyphus XDG_RUNTIME_DIR=/run/user/$(id -u sisyphus) systemctl --user daemon-reload
101
104
  - sudo -u sisyphus XDG_RUNTIME_DIR=/run/user/$(id -u sisyphus) systemctl --user enable --now sisyphusd
package/dist/cli.js CHANGED
@@ -36,6 +36,12 @@ function projectDir(cwd) {
36
36
  function projectConfigPath(cwd) {
37
37
  return join(projectDir(cwd), "config.json");
38
38
  }
39
+ function projectAgentPluginDir(cwd) {
40
+ return join(projectDir(cwd), "agent-plugin");
41
+ }
42
+ function userAgentPluginDir() {
43
+ return join(globalDir(), "agent-plugin");
44
+ }
39
45
  function sessionsDir(cwd) {
40
46
  return join(projectDir(cwd), "sessions");
41
47
  }
@@ -2320,6 +2326,11 @@ function getTmuxSessionInfo() {
2320
2326
  function shellQuote(s) {
2321
2327
  return `'${s.replace(/'/g, "'\\''")}'`;
2322
2328
  }
2329
+ function shellQuoteHomePath(path) {
2330
+ if (path === "~") return "~";
2331
+ if (path.startsWith("~/")) return `~/${shellQuote(path.slice(2))}`;
2332
+ return shellQuote(path);
2333
+ }
2323
2334
  function validateRepoName(repo) {
2324
2335
  return !repo.includes("/") && !repo.includes("\\") && !repo.includes("..");
2325
2336
  }
@@ -3277,13 +3288,79 @@ function emitHistoryEvent(sessionId, event, data) {
3277
3288
  }
3278
3289
  }
3279
3290
 
3291
+ // src/daemon/state.ts
3292
+ init_atomic();
3293
+ init_paths();
3294
+ import { copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
3295
+ import { join as join10 } from "path";
3296
+
3297
+ // src/shared/gitignore.ts
3298
+ import { existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
3299
+ import { join as join9 } from "path";
3300
+
3301
+ // src/shared/types.ts
3302
+ var ORCHESTRATOR_ASKED_BY = "orchestrator";
3303
+
3304
+ // src/daemon/state.ts
3305
+ function withSessionLock(sessionId, fn) {
3306
+ return withLock(sessionId, fn);
3307
+ }
3308
+ function getSession(cwd, sessionId) {
3309
+ const content = readFileSync11(statePath(cwd, sessionId), "utf-8");
3310
+ const session2 = JSON.parse(content);
3311
+ if (session2.activeMs == null) session2.activeMs = 0;
3312
+ if (session2.userBlockedMs == null) session2.userBlockedMs = 0;
3313
+ for (const agent2 of session2.agents) {
3314
+ if (!agent2.repo) agent2.repo = ".";
3315
+ if (agent2.activeMs == null) agent2.activeMs = 0;
3316
+ if (agent2.orphaned == null) agent2.orphaned = false;
3317
+ }
3318
+ if (session2.orphaned == null) session2.orphaned = false;
3319
+ for (const cycle of session2.orchestratorCycles) {
3320
+ if (cycle.activeMs == null) cycle.activeMs = 0;
3321
+ if (cycle.userBlockedMs == null) cycle.userBlockedMs = 0;
3322
+ }
3323
+ return session2;
3324
+ }
3325
+ function saveSession(session2) {
3326
+ atomicWrite(statePath(session2.cwd, session2.id), JSON.stringify(session2, null, 2));
3327
+ }
3328
+ function isSessionDangerous(cwd, sessionId) {
3329
+ try {
3330
+ return getSession(cwd, sessionId).dangerousMode === true;
3331
+ } catch {
3332
+ return false;
3333
+ }
3334
+ }
3335
+ async function incrementUserBlockedMs(cwd, sessionId, deltaMs, askedAt, askedBy) {
3336
+ if (deltaMs <= 0) return;
3337
+ return withSessionLock(sessionId, () => {
3338
+ const session2 = getSession(cwd, sessionId);
3339
+ session2.userBlockedMs = (session2.userBlockedMs ?? 0) + deltaMs;
3340
+ if (askedAt) {
3341
+ const askedAtMs = new Date(askedAt).getTime();
3342
+ const cycle = session2.orchestratorCycles.find((c2) => {
3343
+ const startMs = new Date(c2.timestamp).getTime();
3344
+ const endMs = c2.completedAt ? new Date(c2.completedAt).getTime() : Infinity;
3345
+ return startMs <= askedAtMs && askedAtMs < endMs;
3346
+ });
3347
+ if (cycle) cycle.userBlockedMs = (cycle.userBlockedMs ?? 0) + deltaMs;
3348
+ }
3349
+ if (askedBy && askedBy !== ORCHESTRATOR_ASKED_BY) {
3350
+ const agent2 = session2.agents.slice().reverse().find((a) => a.id === askedBy);
3351
+ if (agent2) agent2.userBlockedMs = (agent2.userBlockedMs ?? 0) + deltaMs;
3352
+ }
3353
+ saveSession(session2);
3354
+ });
3355
+ }
3356
+
3280
3357
  // src/daemon/ask-store.ts
3281
3358
  init_atomic();
3282
3359
 
3283
3360
  // src/daemon/notify.ts
3284
3361
  import { spawn, execFile } from "child_process";
3285
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
3286
- import { join as join9 } from "path";
3362
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
3363
+ import { join as join11 } from "path";
3287
3364
  import { homedir as homedir6 } from "os";
3288
3365
  var TMUX_SOCKET = `/tmp/tmux-${process.getuid?.() ?? 0}/default`;
3289
3366
  var SWITCH_SCRIPT = [
@@ -3326,24 +3403,24 @@ var SWITCH_SCRIPT = [
3326
3403
  ""
3327
3404
  ].join("\n");
3328
3405
  function ensureSwitchScript() {
3329
- const dir = join9(homedir6(), ".sisyphus");
3330
- const scriptPath2 = join9(dir, "notify-switch.sh");
3406
+ const dir = join11(homedir6(), ".sisyphus");
3407
+ const scriptPath2 = join11(dir, "notify-switch.sh");
3331
3408
  try {
3332
- mkdirSync4(dir, { recursive: true });
3333
- writeFileSync5(scriptPath2, SWITCH_SCRIPT, { mode: 493 });
3409
+ mkdirSync5(dir, { recursive: true });
3410
+ writeFileSync7(scriptPath2, SWITCH_SCRIPT, { mode: 493 });
3334
3411
  } catch {
3335
3412
  }
3336
3413
  }
3337
3414
  var notifyProcess = null;
3338
3415
  function getNotifyBinary() {
3339
- return join9(homedir6(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
3416
+ return join11(homedir6(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
3340
3417
  }
3341
3418
  function ensureNotifyProcess() {
3342
3419
  if (notifyProcess && !notifyProcess.killed && notifyProcess.stdin?.writable) {
3343
3420
  return notifyProcess;
3344
3421
  }
3345
3422
  const binary = getNotifyBinary();
3346
- if (!existsSync6(binary)) {
3423
+ if (!existsSync8(binary)) {
3347
3424
  return null;
3348
3425
  }
3349
3426
  notifyProcess = spawn(binary, [], {
@@ -3399,65 +3476,6 @@ function sendTerminalNotification(titleOrOpts, message, tmuxSession, level) {
3399
3476
  });
3400
3477
  }
3401
3478
 
3402
- // src/daemon/state.ts
3403
- init_atomic();
3404
- init_paths();
3405
- import { copyFileSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
3406
- import { join as join11 } from "path";
3407
-
3408
- // src/shared/gitignore.ts
3409
- import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
3410
- import { join as join10 } from "path";
3411
-
3412
- // src/shared/types.ts
3413
- var ORCHESTRATOR_ASKED_BY = "orchestrator";
3414
-
3415
- // src/daemon/state.ts
3416
- function withSessionLock(sessionId, fn) {
3417
- return withLock(sessionId, fn);
3418
- }
3419
- function getSession(cwd, sessionId) {
3420
- const content = readFileSync11(statePath(cwd, sessionId), "utf-8");
3421
- const session2 = JSON.parse(content);
3422
- if (session2.activeMs == null) session2.activeMs = 0;
3423
- if (session2.userBlockedMs == null) session2.userBlockedMs = 0;
3424
- for (const agent2 of session2.agents) {
3425
- if (!agent2.repo) agent2.repo = ".";
3426
- if (agent2.activeMs == null) agent2.activeMs = 0;
3427
- if (agent2.orphaned == null) agent2.orphaned = false;
3428
- }
3429
- if (session2.orphaned == null) session2.orphaned = false;
3430
- for (const cycle of session2.orchestratorCycles) {
3431
- if (cycle.activeMs == null) cycle.activeMs = 0;
3432
- if (cycle.userBlockedMs == null) cycle.userBlockedMs = 0;
3433
- }
3434
- return session2;
3435
- }
3436
- function saveSession(session2) {
3437
- atomicWrite(statePath(session2.cwd, session2.id), JSON.stringify(session2, null, 2));
3438
- }
3439
- async function incrementUserBlockedMs(cwd, sessionId, deltaMs, askedAt, askedBy) {
3440
- if (deltaMs <= 0) return;
3441
- return withSessionLock(sessionId, () => {
3442
- const session2 = getSession(cwd, sessionId);
3443
- session2.userBlockedMs = (session2.userBlockedMs ?? 0) + deltaMs;
3444
- if (askedAt) {
3445
- const askedAtMs = new Date(askedAt).getTime();
3446
- const cycle = session2.orchestratorCycles.find((c2) => {
3447
- const startMs = new Date(c2.timestamp).getTime();
3448
- const endMs = c2.completedAt ? new Date(c2.completedAt).getTime() : Infinity;
3449
- return startMs <= askedAtMs && askedAtMs < endMs;
3450
- });
3451
- if (cycle) cycle.userBlockedMs = (cycle.userBlockedMs ?? 0) + deltaMs;
3452
- }
3453
- if (askedBy && askedBy !== ORCHESTRATOR_ASKED_BY) {
3454
- const agent2 = session2.agents.slice().reverse().find((a) => a.id === askedBy);
3455
- if (agent2) agent2.userBlockedMs = (agent2.userBlockedMs ?? 0) + deltaMs;
3456
- }
3457
- saveSession(session2);
3458
- });
3459
- }
3460
-
3461
3479
  // src/daemon/ask-store.ts
3462
3480
  var ACTIONABLE_KINDS = /* @__PURE__ */ new Set([
3463
3481
  "validation",
@@ -3511,6 +3529,21 @@ function createAsk(cwd, sessionId, params) {
3511
3529
  }
3512
3530
  function writeDecisions(cwd, sessionId, askId, deck) {
3513
3531
  atomicWrite(askDecisionsPath(cwd, sessionId, askId), JSON.stringify(deck, null, 2));
3532
+ void maybeAutoResolveAsk(cwd, sessionId, askId, deck);
3533
+ }
3534
+ function readDecisions(cwd, sessionId, askId) {
3535
+ const p = askDecisionsPath(cwd, sessionId, askId);
3536
+ try {
3537
+ return JSON.parse(readFileSync12(p, { encoding: "utf-8" }));
3538
+ } catch (_e) {
3539
+ return null;
3540
+ }
3541
+ }
3542
+ function writeOutput(cwd, sessionId, askId, responses, completedAt) {
3543
+ atomicWrite(askOutputPath(cwd, sessionId, askId), JSON.stringify({
3544
+ responses,
3545
+ completedAt: completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3546
+ }, null, 2));
3514
3547
  }
3515
3548
  function readMeta(cwd, sessionId, askId) {
3516
3549
  const p = askMetaPath(cwd, sessionId, askId);
@@ -3530,6 +3563,40 @@ async function updateMeta(cwd, sessionId, askId, patch) {
3530
3563
  return next;
3531
3564
  });
3532
3565
  }
3566
+ function buildAutoResponses(deck) {
3567
+ const out = [];
3568
+ for (const interaction of deck.interactions) {
3569
+ const first = interaction.options[0];
3570
+ if (!first) continue;
3571
+ out.push({ id: interaction.id, selectedOptionId: first.id });
3572
+ }
3573
+ return out;
3574
+ }
3575
+ async function autoResolveAsk(cwd, sessionId, askId, deck) {
3576
+ try {
3577
+ if (existsSync9(askOutputPath(cwd, sessionId, askId))) return false;
3578
+ const d = deck ?? readDecisions(cwd, sessionId, askId);
3579
+ if (!d) return false;
3580
+ const responses = buildAutoResponses(d);
3581
+ if (responses.length === 0) return false;
3582
+ writeOutput(cwd, sessionId, askId, responses);
3583
+ await updateMeta(cwd, sessionId, askId, {
3584
+ status: "answered",
3585
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
3586
+ });
3587
+ return true;
3588
+ } catch (err) {
3589
+ console.warn(`[sisyphus] dangerous-mode auto-resolve failed for ask ${askId}:`, err instanceof Error ? err.message : err);
3590
+ return false;
3591
+ }
3592
+ }
3593
+ async function maybeAutoResolveAsk(cwd, sessionId, askId, deck) {
3594
+ try {
3595
+ if (!isSessionDangerous(cwd, sessionId)) return;
3596
+ await autoResolveAsk(cwd, sessionId, askId, deck);
3597
+ } catch {
3598
+ }
3599
+ }
3533
3600
 
3534
3601
  // src/cli/commands/ask.ts
3535
3602
  init_paths();
@@ -4230,6 +4297,7 @@ import { existsSync as existsSync12 } from "fs";
4230
4297
  import { join as join14, resolve as resolve5 } from "path";
4231
4298
 
4232
4299
  // src/daemon/frontmatter.ts
4300
+ init_paths();
4233
4301
  import { readFileSync as readFileSync16, existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
4234
4302
  import { homedir as homedir7 } from "os";
4235
4303
  import { join as join13, basename as basename4 } from "path";
@@ -4290,6 +4358,8 @@ function discoverAgentTypes(pluginDir, cwd) {
4290
4358
  }
4291
4359
  }
4292
4360
  }
4361
+ scanDir(join13(projectAgentPluginDir(cwd), "agents"), null, "project-sis");
4362
+ scanDir(join13(userAgentPluginDir(), "agents"), null, "user-sis");
4293
4363
  scanDir(join13(cwd, ".claude", "agents"), null, "project");
4294
4364
  scanDir(join13(homedir7(), ".claude", "agents"), null, "user");
4295
4365
  scanDir(join13(pluginDir, "agents"), "sisyphus", "bundled");
@@ -10296,7 +10366,7 @@ function ensureGroveInstalled(provider) {
10296
10366
  }
10297
10367
  }
10298
10368
  function ensureGroveRegistered(provider, repo, instancePath) {
10299
- const cmd = `grove register --update --name ${shellQuote(repo)} ${shellQuote(instancePath)}`;
10369
+ const cmd = `grove register --update --name ${shellQuote(repo)} ${shellQuoteHomePath(instancePath)}`;
10300
10370
  const result = runOnBox(provider, cmd);
10301
10371
  if (result.exitCode !== 0) {
10302
10372
  throw new Error(`Failed to register grove project ${repo}: ${result.stderr || result.stdout}`);
@@ -10382,7 +10452,7 @@ function packageManagerInstallCmd(pm) {
10382
10452
  init_paths();
10383
10453
  function readSidecar(provider, repo) {
10384
10454
  const path = boxCloudSidecarPath(repo);
10385
- const result = runOnBox(provider, `cat ${shellQuote(path)} 2>/dev/null`);
10455
+ const result = runOnBox(provider, `cat ${shellQuoteHomePath(path)} 2>/dev/null`);
10386
10456
  if (result.exitCode !== 0 || !result.stdout.trim()) return null;
10387
10457
  try {
10388
10458
  const parsed = JSON.parse(result.stdout);
@@ -10396,8 +10466,8 @@ function writeSidecar(provider, repo, data) {
10396
10466
  const path = boxCloudSidecarPath(repo);
10397
10467
  const json = JSON.stringify(data, null, 2);
10398
10468
  const cmd = [
10399
- `mkdir -p ${shellQuote(dir)}`,
10400
- `cat > ${shellQuote(path)} <<'SISYPHUS_CLOUD_SIDECAR_EOF'`,
10469
+ `mkdir -p ${shellQuoteHomePath(dir)}`,
10470
+ `cat > ${shellQuoteHomePath(path)} <<'SISYPHUS_CLOUD_SIDECAR_EOF'`,
10401
10471
  json,
10402
10472
  "SISYPHUS_CLOUD_SIDECAR_EOF"
10403
10473
  ].join("\n");
@@ -10438,14 +10508,14 @@ Pass --name <slug> to disambiguate, or --fresh to overwrite.`
10438
10508
  }
10439
10509
  console.log(`\u2192 wiping ${remoteDir} and cloning ${localOrigin} on box...`);
10440
10510
  const cloneCmd = [
10441
- `rm -rf ${shellQuote(remoteDir)}`,
10442
- `mkdir -p ${shellQuote("~/projects")}`,
10443
- `git clone ${shellQuote(localOrigin)} ${shellQuote(remoteDir)}`
10511
+ `rm -rf ${shellQuoteHomePath(remoteDir)}`,
10512
+ `mkdir -p ${shellQuoteHomePath("~/projects")}`,
10513
+ `git clone ${shellQuote(localOrigin)} ${shellQuoteHomePath(remoteDir)}`
10444
10514
  ].join(" && ");
10445
10515
  const code = await runOnBoxStreaming(provider, cloneCmd);
10446
10516
  if (code !== 0) throw new Error(`fresh clone failed (exit ${code})`);
10447
10517
  } else {
10448
- const mkdir = runOnBox(provider, `mkdir -p ${shellQuote(remoteDir)}`);
10518
+ const mkdir = runOnBox(provider, `mkdir -p ${shellQuoteHomePath(remoteDir)}`);
10449
10519
  if (mkdir.exitCode !== 0) {
10450
10520
  throw new Error(`Failed to mkdir on box: ${mkdir.stderr}`);
10451
10521
  }
@@ -10483,7 +10553,7 @@ async function cloudInstall(provider, repo) {
10483
10553
  return;
10484
10554
  }
10485
10555
  console.log(`\u2192 ${pm} install in ${remoteDir} on box...`);
10486
- const remoteCmd = `cd ${shellQuote(remoteDir)} && ${cmd}`;
10556
+ const remoteCmd = `cd ${shellQuoteHomePath(remoteDir)} && ${cmd}`;
10487
10557
  const code = await runOnBoxStreaming(provider, remoteCmd);
10488
10558
  if (code !== 0) throw new Error(`${pm} install failed (exit ${code})`);
10489
10559
  const existing = readSidecar(provider, repo);
@@ -10499,7 +10569,7 @@ async function cloudInstall(provider, repo) {
10499
10569
  }
10500
10570
  async function cloudSession(provider, repo) {
10501
10571
  const remoteDir = boxRepoPath(repo);
10502
- const cmd = `sis admin home-init ${shellQuote(repo)} ${shellQuote(remoteDir)}`;
10572
+ const cmd = `sis admin home-init ${shellQuote(repo)} ${shellQuoteHomePath(remoteDir)}`;
10503
10573
  console.log(`\u2192 initializing tmux home session "${repo}" on box...`);
10504
10574
  const result = runOnBox(provider, cmd);
10505
10575
  if (result.exitCode !== 0) {
@@ -10523,6 +10593,19 @@ Use a fresh terminal, or run from outside tmux:
10523
10593
  });
10524
10594
  child.on("exit", (code) => process.exit(code === null ? 1 : code));
10525
10595
  }
10596
+ function cloudClaudeLogin(provider) {
10597
+ const target = effectiveSshTarget(provider);
10598
+ const remote = [
10599
+ "command -v claude >/dev/null 2>&1",
10600
+ "|| sudo npm i -g @anthropic-ai/claude-code",
10601
+ "&& claude auth login"
10602
+ ].join(" ");
10603
+ const child = spawn4("ssh", ["-t", target, remote], {
10604
+ stdio: "inherit",
10605
+ env: EXEC_ENV
10606
+ });
10607
+ child.on("exit", (code) => process.exit(code === null ? 1 : code));
10608
+ }
10526
10609
  async function cloudStart(provider, repo, opts) {
10527
10610
  await cloudSync(provider, repo, { fresh: opts.fresh, yes: opts.yes });
10528
10611
  await cloudInstall(provider, repo);
@@ -10605,6 +10688,10 @@ function registerCloud(program2) {
10605
10688
  const { provider, repo } = resolve11(raw);
10606
10689
  await cloudStart(provider, repo, { fresh: raw.fresh === true, yes: raw.yes === true });
10607
10690
  });
10691
+ cloud.command("claude-login").description("Run `claude auth login` on the box (device-code flow; paste the URL into your local browser).").option("--provider <name>", "Cloud provider.").action((raw) => {
10692
+ const provider = pickProvider(raw.provider);
10693
+ cloudClaudeLogin(provider);
10694
+ });
10608
10695
  cloud.command("status").description("Print box-side status for this repo (planted, session running, last sync/install).").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action((raw) => {
10609
10696
  const { provider, repo } = resolve11(raw);
10610
10697
  cloudStatus(provider, repo);