sisyphi 1.2.2 → 1.2.12
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 +20 -20
- package/dist/cli.js +12450 -11255
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1113 -565
- package/dist/daemon.js.map +1 -1
- package/dist/templates/agent-plugin/agents/CLAUDE.md +2 -2
- package/dist/templates/agent-plugin/agents/operator.md +3 -4
- package/dist/templates/agent-plugin/agents/plan.md +1 -1
- package/dist/templates/agent-plugin/agents/problem.md +20 -20
- package/dist/templates/agent-plugin/agents/research-lead.md +1 -1
- package/dist/templates/agent-plugin/agents/spec/engineer.md +9 -7
- package/dist/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
- package/dist/templates/agent-plugin/agents/spec.md +31 -25
- package/dist/templates/agent-plugin/hooks/CLAUDE.md +0 -1
- package/dist/templates/agent-plugin/hooks/ask-background-guard.sh +11 -11
- package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/dist/templates/agent-plugin/hooks/operator-user-prompt.sh +2 -2
- package/dist/templates/agent-plugin/hooks/plan-validate.sh +3 -3
- package/dist/templates/agent-plugin/hooks/require-submit.sh +1 -1
- package/dist/templates/agent-plugin/skills/operator/SKILL.md +1 -1
- package/dist/templates/agent-suffix.md +4 -18
- package/dist/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
- package/dist/templates/dashboard-claude.md +15 -13
- package/dist/templates/orchestrator-base.md +44 -78
- package/dist/templates/orchestrator-completion.md +9 -11
- package/dist/templates/orchestrator-discovery.md +8 -8
- package/dist/templates/orchestrator-impl.md +6 -7
- package/dist/templates/orchestrator-planning.md +2 -2
- package/dist/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
- package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
- package/dist/templates/orchestrator-validation.md +1 -3
- package/dist/templates/termrender-haiku-system.md +5 -3
- package/dist/tui.js +1817 -1400
- package/dist/tui.js.map +1 -1
- package/native/build-notify.sh +2 -2
- package/package.json +3 -3
- package/templates/agent-plugin/agents/CLAUDE.md +2 -2
- package/templates/agent-plugin/agents/operator.md +3 -4
- package/templates/agent-plugin/agents/plan.md +1 -1
- package/templates/agent-plugin/agents/problem.md +20 -20
- package/templates/agent-plugin/agents/research-lead.md +1 -1
- package/templates/agent-plugin/agents/spec/engineer.md +9 -7
- package/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
- package/templates/agent-plugin/agents/spec.md +31 -25
- package/templates/agent-plugin/hooks/CLAUDE.md +0 -1
- package/templates/agent-plugin/hooks/ask-background-guard.sh +11 -11
- package/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/templates/agent-plugin/hooks/operator-user-prompt.sh +2 -2
- package/templates/agent-plugin/hooks/plan-validate.sh +3 -3
- package/templates/agent-plugin/hooks/require-submit.sh +1 -1
- package/templates/agent-plugin/skills/operator/SKILL.md +1 -1
- package/templates/agent-suffix.md +4 -18
- package/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
- package/templates/dashboard-claude.md +15 -13
- package/templates/orchestrator-base.md +44 -78
- package/templates/orchestrator-completion.md +9 -11
- package/templates/orchestrator-discovery.md +8 -8
- package/templates/orchestrator-impl.md +6 -7
- package/templates/orchestrator-planning.md +2 -2
- package/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
- package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
- package/templates/orchestrator-validation.md +1 -3
- package/templates/termrender-haiku-system.md +5 -3
- package/dist/templates/agent-plugin/skills/humanloop/SKILL.md +0 -148
- package/dist/templates/agent-plugin/skills/operator-memory/SKILL.md +0 -64
- package/dist/templates/agent-plugin/skills/perspective-fanout/SKILL.md +0 -115
- package/dist/templates/agent-plugin/skills/problem-document/SKILL.md +0 -105
- package/dist/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +0 -83
- package/dist/templates/orchestrator-plugin/skills/humanloop/SKILL.md +0 -150
- package/dist/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +0 -1
- package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +0 -29
- package/dist/templates/orchestrator-plugin/skills/orchestration/strategy.md +0 -160
- package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +0 -266
- package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +0 -428
- package/templates/agent-plugin/skills/humanloop/SKILL.md +0 -148
- package/templates/agent-plugin/skills/operator-memory/SKILL.md +0 -64
- package/templates/agent-plugin/skills/perspective-fanout/SKILL.md +0 -115
- package/templates/agent-plugin/skills/problem-document/SKILL.md +0 -105
- package/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +0 -83
- package/templates/orchestrator-plugin/skills/humanloop/SKILL.md +0 -150
- package/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +0 -1
- package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +0 -29
- package/templates/orchestrator-plugin/skills/orchestration/strategy.md +0 -160
- package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +0 -266
- package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +0 -428
package/dist/daemon.js
CHANGED
|
@@ -18,6 +18,9 @@ __export(paths_exports, {
|
|
|
18
18
|
askMetaPath: () => askMetaPath,
|
|
19
19
|
askOutputPath: () => askOutputPath,
|
|
20
20
|
askProgressPath: () => askProgressPath,
|
|
21
|
+
askReviewDraftPath: () => askReviewDraftPath,
|
|
22
|
+
askReviewPath: () => askReviewPath,
|
|
23
|
+
askReviewSubmitFlagPath: () => askReviewSubmitFlagPath,
|
|
21
24
|
askVisualAnsiPath: () => askVisualAnsiPath,
|
|
22
25
|
askVisualMarkdownPath: () => askVisualMarkdownPath,
|
|
23
26
|
askVisualsDir: () => askVisualsDir,
|
|
@@ -195,14 +198,23 @@ function askMetaPath(cwd, sessionId, askId) {
|
|
|
195
198
|
return join(askEntryDir(cwd, sessionId, askId), "meta.json");
|
|
196
199
|
}
|
|
197
200
|
function askDecisionsPath(cwd, sessionId, askId) {
|
|
198
|
-
return join(askEntryDir(cwd, sessionId, askId), "
|
|
201
|
+
return join(askEntryDir(cwd, sessionId, askId), "deck.json");
|
|
199
202
|
}
|
|
200
203
|
function askOutputPath(cwd, sessionId, askId) {
|
|
201
|
-
return join(askEntryDir(cwd, sessionId, askId), "
|
|
204
|
+
return join(askEntryDir(cwd, sessionId, askId), "response.json");
|
|
202
205
|
}
|
|
203
206
|
function askProgressPath(cwd, sessionId, askId) {
|
|
204
207
|
return join(askEntryDir(cwd, sessionId, askId), "progress.json");
|
|
205
208
|
}
|
|
209
|
+
function askReviewPath(cwd, sessionId, askId) {
|
|
210
|
+
return join(askEntryDir(cwd, sessionId, askId), "review.json");
|
|
211
|
+
}
|
|
212
|
+
function askReviewDraftPath(cwd, sessionId, askId) {
|
|
213
|
+
return join(askEntryDir(cwd, sessionId, askId), "draft.json");
|
|
214
|
+
}
|
|
215
|
+
function askReviewSubmitFlagPath(cwd, sessionId, askId) {
|
|
216
|
+
return join(askEntryDir(cwd, sessionId, askId), "submitted");
|
|
217
|
+
}
|
|
206
218
|
function askVisualsDir(cwd, sessionId, askId) {
|
|
207
219
|
return join(askEntryDir(cwd, sessionId, askId), "visuals");
|
|
208
220
|
}
|
|
@@ -328,6 +340,7 @@ var init_config = __esm({
|
|
|
328
340
|
DEFAULT_CONFIG = {
|
|
329
341
|
model: "claude-opus-4-7[1m]",
|
|
330
342
|
pollIntervalMs: 5e3,
|
|
343
|
+
statusBarRenderTicks: 4,
|
|
331
344
|
orchestratorEffort: "xhigh",
|
|
332
345
|
agentEffort: "medium",
|
|
333
346
|
notifications: {
|
|
@@ -337,11 +350,32 @@ var init_config = __esm({
|
|
|
337
350
|
companionPopup: true,
|
|
338
351
|
requiredPlugins: [
|
|
339
352
|
{ name: "devcore", marketplace: "crouton-kit", owner: "crouton-labs" }
|
|
353
|
+
],
|
|
354
|
+
requiredCrtrPlugins: [
|
|
355
|
+
{
|
|
356
|
+
marketplace: "sisyphus",
|
|
357
|
+
plugin: "sisyphus",
|
|
358
|
+
gitUrl: "https://github.com/crouton-labs/sisyphus"
|
|
359
|
+
}
|
|
340
360
|
]
|
|
341
361
|
};
|
|
342
362
|
}
|
|
343
363
|
});
|
|
344
364
|
|
|
365
|
+
// src/shared/protocol.ts
|
|
366
|
+
var errUsage, errNotFound, errAmbiguous, errConflict, errTransient, errPermanent;
|
|
367
|
+
var init_protocol = __esm({
|
|
368
|
+
"src/shared/protocol.ts"() {
|
|
369
|
+
"use strict";
|
|
370
|
+
errUsage = (code, f) => ({ kind: "usage", code, ...f });
|
|
371
|
+
errNotFound = (code, f) => ({ kind: "not_found", code, ...f });
|
|
372
|
+
errAmbiguous = (code, f) => ({ kind: "ambiguous", code, ...f });
|
|
373
|
+
errConflict = (code, f) => ({ kind: "conflict", code, ...f });
|
|
374
|
+
errTransient = (code, f) => ({ kind: "transient", code, ...f });
|
|
375
|
+
errPermanent = (code, f) => ({ kind: "permanent", code, ...f });
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
345
379
|
// src/shared/shell.ts
|
|
346
380
|
function shellQuote(s) {
|
|
347
381
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
@@ -439,7 +473,7 @@ var init_types = __esm({
|
|
|
439
473
|
});
|
|
440
474
|
|
|
441
475
|
// src/daemon/state.ts
|
|
442
|
-
import { copyFileSync, cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, readdirSync, rmSync, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
476
|
+
import { copyFileSync, cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, readdirSync, rmSync, statSync, watch as fsWatch, writeFileSync as writeFileSync3 } from "fs";
|
|
443
477
|
import { join as join4 } from "path";
|
|
444
478
|
function withSessionLock(sessionId, fn) {
|
|
445
479
|
return withLock(sessionId, fn);
|
|
@@ -481,9 +515,7 @@ function createSession(id, task, cwd, context, name, effort) {
|
|
|
481
515
|
atomicWrite(statePath(cwd, id), JSON.stringify(session, null, 2));
|
|
482
516
|
return session;
|
|
483
517
|
}
|
|
484
|
-
function
|
|
485
|
-
const content = readFileSync3(statePath(cwd, sessionId), "utf-8");
|
|
486
|
-
const session = JSON.parse(content);
|
|
518
|
+
function normalizeSession(session) {
|
|
487
519
|
if (session.activeMs == null) session.activeMs = 0;
|
|
488
520
|
if (session.userBlockedMs == null) session.userBlockedMs = 0;
|
|
489
521
|
for (const agent of session.agents) {
|
|
@@ -496,10 +528,106 @@ function getSession(cwd, sessionId) {
|
|
|
496
528
|
if (cycle.activeMs == null) cycle.activeMs = 0;
|
|
497
529
|
if (cycle.userBlockedMs == null) cycle.userBlockedMs = 0;
|
|
498
530
|
}
|
|
531
|
+
}
|
|
532
|
+
function loadFromDisk(p) {
|
|
533
|
+
const session = JSON.parse(readFileSync3(p, "utf-8"));
|
|
534
|
+
normalizeSession(session);
|
|
499
535
|
return session;
|
|
500
536
|
}
|
|
537
|
+
function reloadIntoCache(p) {
|
|
538
|
+
if (!sessionCache.has(p)) return;
|
|
539
|
+
try {
|
|
540
|
+
sessionCache.set(p, loadFromDisk(p));
|
|
541
|
+
} catch (err) {
|
|
542
|
+
console.warn(`[sisyphus] state cache reload failed for ${p}:`, err instanceof Error ? err.message : err);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function scheduleReload(p) {
|
|
546
|
+
const existing = debounceTimers.get(p);
|
|
547
|
+
if (existing) clearTimeout(existing);
|
|
548
|
+
const t2 = setTimeout(() => {
|
|
549
|
+
debounceTimers.delete(p);
|
|
550
|
+
reloadIntoCache(p);
|
|
551
|
+
}, DEBOUNCE_MS);
|
|
552
|
+
t2.unref?.();
|
|
553
|
+
debounceTimers.set(p, t2);
|
|
554
|
+
}
|
|
555
|
+
function installWatcher(p) {
|
|
556
|
+
if (watchers.has(p)) return;
|
|
557
|
+
let w;
|
|
558
|
+
try {
|
|
559
|
+
w = fsWatch(p, { persistent: false });
|
|
560
|
+
} catch (err) {
|
|
561
|
+
console.warn(`[sisyphus] fs.watch install failed for ${p}:`, err instanceof Error ? err.message : err);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
w.on("change", (eventType) => {
|
|
565
|
+
if (eventType === "rename") {
|
|
566
|
+
try {
|
|
567
|
+
w.close();
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
watchers.delete(p);
|
|
571
|
+
scheduleReload(p);
|
|
572
|
+
const reinstall = setTimeout(() => {
|
|
573
|
+
if (sessionCache.has(p)) installWatcher(p);
|
|
574
|
+
}, DEBOUNCE_MS + 10);
|
|
575
|
+
reinstall.unref?.();
|
|
576
|
+
} else {
|
|
577
|
+
scheduleReload(p);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
w.on("error", (err) => {
|
|
581
|
+
console.warn(`[sisyphus] fs.watch error for ${p}:`, err instanceof Error ? err.message : err);
|
|
582
|
+
try {
|
|
583
|
+
w.close();
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
watchers.delete(p);
|
|
587
|
+
});
|
|
588
|
+
watchers.set(p, w);
|
|
589
|
+
}
|
|
590
|
+
function installStateWatcher(cwd, sessionId) {
|
|
591
|
+
const p = statePath(cwd, sessionId);
|
|
592
|
+
try {
|
|
593
|
+
sessionCache.set(p, loadFromDisk(p));
|
|
594
|
+
} catch (err) {
|
|
595
|
+
console.warn(`[sisyphus] state cache prime failed for ${p}:`, err instanceof Error ? err.message : err);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
setImmediate(() => {
|
|
599
|
+
if (sessionCache.has(p)) installWatcher(p);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
function uninstallStateWatcher(cwd, sessionId) {
|
|
603
|
+
const p = statePath(cwd, sessionId);
|
|
604
|
+
const w = watchers.get(p);
|
|
605
|
+
if (w) {
|
|
606
|
+
try {
|
|
607
|
+
w.close();
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
watchers.delete(p);
|
|
611
|
+
}
|
|
612
|
+
const t2 = debounceTimers.get(p);
|
|
613
|
+
if (t2) {
|
|
614
|
+
clearTimeout(t2);
|
|
615
|
+
debounceTimers.delete(p);
|
|
616
|
+
}
|
|
617
|
+
sessionCache.delete(p);
|
|
618
|
+
}
|
|
619
|
+
function getSession(cwd, sessionId) {
|
|
620
|
+
const p = statePath(cwd, sessionId);
|
|
621
|
+
const hit = sessionCache.get(p);
|
|
622
|
+
if (hit) return structuredClone(hit);
|
|
623
|
+
return loadFromDisk(p);
|
|
624
|
+
}
|
|
501
625
|
function saveSession(session) {
|
|
502
|
-
|
|
626
|
+
const p = statePath(session.cwd, session.id);
|
|
627
|
+
atomicWrite(p, JSON.stringify(session, null, 2));
|
|
628
|
+
if (sessionCache.has(p)) {
|
|
629
|
+
sessionCache.set(p, structuredClone(session));
|
|
630
|
+
}
|
|
503
631
|
}
|
|
504
632
|
function isSessionDangerous(cwd, sessionId) {
|
|
505
633
|
try {
|
|
@@ -874,7 +1002,7 @@ async function createCloneState(sourceCwd, sourceId, cloneId, goal, context, con
|
|
|
874
1002
|
return clone;
|
|
875
1003
|
});
|
|
876
1004
|
}
|
|
877
|
-
var ROADMAP_SEED, CONTEXT_CLAUDE_MD;
|
|
1005
|
+
var ROADMAP_SEED, CONTEXT_CLAUDE_MD, sessionCache, watchers, debounceTimers, DEBOUNCE_MS;
|
|
878
1006
|
var init_state = __esm({
|
|
879
1007
|
"src/daemon/state.ts"() {
|
|
880
1008
|
"use strict";
|
|
@@ -891,6 +1019,10 @@ description: >
|
|
|
891
1019
|
|
|
892
1020
|
Agents save exploration findings, architectural notes, and reference material here for use across cycles.
|
|
893
1021
|
`;
|
|
1022
|
+
sessionCache = /* @__PURE__ */ new Map();
|
|
1023
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1024
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
1025
|
+
DEBOUNCE_MS = 25;
|
|
894
1026
|
}
|
|
895
1027
|
});
|
|
896
1028
|
|
|
@@ -926,6 +1058,38 @@ var init_spawn_helpers = __esm({
|
|
|
926
1058
|
}
|
|
927
1059
|
});
|
|
928
1060
|
|
|
1061
|
+
// src/daemon/help-inject.ts
|
|
1062
|
+
import { execSync } from "child_process";
|
|
1063
|
+
function injectHelp(text) {
|
|
1064
|
+
if (!text.includes("{{HELP:")) return text;
|
|
1065
|
+
const cliBin = resolveCliBin();
|
|
1066
|
+
const renderHelp = (cmd) => {
|
|
1067
|
+
try {
|
|
1068
|
+
return execSync(`${process.execPath} ${cliBin} ${cmd} -h`, {
|
|
1069
|
+
encoding: "utf-8",
|
|
1070
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1071
|
+
timeout: 1e4
|
|
1072
|
+
}).trim();
|
|
1073
|
+
} catch (e) {
|
|
1074
|
+
const out = e.stdout;
|
|
1075
|
+
if (typeof out === "string" && out.length > 0) return out.trim();
|
|
1076
|
+
return `(help unavailable: sis ${cmd} -h)`;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
return text.replace(/\{\{HELP:([^}]+)\}\}/g, (_m, cmd) => {
|
|
1080
|
+
const c = cmd.trim();
|
|
1081
|
+
return `<cli-guide bash="sis ${c} -h">
|
|
1082
|
+
${renderHelp(c)}
|
|
1083
|
+
</cli-guide>`;
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
var init_help_inject = __esm({
|
|
1087
|
+
"src/daemon/help-inject.ts"() {
|
|
1088
|
+
"use strict";
|
|
1089
|
+
init_spawn_helpers();
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
|
|
929
1093
|
// src/shared/env.ts
|
|
930
1094
|
import { resolve as resolve2 } from "path";
|
|
931
1095
|
function augmentedPath() {
|
|
@@ -963,10 +1127,12 @@ function augmentedPath() {
|
|
|
963
1127
|
return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
|
|
964
1128
|
}
|
|
965
1129
|
function execEnv() {
|
|
966
|
-
|
|
1130
|
+
const env = {
|
|
967
1131
|
...process.env,
|
|
968
1132
|
PATH: augmentedPath()
|
|
969
1133
|
};
|
|
1134
|
+
if (!env["LC_ALL"] && !env["LANG"]) env["LANG"] = "en_US.UTF-8";
|
|
1135
|
+
return env;
|
|
970
1136
|
}
|
|
971
1137
|
var init_env = __esm({
|
|
972
1138
|
"src/shared/env.ts"() {
|
|
@@ -975,13 +1141,13 @@ var init_env = __esm({
|
|
|
975
1141
|
});
|
|
976
1142
|
|
|
977
1143
|
// src/shared/exec.ts
|
|
978
|
-
import { execSync } from "child_process";
|
|
1144
|
+
import { execSync as execSync2 } from "child_process";
|
|
979
1145
|
function exec(cmd, cwd, timeoutMs = 3e4) {
|
|
980
|
-
return
|
|
1146
|
+
return execSync2(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, timeout: timeoutMs }).trim();
|
|
981
1147
|
}
|
|
982
1148
|
function execSafe(cmd, cwd, timeoutMs) {
|
|
983
1149
|
try {
|
|
984
|
-
return
|
|
1150
|
+
return execSync2(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
985
1151
|
} catch {
|
|
986
1152
|
return null;
|
|
987
1153
|
}
|
|
@@ -995,6 +1161,31 @@ var init_exec = __esm({
|
|
|
995
1161
|
}
|
|
996
1162
|
});
|
|
997
1163
|
|
|
1164
|
+
// src/shared/digest-verbs.ts
|
|
1165
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1166
|
+
function digestSpinnerVerbs(cwd, sessionId) {
|
|
1167
|
+
try {
|
|
1168
|
+
const dp = digestPath(cwd, sessionId);
|
|
1169
|
+
if (!existsSync4(dp)) return null;
|
|
1170
|
+
const raw = JSON.parse(readFileSync4(dp, "utf-8"));
|
|
1171
|
+
if (!raw || typeof raw.recentWork !== "string" || typeof raw.currentActivity !== "string" || typeof raw.whatsNext !== "string" || !Array.isArray(raw.unusualEvents)) {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
const base = [raw.recentWork, raw.currentActivity, raw.whatsNext].map((v) => v.trim()).filter((v) => v.length > 0);
|
|
1175
|
+
const unusual = raw.unusualEvents.filter((e) => typeof e === "string").map((e) => e.trim()).filter((e) => e.length > 0).map((e) => `unusual: ${e}`);
|
|
1176
|
+
const verbs = [...base, ...unusual];
|
|
1177
|
+
return verbs.length > 0 ? verbs : null;
|
|
1178
|
+
} catch {
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
var init_digest_verbs = __esm({
|
|
1183
|
+
"src/shared/digest-verbs.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
init_paths();
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
|
|
998
1189
|
// src/daemon/colors.ts
|
|
999
1190
|
function normalizeTmuxColor(color) {
|
|
1000
1191
|
return TMUX_COLOR_MAP[color] ?? color;
|
|
@@ -1023,7 +1214,7 @@ var init_colors = __esm({
|
|
|
1023
1214
|
});
|
|
1024
1215
|
|
|
1025
1216
|
// src/daemon/frontmatter.ts
|
|
1026
|
-
import { readFileSync as
|
|
1217
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
1027
1218
|
import { homedir as homedir2 } from "os";
|
|
1028
1219
|
import { join as join5, basename as basename2 } from "path";
|
|
1029
1220
|
function detectProvider(model) {
|
|
@@ -1070,7 +1261,7 @@ function extractAgentBody(content) {
|
|
|
1070
1261
|
function findPluginInstallPath(namespace) {
|
|
1071
1262
|
try {
|
|
1072
1263
|
const registryPath2 = join5(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
1073
|
-
const registry = JSON.parse(
|
|
1264
|
+
const registry = JSON.parse(readFileSync5(registryPath2, "utf-8"));
|
|
1074
1265
|
for (const key of Object.keys(registry)) {
|
|
1075
1266
|
if (key.startsWith(`${namespace}@`)) {
|
|
1076
1267
|
return registry[key].installPath ?? null;
|
|
@@ -1104,7 +1295,7 @@ function resolveAgentTypePath(agentType, pluginDir, cwd) {
|
|
|
1104
1295
|
searchPaths.push(join5(pluginDir, "agents", `${name}.md`));
|
|
1105
1296
|
}
|
|
1106
1297
|
for (const path of searchPaths) {
|
|
1107
|
-
if (
|
|
1298
|
+
if (existsSync5(path)) return path;
|
|
1108
1299
|
}
|
|
1109
1300
|
return null;
|
|
1110
1301
|
}
|
|
@@ -1125,7 +1316,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
1125
1316
|
if (seen.has(qualifiedName)) continue;
|
|
1126
1317
|
seen.add(qualifiedName);
|
|
1127
1318
|
try {
|
|
1128
|
-
const content =
|
|
1319
|
+
const content = readFileSync5(join5(dir, file), "utf-8");
|
|
1129
1320
|
const fm = parseAgentFrontmatter(content);
|
|
1130
1321
|
results.push({ qualifiedName, source, description: fm.description, model: fm.model });
|
|
1131
1322
|
} catch {
|
|
@@ -1140,7 +1331,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
1140
1331
|
scanDir(join5(pluginDir, "agents"), "sisyphus", "bundled");
|
|
1141
1332
|
try {
|
|
1142
1333
|
const registryPath2 = join5(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
1143
|
-
const registry = JSON.parse(
|
|
1334
|
+
const registry = JSON.parse(readFileSync5(registryPath2, "utf-8"));
|
|
1144
1335
|
const pluginEntries = registry.plugins ?? registry;
|
|
1145
1336
|
for (const key of Object.keys(pluginEntries)) {
|
|
1146
1337
|
const atIdx = key.indexOf("@");
|
|
@@ -1160,7 +1351,7 @@ function resolveAgentConfig(agentType, pluginDir, cwd) {
|
|
|
1160
1351
|
const filePath = resolveAgentTypePath(agentType, pluginDir, cwd);
|
|
1161
1352
|
if (!filePath) return null;
|
|
1162
1353
|
try {
|
|
1163
|
-
const content =
|
|
1354
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1164
1355
|
return {
|
|
1165
1356
|
frontmatter: parseAgentFrontmatter(content),
|
|
1166
1357
|
body: extractAgentBody(content),
|
|
@@ -1178,22 +1369,22 @@ var init_frontmatter = __esm({
|
|
|
1178
1369
|
});
|
|
1179
1370
|
|
|
1180
1371
|
// src/daemon/orchestrator-modes.ts
|
|
1181
|
-
import { existsSync as
|
|
1372
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
1182
1373
|
import { resolve as resolve3, join as join6 } from "path";
|
|
1183
1374
|
import { homedir as homedir3 } from "os";
|
|
1184
1375
|
function resolveTemplatesDir() {
|
|
1185
1376
|
const distLayout = resolve3(import.meta.dirname, "../templates");
|
|
1186
|
-
if (
|
|
1377
|
+
if (existsSync6(distLayout)) return distLayout;
|
|
1187
1378
|
const srcLayout = resolve3(import.meta.dirname, "../../templates");
|
|
1188
|
-
if (
|
|
1379
|
+
if (existsSync6(srcLayout)) return srcLayout;
|
|
1189
1380
|
return void 0;
|
|
1190
1381
|
}
|
|
1191
1382
|
function modeLayers(cwd) {
|
|
1192
1383
|
const layers = [];
|
|
1193
1384
|
const project = projectDir(cwd);
|
|
1194
|
-
if (
|
|
1385
|
+
if (existsSync6(project)) layers.push({ source: "project", dir: project });
|
|
1195
1386
|
const user = join6(homedir3(), ".sisyphus");
|
|
1196
|
-
if (
|
|
1387
|
+
if (existsSync6(user)) layers.push({ source: "user", dir: user });
|
|
1197
1388
|
const bundled = resolveTemplatesDir();
|
|
1198
1389
|
if (bundled) layers.push({ source: "bundled", dir: bundled });
|
|
1199
1390
|
return layers;
|
|
@@ -1215,7 +1406,7 @@ function discoverOrchestratorModes(cwd) {
|
|
|
1215
1406
|
const filePath = join6(layer.dir, file);
|
|
1216
1407
|
let content = "";
|
|
1217
1408
|
try {
|
|
1218
|
-
content =
|
|
1409
|
+
content = readFileSync6(filePath, "utf-8");
|
|
1219
1410
|
} catch {
|
|
1220
1411
|
continue;
|
|
1221
1412
|
}
|
|
@@ -1270,7 +1461,7 @@ var init_effort_render = __esm({
|
|
|
1270
1461
|
});
|
|
1271
1462
|
|
|
1272
1463
|
// src/daemon/lib/render-plugin.ts
|
|
1273
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
1464
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2, copyFileSync as copyFileSync2, rmSync as rmSync2, existsSync as existsSync7 } from "fs";
|
|
1274
1465
|
import { join as join7 } from "path";
|
|
1275
1466
|
function isFiltered(name) {
|
|
1276
1467
|
for (const ext of FILTERED_EXTS) {
|
|
@@ -1279,7 +1470,7 @@ function isFiltered(name) {
|
|
|
1279
1470
|
return false;
|
|
1280
1471
|
}
|
|
1281
1472
|
function renderPluginDir(srcDir, destDir, tier) {
|
|
1282
|
-
if (!
|
|
1473
|
+
if (!existsSync7(srcDir)) {
|
|
1283
1474
|
throw new Error(`renderPluginDir: source dir does not exist: ${srcDir}`);
|
|
1284
1475
|
}
|
|
1285
1476
|
rmSync2(destDir, { recursive: true, force: true });
|
|
@@ -1295,7 +1486,7 @@ function walk(src, dest, tier) {
|
|
|
1295
1486
|
walk(srcPath, destPath, tier);
|
|
1296
1487
|
} else if (entry.isFile()) {
|
|
1297
1488
|
if (isFiltered(entry.name)) {
|
|
1298
|
-
const rendered = renderEffortMarkers(
|
|
1489
|
+
const rendered = renderEffortMarkers(readFileSync7(srcPath, "utf-8"), tier);
|
|
1299
1490
|
writeFileSync5(destPath, rendered, "utf-8");
|
|
1300
1491
|
} else {
|
|
1301
1492
|
copyFileSync2(srcPath, destPath);
|
|
@@ -1313,21 +1504,21 @@ var init_render_plugin = __esm({
|
|
|
1313
1504
|
});
|
|
1314
1505
|
|
|
1315
1506
|
// src/daemon/extensions.ts
|
|
1316
|
-
import { existsSync as
|
|
1507
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, copyFileSync as copyFileSync3, mkdirSync as mkdirSync3, statSync as statSync2, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
1317
1508
|
import { resolve as resolve4, join as join8, basename as basename3, relative } from "path";
|
|
1318
1509
|
function resolveBundledTemplateDir(kind) {
|
|
1319
1510
|
const built = resolve4(import.meta.dirname, "../templates", kind);
|
|
1320
|
-
if (
|
|
1511
|
+
if (existsSync8(built)) return built;
|
|
1321
1512
|
const src = resolve4(import.meta.dirname, "../../templates", kind);
|
|
1322
|
-
if (
|
|
1513
|
+
if (existsSync8(src)) return src;
|
|
1323
1514
|
return void 0;
|
|
1324
1515
|
}
|
|
1325
1516
|
function agentPluginLayers(cwd) {
|
|
1326
1517
|
const layers = [];
|
|
1327
1518
|
const project = projectAgentPluginDir(cwd);
|
|
1328
|
-
if (
|
|
1519
|
+
if (existsSync8(project)) layers.push({ source: "project", root: project });
|
|
1329
1520
|
const user = userAgentPluginDir();
|
|
1330
|
-
if (
|
|
1521
|
+
if (existsSync8(user)) layers.push({ source: "user", root: user });
|
|
1331
1522
|
const bundled = resolveBundledTemplateDir("agent-plugin");
|
|
1332
1523
|
if (bundled) layers.push({ source: "bundled", root: bundled });
|
|
1333
1524
|
return layers;
|
|
@@ -1335,18 +1526,18 @@ function agentPluginLayers(cwd) {
|
|
|
1335
1526
|
function orchestratorPluginLayers(cwd) {
|
|
1336
1527
|
const layers = [];
|
|
1337
1528
|
const project = projectOrchestratorPluginDir(cwd);
|
|
1338
|
-
if (
|
|
1529
|
+
if (existsSync8(project)) layers.push({ source: "project", root: project });
|
|
1339
1530
|
const user = userOrchestratorPluginDir();
|
|
1340
|
-
if (
|
|
1531
|
+
if (existsSync8(user)) layers.push({ source: "user", root: user });
|
|
1341
1532
|
const bundled = resolveBundledTemplateDir("orchestrator-plugin");
|
|
1342
1533
|
if (bundled) layers.push({ source: "bundled", root: bundled });
|
|
1343
1534
|
return layers;
|
|
1344
1535
|
}
|
|
1345
1536
|
function readManifest(layerRoot) {
|
|
1346
1537
|
const path = join8(layerRoot, "hooks", "hooks.json");
|
|
1347
|
-
if (!
|
|
1538
|
+
if (!existsSync8(path)) return null;
|
|
1348
1539
|
try {
|
|
1349
|
-
const raw =
|
|
1540
|
+
const raw = readFileSync8(path, "utf-8");
|
|
1350
1541
|
const parsed = JSON.parse(raw);
|
|
1351
1542
|
return parsed;
|
|
1352
1543
|
} catch (err) {
|
|
@@ -1354,8 +1545,8 @@ function readManifest(layerRoot) {
|
|
|
1354
1545
|
return null;
|
|
1355
1546
|
}
|
|
1356
1547
|
}
|
|
1357
|
-
function commandScriptName(
|
|
1358
|
-
const match =
|
|
1548
|
+
function commandScriptName(command) {
|
|
1549
|
+
const match = command.match(/([\w.-]+\.[\w]+)\s*$/);
|
|
1359
1550
|
return match ? match[1] : null;
|
|
1360
1551
|
}
|
|
1361
1552
|
function groupApplies(group, ctx) {
|
|
@@ -1402,7 +1593,7 @@ function copyLayered(layers, opts) {
|
|
|
1402
1593
|
const written = /* @__PURE__ */ new Set();
|
|
1403
1594
|
for (const layer of layers) {
|
|
1404
1595
|
const layerSubdir = join8(layer.root, opts.subdir);
|
|
1405
|
-
if (!
|
|
1596
|
+
if (!existsSync8(layerSubdir)) continue;
|
|
1406
1597
|
let entries;
|
|
1407
1598
|
try {
|
|
1408
1599
|
entries = readdirSync5(layerSubdir);
|
|
@@ -1470,7 +1661,7 @@ function indexAvailableSkills(layers) {
|
|
|
1470
1661
|
const index = /* @__PURE__ */ new Map();
|
|
1471
1662
|
for (const layer of layers) {
|
|
1472
1663
|
const skillsRoot = join8(layer.root, "skills");
|
|
1473
|
-
if (!
|
|
1664
|
+
if (!existsSync8(skillsRoot)) continue;
|
|
1474
1665
|
let entries;
|
|
1475
1666
|
try {
|
|
1476
1667
|
entries = readdirSync5(skillsRoot);
|
|
@@ -1504,7 +1695,7 @@ function isRenderable(name) {
|
|
|
1504
1695
|
function collectOverlay(layers) {
|
|
1505
1696
|
const overlay = /* @__PURE__ */ new Map();
|
|
1506
1697
|
for (const layer of layers) {
|
|
1507
|
-
if (!
|
|
1698
|
+
if (!existsSync8(layer.root)) continue;
|
|
1508
1699
|
walkRelative(layer.root, "", (relPath, absPath) => {
|
|
1509
1700
|
if (overlay.has(relPath)) return;
|
|
1510
1701
|
overlay.set(relPath, { src: absPath, source: layer.source });
|
|
@@ -1544,7 +1735,7 @@ function renderLayeredPluginDir(layers, destDir, tier) {
|
|
|
1544
1735
|
const destPath = join8(destDir, relPath);
|
|
1545
1736
|
mkdirSync3(join8(destPath, ".."), { recursive: true });
|
|
1546
1737
|
if (isRenderable(basename3(relPath))) {
|
|
1547
|
-
const rendered = renderEffortMarkers(
|
|
1738
|
+
const rendered = renderEffortMarkers(readFileSync8(entry.src, "utf-8"), tier);
|
|
1548
1739
|
writeFileSync6(destPath, rendered, "utf-8");
|
|
1549
1740
|
} else {
|
|
1550
1741
|
copyFileSync3(entry.src, destPath);
|
|
@@ -1563,7 +1754,7 @@ var init_extensions = __esm({
|
|
|
1563
1754
|
});
|
|
1564
1755
|
|
|
1565
1756
|
// src/daemon/tmux.ts
|
|
1566
|
-
import { execSync as
|
|
1757
|
+
import { execSync as execSync3 } from "child_process";
|
|
1567
1758
|
function planSendKeys(state) {
|
|
1568
1759
|
if (!state.exists || state.dead) return { action: "abort" };
|
|
1569
1760
|
if (state.inMode) return { action: "cancel-then-send" };
|
|
@@ -1585,14 +1776,14 @@ function createPane(windowTarget, cwd, position = "right") {
|
|
|
1585
1776
|
execSafe(`tmux select-layout -t ${t(windowTarget)} even-horizontal`);
|
|
1586
1777
|
return paneId;
|
|
1587
1778
|
}
|
|
1588
|
-
function sendKeys(paneTarget,
|
|
1779
|
+
function sendKeys(paneTarget, command) {
|
|
1589
1780
|
const state = getPaneState(paneTarget);
|
|
1590
1781
|
const { action } = planSendKeys(state);
|
|
1591
1782
|
if (action === "abort") throw new PaneUnavailableError(paneTarget, state);
|
|
1592
1783
|
if (action === "cancel-then-send") {
|
|
1593
1784
|
execSafe(`tmux send-keys -t ${t(paneTarget)} -X cancel`, void 0, TMUX_TIMEOUT_MS);
|
|
1594
1785
|
}
|
|
1595
|
-
exec(`tmux send-keys -t ${t(paneTarget)} ${shellQuote(
|
|
1786
|
+
exec(`tmux send-keys -t ${t(paneTarget)} ${shellQuote(command)} Enter`, void 0, TMUX_TIMEOUT_MS);
|
|
1596
1787
|
}
|
|
1597
1788
|
function pasteToPane(paneTarget, text, submit) {
|
|
1598
1789
|
const state = getPaneState(paneTarget);
|
|
@@ -1602,7 +1793,7 @@ function pasteToPane(paneTarget, text, submit) {
|
|
|
1602
1793
|
execSafe(`tmux send-keys -t ${t(paneTarget)} -X cancel`, void 0, TMUX_TIMEOUT_MS);
|
|
1603
1794
|
}
|
|
1604
1795
|
const bufName = `sisyphus-tell-${Math.random().toString(36).slice(2, 10)}`;
|
|
1605
|
-
|
|
1796
|
+
execSync3(`tmux load-buffer -b ${shellQuote(bufName)} -`, {
|
|
1606
1797
|
input: text,
|
|
1607
1798
|
env: EXEC_ENV,
|
|
1608
1799
|
timeout: TMUX_TIMEOUT_MS,
|
|
@@ -1703,13 +1894,27 @@ function getFirstWindowId(sessionTarget) {
|
|
|
1703
1894
|
return execSafe(`tmux list-windows -t ${t(sessionTarget)} -F "#{window_id}" -f "#{==:#{window_index},0}"`)?.trim() || null;
|
|
1704
1895
|
}
|
|
1705
1896
|
function listPanes(windowTarget) {
|
|
1706
|
-
const output = execSafe(`tmux list-panes -t ${t(windowTarget)} -F "#{pane_id} #{pane_pid}"
|
|
1897
|
+
const output = execSafe(`tmux list-panes -t ${t(windowTarget)} -F "#{pane_id} #{pane_pid}"`, void 0, TMUX_TIMEOUT_MS);
|
|
1707
1898
|
if (!output) return [];
|
|
1708
1899
|
return output.split("\n").filter(Boolean).map((line) => {
|
|
1709
1900
|
const [paneId, panePid] = line.split(" ");
|
|
1710
1901
|
return { paneId, panePid };
|
|
1711
1902
|
});
|
|
1712
1903
|
}
|
|
1904
|
+
function listAllPanesByWindow() {
|
|
1905
|
+
const output = execSafe('tmux list-panes -a -F "#{window_id} #{pane_id} #{pane_pid}"', void 0, TMUX_TIMEOUT_MS);
|
|
1906
|
+
const map = /* @__PURE__ */ new Map();
|
|
1907
|
+
if (!output) return map;
|
|
1908
|
+
for (const line of output.split("\n")) {
|
|
1909
|
+
if (!line) continue;
|
|
1910
|
+
const [windowId, paneId, panePid] = line.split(" ");
|
|
1911
|
+
if (!windowId || !paneId || !panePid) continue;
|
|
1912
|
+
const bucket = map.get(windowId);
|
|
1913
|
+
if (bucket) bucket.push({ paneId, panePid });
|
|
1914
|
+
else map.set(windowId, [{ paneId, panePid }]);
|
|
1915
|
+
}
|
|
1916
|
+
return map;
|
|
1917
|
+
}
|
|
1713
1918
|
function setPaneTitle(paneTarget, title) {
|
|
1714
1919
|
execSafe(`tmux select-pane -t ${t(paneTarget)} -T ${shellQuote(title)}`);
|
|
1715
1920
|
}
|
|
@@ -1754,11 +1959,6 @@ function listAllSessions() {
|
|
|
1754
1959
|
if (!output) return [];
|
|
1755
1960
|
return output.split("\n").filter(Boolean).map(parseSessionLine);
|
|
1756
1961
|
}
|
|
1757
|
-
function listWindowPanes(windowTarget) {
|
|
1758
|
-
const output = execSafe(`tmux list-panes -t ${t(windowTarget)} -F '#{pane_id}'`);
|
|
1759
|
-
if (!output) return [];
|
|
1760
|
-
return output.split("\n").filter(Boolean).map((paneId) => ({ paneId }));
|
|
1761
|
-
}
|
|
1762
1962
|
function listAllPanes() {
|
|
1763
1963
|
const output = execSafe('tmux list-panes -a -F "#{session_name} #{pane_id}"');
|
|
1764
1964
|
if (!output) return [];
|
|
@@ -1767,6 +1967,23 @@ function listAllPanes() {
|
|
|
1767
1967
|
return { sessionName: line.slice(0, spaceIdx), paneId: line.slice(spaceIdx + 1) };
|
|
1768
1968
|
});
|
|
1769
1969
|
}
|
|
1970
|
+
function listAllWindowsBySession() {
|
|
1971
|
+
const output = execSafe('tmux list-windows -a -F "#{session_name} #{window_index} #{window_id} #{window_name}"', void 0, TMUX_TIMEOUT_MS);
|
|
1972
|
+
const map = /* @__PURE__ */ new Map();
|
|
1973
|
+
if (!output) return map;
|
|
1974
|
+
for (const line of output.split("\n")) {
|
|
1975
|
+
if (!line) continue;
|
|
1976
|
+
const [sessionName, indexStr, id, ...nameParts] = line.split(" ");
|
|
1977
|
+
if (!sessionName || indexStr == null || !id) continue;
|
|
1978
|
+
const index = parseInt(indexStr, 10);
|
|
1979
|
+
if (!Number.isFinite(index)) continue;
|
|
1980
|
+
const name = nameParts.join(" ");
|
|
1981
|
+
const bucket = map.get(sessionName);
|
|
1982
|
+
if (bucket) bucket.push({ index, id, name });
|
|
1983
|
+
else map.set(sessionName, [{ index, id, name }]);
|
|
1984
|
+
}
|
|
1985
|
+
return map;
|
|
1986
|
+
}
|
|
1770
1987
|
function configureSessionDefaults(sessionTarget, windowId) {
|
|
1771
1988
|
execSafe(`tmux set -w -t ${t(windowId)} pane-border-status top`);
|
|
1772
1989
|
execSafe(`tmux set -w -t ${t(windowId)} allow-rename off`);
|
|
@@ -2005,7 +2222,7 @@ var init_summarize = __esm({
|
|
|
2005
2222
|
});
|
|
2006
2223
|
|
|
2007
2224
|
// src/daemon/plugins.ts
|
|
2008
|
-
import { readFileSync as
|
|
2225
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2009
2226
|
import { execFileSync } from "child_process";
|
|
2010
2227
|
import { homedir as homedir4 } from "os";
|
|
2011
2228
|
import { join as join9 } from "path";
|
|
@@ -2015,7 +2232,7 @@ function installedPluginsPath() {
|
|
|
2015
2232
|
function resolveInstalledPlugin(name) {
|
|
2016
2233
|
let data;
|
|
2017
2234
|
try {
|
|
2018
|
-
data = JSON.parse(
|
|
2235
|
+
data = JSON.parse(readFileSync9(installedPluginsPath(), "utf-8"));
|
|
2019
2236
|
} catch {
|
|
2020
2237
|
return null;
|
|
2021
2238
|
}
|
|
@@ -2078,7 +2295,7 @@ var init_plugins = __esm({
|
|
|
2078
2295
|
});
|
|
2079
2296
|
|
|
2080
2297
|
// src/daemon/history.ts
|
|
2081
|
-
import { appendFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, renameSync as renameSync2, readdirSync as readdirSync6, readFileSync as
|
|
2298
|
+
import { appendFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, renameSync as renameSync2, readdirSync as readdirSync6, readFileSync as readFileSync10, rmSync as rmSync4, statSync as statSync3 } from "fs";
|
|
2082
2299
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2083
2300
|
import { dirname as dirname2, join as join10 } from "path";
|
|
2084
2301
|
function ensureDir(sessionId) {
|
|
@@ -2181,7 +2398,7 @@ function getRecentSentiments(count = 5, scanLimit = 30, overrideBaseDir) {
|
|
|
2181
2398
|
const limit = Math.min(withMtime.length, scanLimit);
|
|
2182
2399
|
for (let i = 0; i < limit && results.length < count; i++) {
|
|
2183
2400
|
try {
|
|
2184
|
-
const raw =
|
|
2401
|
+
const raw = readFileSync10(join10(base, withMtime[i].name, "session.json"), "utf-8");
|
|
2185
2402
|
const summary = JSON.parse(raw);
|
|
2186
2403
|
if (summary.sentiment && summary.completedAt) {
|
|
2187
2404
|
results.push({
|
|
@@ -2213,13 +2430,13 @@ function pruneHistory() {
|
|
|
2213
2430
|
const dir = join10(base, name);
|
|
2214
2431
|
try {
|
|
2215
2432
|
const summaryPath = join10(dir, "session.json");
|
|
2216
|
-
const raw =
|
|
2433
|
+
const raw = readFileSync10(summaryPath, "utf-8");
|
|
2217
2434
|
const summary = JSON.parse(raw);
|
|
2218
2435
|
sessions.push({ dir, startedAt: new Date(summary.startedAt ?? 0).getTime() });
|
|
2219
2436
|
} catch {
|
|
2220
2437
|
try {
|
|
2221
2438
|
const eventsPath = join10(dir, "events.jsonl");
|
|
2222
|
-
const firstLine =
|
|
2439
|
+
const firstLine = readFileSync10(eventsPath, "utf-8").split("\n")[0];
|
|
2223
2440
|
const firstEvent = JSON.parse(firstLine);
|
|
2224
2441
|
sessions.push({ dir, startedAt: new Date(firstEvent.ts ?? 0).getTime() });
|
|
2225
2442
|
} catch {
|
|
@@ -2255,8 +2472,8 @@ var init_history = __esm({
|
|
|
2255
2472
|
});
|
|
2256
2473
|
|
|
2257
2474
|
// src/shared/platform.ts
|
|
2258
|
-
import { execSync as
|
|
2259
|
-
import { existsSync as
|
|
2475
|
+
import { execSync as execSync4 } from "child_process";
|
|
2476
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
2260
2477
|
function detectPlatform() {
|
|
2261
2478
|
if (cachedPlatform) return cachedPlatform;
|
|
2262
2479
|
if (process.platform === "darwin") {
|
|
@@ -2273,8 +2490,8 @@ function detectPlatform() {
|
|
|
2273
2490
|
function isWsl() {
|
|
2274
2491
|
if (process.env["WSL_DISTRO_NAME"] || process.env["WSL_INTEROP"]) return true;
|
|
2275
2492
|
try {
|
|
2276
|
-
if (
|
|
2277
|
-
const v =
|
|
2493
|
+
if (existsSync9("/proc/version")) {
|
|
2494
|
+
const v = readFileSync11("/proc/version", "utf-8").toLowerCase();
|
|
2278
2495
|
if (v.includes("microsoft") || v.includes("wsl")) return true;
|
|
2279
2496
|
}
|
|
2280
2497
|
} catch {
|
|
@@ -2285,7 +2502,7 @@ function hasCommand(cmd) {
|
|
|
2285
2502
|
const cached = cmdCache.get(cmd);
|
|
2286
2503
|
if (cached !== void 0) return cached;
|
|
2287
2504
|
try {
|
|
2288
|
-
|
|
2505
|
+
execSync4(`command -v ${cmd}`, { stdio: "pipe", shell: "/bin/sh" });
|
|
2289
2506
|
cmdCache.set(cmd, true);
|
|
2290
2507
|
return true;
|
|
2291
2508
|
} catch {
|
|
@@ -2303,7 +2520,7 @@ var init_platform = __esm({
|
|
|
2303
2520
|
|
|
2304
2521
|
// src/daemon/notify.ts
|
|
2305
2522
|
import { spawn, execFile } from "child_process";
|
|
2306
|
-
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as
|
|
2523
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
2307
2524
|
import { join as join11 } from "path";
|
|
2308
2525
|
import { homedir as homedir5 } from "os";
|
|
2309
2526
|
function ensureSwitchScript() {
|
|
@@ -2323,7 +2540,7 @@ function ensureNotifyProcess() {
|
|
|
2323
2540
|
return notifyProcess;
|
|
2324
2541
|
}
|
|
2325
2542
|
const binary = getNotifyBinary();
|
|
2326
|
-
if (!
|
|
2543
|
+
if (!existsSync10(binary)) {
|
|
2327
2544
|
return null;
|
|
2328
2545
|
}
|
|
2329
2546
|
notifyProcess = spawn(binary, [], {
|
|
@@ -2445,7 +2662,7 @@ var init_notify = __esm({
|
|
|
2445
2662
|
});
|
|
2446
2663
|
|
|
2447
2664
|
// src/daemon/ask-store.ts
|
|
2448
|
-
import { existsSync as
|
|
2665
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, readdirSync as readdirSync7 } from "fs";
|
|
2449
2666
|
function maybeNotifyOnAskCreated(cwd, sessionId, meta) {
|
|
2450
2667
|
if (process.env.NODE_ENV === "test" || process.env.SISYPHUS_DISABLE_NOTIFY === "1") return;
|
|
2451
2668
|
const isActionable = meta.kind !== void 0 && ACTIONABLE_KINDS.has(meta.kind);
|
|
@@ -2496,7 +2713,7 @@ function writeDecisions(cwd, sessionId, askId, deck) {
|
|
|
2496
2713
|
function readDecisions(cwd, sessionId, askId) {
|
|
2497
2714
|
const p = askDecisionsPath(cwd, sessionId, askId);
|
|
2498
2715
|
try {
|
|
2499
|
-
return JSON.parse(
|
|
2716
|
+
return JSON.parse(readFileSync12(p, { encoding: "utf-8" }));
|
|
2500
2717
|
} catch (_e) {
|
|
2501
2718
|
return null;
|
|
2502
2719
|
}
|
|
@@ -2509,25 +2726,50 @@ function writeOutput(cwd, sessionId, askId, responses, completedAt) {
|
|
|
2509
2726
|
}
|
|
2510
2727
|
function readMeta(cwd, sessionId, askId) {
|
|
2511
2728
|
const p = askMetaPath(cwd, sessionId, askId);
|
|
2512
|
-
if (!
|
|
2729
|
+
if (!existsSync11(p)) {
|
|
2513
2730
|
return null;
|
|
2514
2731
|
}
|
|
2515
|
-
return JSON.parse(
|
|
2732
|
+
return JSON.parse(readFileSync12(p, "utf-8"));
|
|
2516
2733
|
}
|
|
2517
2734
|
async function updateMeta(cwd, sessionId, askId, patch) {
|
|
2518
|
-
|
|
2735
|
+
const next = await withLock(askId, () => {
|
|
2519
2736
|
const cur = readMeta(cwd, sessionId, askId);
|
|
2520
2737
|
if (!cur) {
|
|
2521
2738
|
throw new Error(`updateMeta: askId ${askId} not found`);
|
|
2522
2739
|
}
|
|
2523
|
-
const
|
|
2524
|
-
atomicWrite(askMetaPath(cwd, sessionId, askId), JSON.stringify(
|
|
2525
|
-
return
|
|
2740
|
+
const updated = { ...cur, ...patch };
|
|
2741
|
+
atomicWrite(askMetaPath(cwd, sessionId, askId), JSON.stringify(updated, null, 2));
|
|
2742
|
+
return updated;
|
|
2743
|
+
});
|
|
2744
|
+
if (patch.status === "answered" && next.heartbeatAskId) {
|
|
2745
|
+
const hbAskId = next.heartbeatAskId;
|
|
2746
|
+
cascadeResolveHeartbeatAsk(cwd, sessionId, hbAskId).catch((err) => {
|
|
2747
|
+
console.warn(
|
|
2748
|
+
`[sisyphus] heartbeat cascade-resolve failed for ${hbAskId}:`,
|
|
2749
|
+
err instanceof Error ? err.message : err
|
|
2750
|
+
);
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
return next;
|
|
2754
|
+
}
|
|
2755
|
+
async function cascadeResolveHeartbeatAsk(cwd, sessionId, heartbeatAskId) {
|
|
2756
|
+
const hbMeta = readMeta(cwd, sessionId, heartbeatAskId);
|
|
2757
|
+
if (!hbMeta) return;
|
|
2758
|
+
if (hbMeta.status === "answered") return;
|
|
2759
|
+
if (existsSync11(askOutputPath(cwd, sessionId, heartbeatAskId))) return;
|
|
2760
|
+
writeOutput(cwd, sessionId, heartbeatAskId, [{
|
|
2761
|
+
id: "heartbeat",
|
|
2762
|
+
selectedOptionId: "ack",
|
|
2763
|
+
freetext: "auto-resolved: original ask was answered"
|
|
2764
|
+
}]);
|
|
2765
|
+
await updateMeta(cwd, sessionId, heartbeatAskId, {
|
|
2766
|
+
status: "answered",
|
|
2767
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2526
2768
|
});
|
|
2527
2769
|
}
|
|
2528
2770
|
function listAsks(cwd, sessionId) {
|
|
2529
2771
|
const dir = askDir(cwd, sessionId);
|
|
2530
|
-
if (!
|
|
2772
|
+
if (!existsSync11(dir)) {
|
|
2531
2773
|
return [];
|
|
2532
2774
|
}
|
|
2533
2775
|
return readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -2543,7 +2785,7 @@ function buildAutoResponses(deck) {
|
|
|
2543
2785
|
}
|
|
2544
2786
|
async function autoResolveAsk(cwd, sessionId, askId, deck) {
|
|
2545
2787
|
try {
|
|
2546
|
-
if (
|
|
2788
|
+
if (existsSync11(askOutputPath(cwd, sessionId, askId))) return false;
|
|
2547
2789
|
const d = deck ?? readDecisions(cwd, sessionId, askId);
|
|
2548
2790
|
if (!d) return false;
|
|
2549
2791
|
const responses = buildAutoResponses(d);
|
|
@@ -2563,6 +2805,8 @@ async function maybeAutoResolveAsk(cwd, sessionId, askId, deck) {
|
|
|
2563
2805
|
try {
|
|
2564
2806
|
if (!isSessionDangerous(cwd, sessionId)) return;
|
|
2565
2807
|
if (deck.source?.askedBy === ORPHAN_ASKED_BY) return;
|
|
2808
|
+
const meta = readMeta(cwd, sessionId, askId);
|
|
2809
|
+
if (meta?.kind === "review") return;
|
|
2566
2810
|
await autoResolveAsk(cwd, sessionId, askId, deck);
|
|
2567
2811
|
} catch {
|
|
2568
2812
|
}
|
|
@@ -2576,7 +2820,7 @@ function listOpenAsksFor(cwd, sessionId, askedBy) {
|
|
|
2576
2820
|
if (meta.orphaned) continue;
|
|
2577
2821
|
if (!meta.blocking) continue;
|
|
2578
2822
|
if (meta.status !== "pending" && meta.status !== "in-progress") continue;
|
|
2579
|
-
if (
|
|
2823
|
+
if (existsSync11(askOutputPath(cwd, sessionId, askId))) continue;
|
|
2580
2824
|
out.push({ askId, status: meta.status, ...meta.title !== void 0 ? { title: meta.title } : {} });
|
|
2581
2825
|
}
|
|
2582
2826
|
return out;
|
|
@@ -2596,7 +2840,8 @@ var init_ask_store = __esm({
|
|
|
2596
2840
|
"validation",
|
|
2597
2841
|
"decision",
|
|
2598
2842
|
"context",
|
|
2599
|
-
"error"
|
|
2843
|
+
"error",
|
|
2844
|
+
"review"
|
|
2600
2845
|
]);
|
|
2601
2846
|
HEARTBEAT_ASKED_BY = "system:heartbeat";
|
|
2602
2847
|
ORPHAN_ASKED_BY = "system:orphan-handler";
|
|
@@ -2792,9 +3037,38 @@ async function orphanRunningAgentAsks(cwd, sessionId, session) {
|
|
|
2792
3037
|
if (runningAgentIds.length === 0) return;
|
|
2793
3038
|
await Promise.all(runningAgentIds.map((agentId) => markAgentAsksOrphan(cwd, sessionId, agentId)));
|
|
2794
3039
|
}
|
|
2795
|
-
async function
|
|
3040
|
+
async function resolveAgentOrphanAsks(cwd, sessionId, agentId, resolution) {
|
|
3041
|
+
const asks = listAsks(cwd, sessionId);
|
|
3042
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3043
|
+
const selectedOptionId = "dismiss";
|
|
3044
|
+
const freetext = resolution === "respawn" ? "auto-resolved: agent was restarted" : resolution === "superseded" ? "auto-resolved: agent superseded by replacement" : "auto-resolved by system";
|
|
3045
|
+
await mapWithLimit(asks, ASK_FANOUT_LIMIT, async (askId) => {
|
|
3046
|
+
try {
|
|
3047
|
+
const meta = readMeta(cwd, sessionId, askId);
|
|
3048
|
+
if (!meta) return;
|
|
3049
|
+
if (meta.askedBy !== ORPHAN_ASKED_BY2) return;
|
|
3050
|
+
if (meta.status === "answered") return;
|
|
3051
|
+
if (meta.orphanTarget?.kind !== "agent") return;
|
|
3052
|
+
if (meta.orphanTarget.agentId !== agentId) return;
|
|
3053
|
+
writeOutput(cwd, sessionId, askId, [{
|
|
3054
|
+
id: "orphan",
|
|
3055
|
+
selectedOptionId,
|
|
3056
|
+
freetext
|
|
3057
|
+
}], completedAt);
|
|
3058
|
+
await updateMeta(cwd, sessionId, askId, { status: "answered", completedAt });
|
|
3059
|
+
} catch (err) {
|
|
3060
|
+
console.warn(
|
|
3061
|
+
`[sisyphus] resolveAgentOrphanAsks: ${sessionId}/${askId} failed:`,
|
|
3062
|
+
err instanceof Error ? err.message : err
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
async function resolveOrchestratorOrphanAsks(cwd, sessionId, resolution) {
|
|
2796
3068
|
const asks = listAsks(cwd, sessionId);
|
|
2797
3069
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3070
|
+
const selectedOptionId = resolution === "respawn" ? "dismiss" : resolution;
|
|
3071
|
+
const freetext = resolution === "resume" ? "auto-resolved by sis session lifecycle resume" : resolution === "respawn" ? "auto-resolved by orchestrator auto-respawn" : "auto-resolved by system";
|
|
2798
3072
|
await mapWithLimit(asks, ASK_FANOUT_LIMIT, async (askId) => {
|
|
2799
3073
|
try {
|
|
2800
3074
|
const meta = readMeta(cwd, sessionId, askId);
|
|
@@ -2805,7 +3079,7 @@ async function resolveOrchestratorOrphanAsks(cwd, sessionId, selectedOptionId) {
|
|
|
2805
3079
|
writeOutput(cwd, sessionId, askId, [{
|
|
2806
3080
|
id: "orphan",
|
|
2807
3081
|
selectedOptionId,
|
|
2808
|
-
freetext
|
|
3082
|
+
freetext
|
|
2809
3083
|
}], completedAt);
|
|
2810
3084
|
await updateMeta(cwd, sessionId, askId, { status: "answered", completedAt });
|
|
2811
3085
|
} catch (err) {
|
|
@@ -2845,17 +3119,18 @@ var init_process = __esm({
|
|
|
2845
3119
|
});
|
|
2846
3120
|
|
|
2847
3121
|
// src/daemon/orphan-sweep.ts
|
|
2848
|
-
import { existsSync as
|
|
2849
|
-
import {
|
|
2850
|
-
|
|
3122
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3123
|
+
import { execFile as execFile2 } from "child_process";
|
|
3124
|
+
import { promisify } from "util";
|
|
3125
|
+
async function probePidLstart(pid, expectedLstart, psRunner = defaultPsRunner) {
|
|
2851
3126
|
try {
|
|
2852
|
-
const lstart = psRunner(pid, execEnv());
|
|
3127
|
+
const lstart = await psRunner(pid, execEnv());
|
|
2853
3128
|
if (!lstart) return "gone";
|
|
2854
3129
|
if (lstart === expectedLstart) return "live";
|
|
2855
3130
|
return "recycled";
|
|
2856
3131
|
} catch (err) {
|
|
2857
3132
|
const e = err;
|
|
2858
|
-
if (e.
|
|
3133
|
+
if (e.code === 1) return "gone";
|
|
2859
3134
|
return "unknown";
|
|
2860
3135
|
}
|
|
2861
3136
|
}
|
|
@@ -2865,7 +3140,7 @@ async function capturePanePidLstart(paneId) {
|
|
|
2865
3140
|
try {
|
|
2866
3141
|
const pid = getPanePid(paneId);
|
|
2867
3142
|
if (pid === null) throw new Error("tmux returned non-integer pane_pid");
|
|
2868
|
-
const lstart = defaultPsRunner(pid, env);
|
|
3143
|
+
const lstart = await defaultPsRunner(pid, env);
|
|
2869
3144
|
if (!lstart) throw new Error("ps returned empty lstart");
|
|
2870
3145
|
return { pid, lstart };
|
|
2871
3146
|
} catch (captureErr) {
|
|
@@ -2879,18 +3154,22 @@ async function capturePanePidLstart(paneId) {
|
|
|
2879
3154
|
}
|
|
2880
3155
|
async function sweepOrphans(registry) {
|
|
2881
3156
|
const reg = registry ?? loadSessionRegistry();
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
3157
|
+
await Promise.all(
|
|
3158
|
+
Object.entries(reg).map(async ([sessionId, cwd]) => {
|
|
3159
|
+
if (!existsSync12(statePath(cwd, sessionId))) return;
|
|
3160
|
+
try {
|
|
3161
|
+
await Promise.all([
|
|
3162
|
+
sweepSessionAgents(cwd, sessionId),
|
|
3163
|
+
sweepSessionAsks(cwd, sessionId)
|
|
3164
|
+
]);
|
|
3165
|
+
} catch (err) {
|
|
3166
|
+
console.warn(
|
|
3167
|
+
`[sisyphus] orphan-sweep failed for ${sessionId}:`,
|
|
3168
|
+
err instanceof Error ? err.message : err
|
|
3169
|
+
);
|
|
3170
|
+
}
|
|
3171
|
+
})
|
|
3172
|
+
);
|
|
2894
3173
|
}
|
|
2895
3174
|
async function sweepSessionAgents(cwd, sessionId) {
|
|
2896
3175
|
let session;
|
|
@@ -2899,11 +3178,13 @@ async function sweepSessionAgents(cwd, sessionId) {
|
|
|
2899
3178
|
} catch {
|
|
2900
3179
|
return;
|
|
2901
3180
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3181
|
+
const candidates = session.agents.filter(
|
|
3182
|
+
(a) => a.status === "running" && !a.orphaned && a.pid !== void 0 && a.pidLstart
|
|
3183
|
+
);
|
|
3184
|
+
const probes = await Promise.all(
|
|
3185
|
+
candidates.map(async (a) => ({ agent: a, probe: await probePidLstart(a.pid, a.pidLstart) }))
|
|
3186
|
+
);
|
|
3187
|
+
for (const { agent, probe } of probes) {
|
|
2907
3188
|
if (probe === "live" || probe === "unknown") continue;
|
|
2908
3189
|
await markAgentOrphan(cwd, sessionId, agent.id, {
|
|
2909
3190
|
reason: probe === "recycled" ? "pid recycled (daemon-startup sweep)" : "process gone (daemon-startup sweep)",
|
|
@@ -2931,7 +3212,7 @@ async function sweepSessionAsks(cwd, sessionId) {
|
|
|
2931
3212
|
await updateMeta(cwd, sessionId, askId, { orphaned: true });
|
|
2932
3213
|
}
|
|
2933
3214
|
}
|
|
2934
|
-
var defaultPsRunner;
|
|
3215
|
+
var execFileAsync, defaultPsRunner;
|
|
2935
3216
|
var init_orphan_sweep = __esm({
|
|
2936
3217
|
"src/daemon/orphan-sweep.ts"() {
|
|
2937
3218
|
"use strict";
|
|
@@ -2943,12 +3224,16 @@ var init_orphan_sweep = __esm({
|
|
|
2943
3224
|
init_paths();
|
|
2944
3225
|
init_orphan_asks();
|
|
2945
3226
|
init_process();
|
|
2946
|
-
|
|
3227
|
+
execFileAsync = promisify(execFile2);
|
|
3228
|
+
defaultPsRunner = async (pid, env) => {
|
|
3229
|
+
const { stdout } = await execFileAsync("ps", ["-o", "lstart=", "-p", String(pid)], { env });
|
|
3230
|
+
return stdout.trim();
|
|
3231
|
+
};
|
|
2947
3232
|
}
|
|
2948
3233
|
});
|
|
2949
3234
|
|
|
2950
3235
|
// src/daemon/agent.ts
|
|
2951
|
-
import { readFileSync as
|
|
3236
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync8, existsSync as existsSync13, unlinkSync } from "fs";
|
|
2952
3237
|
import { execSync as execSync5 } from "child_process";
|
|
2953
3238
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2954
3239
|
import { resolve as resolve5, relative as relative2, dirname as dirname3, join as join12 } from "path";
|
|
@@ -2970,13 +3255,15 @@ function renderAgentSuffix(sessionId, instruction, contextDirRel) {
|
|
|
2970
3255
|
const templatePath = resolve5(import.meta.dirname, "../templates/agent-suffix.md");
|
|
2971
3256
|
let template;
|
|
2972
3257
|
try {
|
|
2973
|
-
template =
|
|
3258
|
+
template = readFileSync13(templatePath, "utf-8");
|
|
2974
3259
|
} catch {
|
|
2975
3260
|
template = `# Sisyphus Agent
|
|
2976
3261
|
Session: {{SESSION_ID}}
|
|
2977
3262
|
Task: {{INSTRUCTION}}`;
|
|
2978
3263
|
}
|
|
2979
|
-
return
|
|
3264
|
+
return injectHelp(
|
|
3265
|
+
template.replace(/\{\{SESSION_ID\}\}/g, sessionId).replace(/\{\{INSTRUCTION\}\}/g, instruction).replace(/\{\{CONTEXT_DIR\}\}/g, contextDirRel)
|
|
3266
|
+
);
|
|
2980
3267
|
}
|
|
2981
3268
|
function createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig) {
|
|
2982
3269
|
const base = `${promptsDir(cwd, sessionId)}/${agentId}-plugin`;
|
|
@@ -2994,10 +3281,10 @@ function createAgentPlugin(cwd, sessionId, agentId, agentType, agentConfig) {
|
|
|
2994
3281
|
if (agentConfig?.filePath && agentType && agentType !== "worker") {
|
|
2995
3282
|
const shortName = agentType.replace(/^sisyphus:/, "");
|
|
2996
3283
|
const subAgentDir = join12(dirname3(agentConfig.filePath), shortName);
|
|
2997
|
-
if (
|
|
3284
|
+
if (existsSync13(subAgentDir)) {
|
|
2998
3285
|
for (const f of readdirSync8(subAgentDir)) {
|
|
2999
3286
|
if (f.endsWith(".md") && f !== "CLAUDE.md") {
|
|
3000
|
-
writeFileSync9(`${base}/agents/${f}`, substituteEnvVars(
|
|
3287
|
+
writeFileSync9(`${base}/agents/${f}`, substituteEnvVars(readFileSync13(join12(subAgentDir, f), "utf-8")), "utf-8");
|
|
3001
3288
|
}
|
|
3002
3289
|
}
|
|
3003
3290
|
}
|
|
@@ -3094,7 +3381,24 @@ ${instruction}`);
|
|
|
3094
3381
|
const sessionIdFlag = claudeSessionId ? ` --session-id "${claudeSessionId}"` : "";
|
|
3095
3382
|
const promptFlag = agentConfig?.frontmatter.systemPrompt === "replace" ? "--system-prompt" : "--append-system-prompt";
|
|
3096
3383
|
const siblingSettingsPath = agentConfig?.filePath ? agentConfig.filePath.replace(/\.md$/, ".settings.json") : null;
|
|
3097
|
-
const
|
|
3384
|
+
const hasSibling = !!siblingSettingsPath && existsSync13(siblingSettingsPath);
|
|
3385
|
+
const agentDigestVerbs = digestSpinnerVerbs(cwd, sessionId);
|
|
3386
|
+
let resolvedSettingsPath = hasSibling ? siblingSettingsPath : null;
|
|
3387
|
+
if (agentDigestVerbs) {
|
|
3388
|
+
let base = {};
|
|
3389
|
+
if (hasSibling) {
|
|
3390
|
+
try {
|
|
3391
|
+
base = JSON.parse(readFileSync13(siblingSettingsPath, "utf-8"));
|
|
3392
|
+
} catch {
|
|
3393
|
+
base = {};
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
base.spinnerVerbs = { mode: "replace", verbs: agentDigestVerbs };
|
|
3397
|
+
const mergedSettingsOut = join12(promptsDir(cwd, sessionId), `${agentId}-settings.merged.json`);
|
|
3398
|
+
writeFileSync9(mergedSettingsOut, JSON.stringify(base, null, 2), "utf-8");
|
|
3399
|
+
resolvedSettingsPath = mergedSettingsOut;
|
|
3400
|
+
}
|
|
3401
|
+
const settingsFlag = resolvedSettingsPath ? ` --settings "${resolvedSettingsPath}"` : "";
|
|
3098
3402
|
mainCmd = `claude${permFlag} --effort ${effort}${modelFlag} --plugin-dir "${pluginPath}"${sessionIdFlag}${extraPluginFlags ? ` ${extraPluginFlags}` : ""}${settingsFlag} --name ${shellQuote(agentTitle)} ${promptFlag} "$(cat '${suffixFilePath}')" ${shellQuote(instruction)}`;
|
|
3099
3403
|
resumeArgs = `${permFlag.trimStart()} --effort ${effort}${modelFlag} --plugin-dir "${pluginPath}"${extraPluginFlags ? ` ${extraPluginFlags}` : ""}${settingsFlag}`;
|
|
3100
3404
|
}
|
|
@@ -3128,12 +3432,12 @@ async function spawnAgent(opts) {
|
|
|
3128
3432
|
try {
|
|
3129
3433
|
execSync5(`which ${fallbackCli}`, { stdio: "pipe", env: execEnv() });
|
|
3130
3434
|
} catch {
|
|
3131
|
-
throw new Error(`Neither ${cliToCheck} (model: ${agentConfig?.frontmatter.model}) nor ${fallbackCli} (fallback: ${fallback}) CLI found on PATH. Run \`sis admin doctor\` to diagnose.`);
|
|
3435
|
+
throw new Error(`Neither ${cliToCheck} (model: ${agentConfig?.frontmatter.model}) nor ${fallbackCli} (fallback: ${fallback}) CLI found on PATH. Run \`sis admin check doctor\` to diagnose.`);
|
|
3132
3436
|
}
|
|
3133
3437
|
if (agentConfig) agentConfig.frontmatter.model = fallback;
|
|
3134
3438
|
provider = fallbackProvider;
|
|
3135
3439
|
} else {
|
|
3136
|
-
throw new Error(`${cliToCheck} CLI not found on PATH. Run \`sis admin doctor\` to diagnose.`);
|
|
3440
|
+
throw new Error(`${cliToCheck} CLI not found on PATH. Run \`sis admin check doctor\` to diagnose.`);
|
|
3137
3441
|
}
|
|
3138
3442
|
}
|
|
3139
3443
|
const repo = opts.repo !== void 0 ? opts.repo : ".";
|
|
@@ -3250,6 +3554,12 @@ async function restartAgent(sessionId, cwd, agentId, windowId) {
|
|
|
3250
3554
|
await setAgentPid(cwd, sessionId, agentId, capturedRestart.pid, capturedRestart.lstart);
|
|
3251
3555
|
}
|
|
3252
3556
|
emitHistoryEvent(sessionId, "agent-restarted", { agentId, restartCount, originalSpawnedAt, previousStatus, claudeSessionId });
|
|
3557
|
+
resolveAgentOrphanAsks(cwd, sessionId, agentId, "respawn").catch((err) => {
|
|
3558
|
+
console.warn(
|
|
3559
|
+
`[sisyphus] resolveAgentOrphanAsks on restart failed for ${agentId}:`,
|
|
3560
|
+
err instanceof Error ? err.message : err
|
|
3561
|
+
);
|
|
3562
|
+
});
|
|
3253
3563
|
}
|
|
3254
3564
|
function nextReportNumber(cwd, sessionId, agentId) {
|
|
3255
3565
|
const dir = reportsDir(cwd, sessionId);
|
|
@@ -3283,9 +3593,9 @@ async function handleAgentReport(cwd, sessionId, agentId, content) {
|
|
|
3283
3593
|
}
|
|
3284
3594
|
function gcBgTasks(cwd, sessionId, agentId) {
|
|
3285
3595
|
const file = `${sessionDir(cwd, sessionId)}/runtime/bg-tasks/${agentId}.txt`;
|
|
3286
|
-
if (!
|
|
3596
|
+
if (!existsSync13(file)) return;
|
|
3287
3597
|
try {
|
|
3288
|
-
const leftover =
|
|
3598
|
+
const leftover = readFileSync13(file, "utf-8").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
3289
3599
|
if (leftover.length > 0) {
|
|
3290
3600
|
console.warn(`[bg-tasks] ${agentId} exited with ${leftover.length} untracked background task(s): ${leftover.join(", ")}`);
|
|
3291
3601
|
emitHistoryEvent(sessionId, "bg-tasks-leftover", { agentId, leftover });
|
|
@@ -3392,6 +3702,7 @@ var init_agent = __esm({
|
|
|
3392
3702
|
init_tmux();
|
|
3393
3703
|
init_colors();
|
|
3394
3704
|
init_paths();
|
|
3705
|
+
init_help_inject();
|
|
3395
3706
|
init_pane_registry();
|
|
3396
3707
|
init_pane_monitor();
|
|
3397
3708
|
init_summarize();
|
|
@@ -3401,6 +3712,7 @@ var init_agent = __esm({
|
|
|
3401
3712
|
init_shell();
|
|
3402
3713
|
init_spawn_helpers();
|
|
3403
3714
|
init_plugins();
|
|
3715
|
+
init_digest_verbs();
|
|
3404
3716
|
init_history();
|
|
3405
3717
|
init_orphan_asks();
|
|
3406
3718
|
init_orphan_sweep();
|
|
@@ -3558,7 +3870,7 @@ var init_companion_types = __esm({
|
|
|
3558
3870
|
});
|
|
3559
3871
|
|
|
3560
3872
|
// src/daemon/companion-memory.ts
|
|
3561
|
-
import { existsSync as
|
|
3873
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
3562
3874
|
import { dirname as dirname4, join as join13 } from "path";
|
|
3563
3875
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3564
3876
|
import { z } from "zod";
|
|
@@ -3591,10 +3903,10 @@ function fillDefaults(state) {
|
|
|
3591
3903
|
}
|
|
3592
3904
|
function loadMemoryStrict() {
|
|
3593
3905
|
const path = resolvedMemoryPath();
|
|
3594
|
-
if (!
|
|
3906
|
+
if (!existsSync14(path)) return defaultMemoryState();
|
|
3595
3907
|
let raw;
|
|
3596
3908
|
try {
|
|
3597
|
-
raw =
|
|
3909
|
+
raw = readFileSync14(path, "utf-8");
|
|
3598
3910
|
} catch (err) {
|
|
3599
3911
|
throw new MemoryStoreParseError(err);
|
|
3600
3912
|
}
|
|
@@ -4035,7 +4347,7 @@ var init_companion_memory = __esm({
|
|
|
4035
4347
|
});
|
|
4036
4348
|
|
|
4037
4349
|
// src/daemon/companion.ts
|
|
4038
|
-
import { existsSync as
|
|
4350
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync15, renameSync as renameSync4, writeFileSync as writeFileSync11 } from "fs";
|
|
4039
4351
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4040
4352
|
import { dirname as dirname5, join as join14 } from "path";
|
|
4041
4353
|
function welfordUpdate(stats, value) {
|
|
@@ -4059,12 +4371,12 @@ function zScore(value, stats, metric) {
|
|
|
4059
4371
|
}
|
|
4060
4372
|
function loadCompanion() {
|
|
4061
4373
|
const path = companionPath();
|
|
4062
|
-
if (!
|
|
4374
|
+
if (!existsSync15(path)) {
|
|
4063
4375
|
const state2 = createDefaultCompanion();
|
|
4064
4376
|
saveCompanion(state2);
|
|
4065
4377
|
return state2;
|
|
4066
4378
|
}
|
|
4067
|
-
const raw =
|
|
4379
|
+
const raw = readFileSync15(path, "utf-8");
|
|
4068
4380
|
const state = JSON.parse(raw);
|
|
4069
4381
|
return normalizeCompanion(state);
|
|
4070
4382
|
}
|
|
@@ -4245,7 +4557,7 @@ function computeMood(companion, session, signals) {
|
|
|
4245
4557
|
const now = Date.now();
|
|
4246
4558
|
const weekAgo = now - 7 * 24 * 36e5;
|
|
4247
4559
|
const weeklyCompletions = companion.recentCompletions.filter(
|
|
4248
|
-
(
|
|
4560
|
+
(ts) => new Date(ts).getTime() > weekAgo
|
|
4249
4561
|
).length;
|
|
4250
4562
|
const weeklyAvgDaily = weeklyCompletions / 7;
|
|
4251
4563
|
const weeklyZ = zScore(weeklyAvgDaily, baselines.sessionsPerDay, "sessionsPerDay");
|
|
@@ -4638,8 +4950,8 @@ var init_companion = __esm({
|
|
|
4638
4950
|
},
|
|
4639
4951
|
"overdrive": (c) => {
|
|
4640
4952
|
const dateCounts = {};
|
|
4641
|
-
for (const
|
|
4642
|
-
const date =
|
|
4953
|
+
for (const ts of c.recentCompletions) {
|
|
4954
|
+
const date = ts.slice(0, 10);
|
|
4643
4955
|
dateCounts[date] = (dateCounts[date] ?? 0) + 1;
|
|
4644
4956
|
}
|
|
4645
4957
|
return Object.values(dateCounts).some((count) => count >= 6);
|
|
@@ -5803,7 +6115,7 @@ var init_companion_render = __esm({
|
|
|
5803
6115
|
});
|
|
5804
6116
|
|
|
5805
6117
|
// src/daemon/companion-popup.ts
|
|
5806
|
-
import { writeFileSync as writeFileSync12, readFileSync as
|
|
6118
|
+
import { writeFileSync as writeFileSync12, readFileSync as readFileSync16, unlinkSync as unlinkSync2, existsSync as existsSync16 } from "fs";
|
|
5807
6119
|
import { tmpdir } from "os";
|
|
5808
6120
|
import { join as join15, resolve as resolve6 } from "path";
|
|
5809
6121
|
function wrapText(text, width) {
|
|
@@ -5847,7 +6159,7 @@ function showCommentaryPopupQueue(pages) {
|
|
|
5847
6159
|
if (contentHeight > maxContentHeight) maxContentHeight = contentHeight;
|
|
5848
6160
|
writeFileSync12(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
|
|
5849
6161
|
}
|
|
5850
|
-
const whipAvailable =
|
|
6162
|
+
const whipAvailable = existsSync16(WHIP_ANIMATION_PATH);
|
|
5851
6163
|
if (whipAvailable && maxContentHeight < WHIP_ANIMATION_ROWS + 2) {
|
|
5852
6164
|
maxContentHeight = WHIP_ANIMATION_ROWS + 2;
|
|
5853
6165
|
}
|
|
@@ -5921,7 +6233,7 @@ fi
|
|
|
5921
6233
|
}
|
|
5922
6234
|
let raw;
|
|
5923
6235
|
try {
|
|
5924
|
-
raw =
|
|
6236
|
+
raw = readFileSync16(POPUP_RESULT_PREFIX, "utf8").trim();
|
|
5925
6237
|
} catch {
|
|
5926
6238
|
return null;
|
|
5927
6239
|
} finally {
|
|
@@ -6074,6 +6386,7 @@ function stopMonitor() {
|
|
|
6074
6386
|
function trackSession(sessionId, cwd, tmuxSessionId, tmuxSessionName2) {
|
|
6075
6387
|
const existing = trackedSessions.get(sessionId);
|
|
6076
6388
|
trackedSessions.set(sessionId, { id: sessionId, cwd, tmuxSessionId, tmuxSessionName: tmuxSessionName2, windowId: existing ? existing.windowId : null });
|
|
6389
|
+
installStateWatcher(cwd, sessionId);
|
|
6077
6390
|
if (!activeTimers.has(sessionId)) {
|
|
6078
6391
|
try {
|
|
6079
6392
|
const session = getSession(cwd, sessionId);
|
|
@@ -6088,7 +6401,9 @@ function updateTrackedWindow(sessionId, windowId) {
|
|
|
6088
6401
|
entry.windowId = windowId;
|
|
6089
6402
|
}
|
|
6090
6403
|
function untrackSession(sessionId) {
|
|
6404
|
+
const entry = trackedSessions.get(sessionId);
|
|
6091
6405
|
trackedSessions.delete(sessionId);
|
|
6406
|
+
if (entry) uninstallStateWatcher(entry.cwd, sessionId);
|
|
6092
6407
|
}
|
|
6093
6408
|
function hasRecentSessionActivity(s, recentCutoffMs) {
|
|
6094
6409
|
for (const agent of s.agents) {
|
|
@@ -6112,13 +6427,16 @@ async function pollAllSessions() {
|
|
|
6112
6427
|
const increment = elapsed > threshold ? storedPollIntervalMs : elapsed;
|
|
6113
6428
|
lastPollTime = now;
|
|
6114
6429
|
const pollSessionCache = /* @__PURE__ */ new Map();
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6430
|
+
const panesByWindow = listAllPanesByWindow();
|
|
6431
|
+
await Promise.all(
|
|
6432
|
+
[...trackedSessions.values()].filter(({ windowId }) => !!windowId).map(
|
|
6433
|
+
({ id: sessionId, cwd, windowId }) => pollSession(sessionId, cwd, windowId, increment, panesByWindow, pollSessionCache).catch((err) => {
|
|
6434
|
+
console.error(`[sisyphus] pollSession error for ${sessionId}:`, err);
|
|
6435
|
+
})
|
|
6436
|
+
)
|
|
6437
|
+
);
|
|
6120
6438
|
try {
|
|
6121
|
-
onDotsUpdate?.();
|
|
6439
|
+
onDotsUpdate?.(panesByWindow);
|
|
6122
6440
|
} catch {
|
|
6123
6441
|
}
|
|
6124
6442
|
try {
|
|
@@ -6269,11 +6587,12 @@ async function pollAllSessions() {
|
|
|
6269
6587
|
} catch {
|
|
6270
6588
|
}
|
|
6271
6589
|
}
|
|
6272
|
-
async function pollSession(sessionId, cwd, windowId, increment,
|
|
6590
|
+
async function pollSession(sessionId, cwd, windowId, increment, panesByWindow, sessionCache2) {
|
|
6591
|
+
const panesForWindow = () => panesByWindow.get(windowId) ?? [];
|
|
6273
6592
|
let session;
|
|
6274
6593
|
try {
|
|
6275
6594
|
session = getSession(cwd, sessionId);
|
|
6276
|
-
|
|
6595
|
+
sessionCache2?.set(sessionId, session);
|
|
6277
6596
|
} catch (err) {
|
|
6278
6597
|
console.error(`[sisyphus] Failed to read state for session ${sessionId}:`, err);
|
|
6279
6598
|
return;
|
|
@@ -6281,8 +6600,7 @@ async function pollSession(sessionId, cwd, windowId, increment, sessionCache) {
|
|
|
6281
6600
|
if (session.status === "completed") {
|
|
6282
6601
|
const orchPaneId2 = getOrchestratorPaneId(sessionId);
|
|
6283
6602
|
if (orchPaneId2) {
|
|
6284
|
-
const
|
|
6285
|
-
const livePaneIds2 = new Set(livePanes2.map((p) => p.paneId));
|
|
6603
|
+
const livePaneIds2 = new Set(panesForWindow().map((p) => p.paneId));
|
|
6286
6604
|
if (!livePaneIds2.has(orchPaneId2)) {
|
|
6287
6605
|
cleanupSessionMaps(sessionId);
|
|
6288
6606
|
untrackSession(sessionId);
|
|
@@ -6295,7 +6613,7 @@ async function pollSession(sessionId, cwd, windowId, increment, sessionCache) {
|
|
|
6295
6613
|
return;
|
|
6296
6614
|
}
|
|
6297
6615
|
if (session.status !== "active") return;
|
|
6298
|
-
const livePanes =
|
|
6616
|
+
const livePanes = panesForWindow();
|
|
6299
6617
|
if (livePanes.length === 0) {
|
|
6300
6618
|
if (respawningSessions.has(sessionId)) return;
|
|
6301
6619
|
const tracked = trackedSessions.get(sessionId);
|
|
@@ -6346,9 +6664,9 @@ async function pollSession(sessionId, cwd, windowId, increment, sessionCache) {
|
|
|
6346
6664
|
if (orchPaneId && !livePaneIds.has(orchPaneId) && !respawningSessions.has(sessionId)) {
|
|
6347
6665
|
const cycleActiveMs = flushCycleTimer(sessionId, session.orchestratorCycles.length);
|
|
6348
6666
|
await completeOrchestratorCycle(cwd, sessionId, void 0, void 0, cycleActiveMs);
|
|
6349
|
-
await orphanOrchestrator(cwd, sessionId, "orchestrator pane vanished without yield", "orchestrator-gone");
|
|
6350
6667
|
const runningAgents = session.agents.filter((a) => a.status === "running");
|
|
6351
6668
|
if (runningAgents.length === 0) {
|
|
6669
|
+
await orphanOrchestrator(cwd, sessionId, "orchestrator pane vanished without yield", "orchestrator-gone");
|
|
6352
6670
|
await flushTimers(sessionId);
|
|
6353
6671
|
await updateSessionStatus(cwd, sessionId, "paused");
|
|
6354
6672
|
console.log(`[sisyphus] Session ${sessionId} paused: orchestrator pane disappeared`);
|
|
@@ -6392,7 +6710,7 @@ var init_pane_monitor = __esm({
|
|
|
6392
6710
|
});
|
|
6393
6711
|
|
|
6394
6712
|
// src/daemon/mode-notify.ts
|
|
6395
|
-
import { existsSync as
|
|
6713
|
+
import { existsSync as existsSync17 } from "fs";
|
|
6396
6714
|
import { ulid as ulid2 } from "ulid";
|
|
6397
6715
|
function capitalize(s) {
|
|
6398
6716
|
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
@@ -6414,7 +6732,7 @@ function findOpenModeTransitionAsk(cwd, sessionId) {
|
|
|
6414
6732
|
if (meta.modeTransition !== true) continue;
|
|
6415
6733
|
if (meta.status === "answered") continue;
|
|
6416
6734
|
if (meta.orphaned === true) continue;
|
|
6417
|
-
if (
|
|
6735
|
+
if (existsSync17(askOutputPath(cwd, sessionId, askId))) continue;
|
|
6418
6736
|
return askId;
|
|
6419
6737
|
}
|
|
6420
6738
|
return null;
|
|
@@ -6478,13 +6796,14 @@ async function emitModeTransitionNotify(cwd, sessionId, prevMode, nextMode, prev
|
|
|
6478
6796
|
kind: "notify",
|
|
6479
6797
|
options: [{ id: "ack", label: "Acknowledged" }]
|
|
6480
6798
|
};
|
|
6799
|
+
const deckSource = {
|
|
6800
|
+
...sessionName !== void 0 ? { sessionName } : {},
|
|
6801
|
+
askedBy: ORCHESTRATOR_ASKED_BY,
|
|
6802
|
+
modeChain: chain
|
|
6803
|
+
};
|
|
6481
6804
|
const deck = {
|
|
6482
6805
|
title: deckTitle,
|
|
6483
|
-
source:
|
|
6484
|
-
...sessionName !== void 0 ? { sessionName } : {},
|
|
6485
|
-
askedBy: ORCHESTRATOR_ASKED_BY,
|
|
6486
|
-
modeChain: chain
|
|
6487
|
-
},
|
|
6806
|
+
source: deckSource,
|
|
6488
6807
|
interactions: [interaction]
|
|
6489
6808
|
};
|
|
6490
6809
|
try {
|
|
@@ -6528,14 +6847,14 @@ var init_mode_notify = __esm({
|
|
|
6528
6847
|
});
|
|
6529
6848
|
|
|
6530
6849
|
// src/daemon/orchestrator.ts
|
|
6531
|
-
import { existsSync as
|
|
6850
|
+
import { existsSync as existsSync18, readdirSync as readdirSync10, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
|
|
6532
6851
|
import { execSync as execSync6 } from "child_process";
|
|
6533
6852
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6534
6853
|
import { resolve as resolve7, join as join16, relative as relative3 } from "path";
|
|
6535
6854
|
function detectRepos(cwd) {
|
|
6536
6855
|
const config = loadConfig(cwd);
|
|
6537
6856
|
const repos = [];
|
|
6538
|
-
if (
|
|
6857
|
+
if (existsSync18(join16(cwd, ".git"))) {
|
|
6539
6858
|
try {
|
|
6540
6859
|
repos.push(getRepoInfo(cwd, "."));
|
|
6541
6860
|
} catch {
|
|
@@ -6547,7 +6866,7 @@ function detectRepos(cwd) {
|
|
|
6547
6866
|
if (!entry.isDirectory()) continue;
|
|
6548
6867
|
if (entry.name.startsWith(".")) continue;
|
|
6549
6868
|
const childPath = join16(cwd, entry.name);
|
|
6550
|
-
if (
|
|
6869
|
+
if (existsSync18(join16(childPath, ".git"))) {
|
|
6551
6870
|
try {
|
|
6552
6871
|
repos.push(getRepoInfo(childPath, entry.name));
|
|
6553
6872
|
} catch {
|
|
@@ -6585,40 +6904,44 @@ function resolveOrchestratorSettings(cwd, sessionId) {
|
|
|
6585
6904
|
const bundled = resolve7(import.meta.dirname, "../templates/orchestrator-settings.json");
|
|
6586
6905
|
const projectSettings = projectOrchestratorSettingsPath(cwd);
|
|
6587
6906
|
const userSettings = userOrchestratorSettingsPath();
|
|
6588
|
-
const hasProject =
|
|
6589
|
-
const hasUser =
|
|
6590
|
-
|
|
6907
|
+
const hasProject = existsSync18(projectSettings);
|
|
6908
|
+
const hasUser = existsSync18(userSettings);
|
|
6909
|
+
const digestVerbs = digestSpinnerVerbs(cwd, sessionId);
|
|
6910
|
+
if (!hasProject && !hasUser && !digestVerbs) return bundled;
|
|
6591
6911
|
let merged = {};
|
|
6592
6912
|
for (const path of [bundled, hasUser ? userSettings : null, hasProject ? projectSettings : null]) {
|
|
6593
|
-
if (!path || !
|
|
6913
|
+
if (!path || !existsSync18(path)) continue;
|
|
6594
6914
|
try {
|
|
6595
|
-
const parsed = JSON.parse(
|
|
6915
|
+
const parsed = JSON.parse(readFileSync17(path, "utf-8"));
|
|
6596
6916
|
merged = { ...merged, ...parsed };
|
|
6597
6917
|
} catch (err) {
|
|
6598
6918
|
console.warn(`[sisyphus] Failed to parse settings layer ${path}: ${err instanceof Error ? err.message : err}`);
|
|
6599
6919
|
}
|
|
6600
6920
|
}
|
|
6921
|
+
if (digestVerbs) {
|
|
6922
|
+
merged.spinnerVerbs = { mode: "replace", verbs: digestVerbs };
|
|
6923
|
+
}
|
|
6601
6924
|
const out = join16(promptsDir(cwd, sessionId), "orchestrator-settings.merged.json");
|
|
6602
6925
|
writeFileSync13(out, JSON.stringify(merged, null, 2), "utf-8");
|
|
6603
6926
|
return out;
|
|
6604
6927
|
}
|
|
6605
6928
|
function loadOrchestratorPrompt(cwd, sessionId, mode) {
|
|
6606
6929
|
const projectPath = projectOrchestratorPromptPath(cwd);
|
|
6607
|
-
if (
|
|
6608
|
-
return
|
|
6930
|
+
if (existsSync18(projectPath)) {
|
|
6931
|
+
return readFileSync17(projectPath, "utf-8");
|
|
6609
6932
|
}
|
|
6610
6933
|
const userPath = userOrchestratorPromptPath();
|
|
6611
|
-
if (
|
|
6612
|
-
return
|
|
6934
|
+
if (existsSync18(userPath)) {
|
|
6935
|
+
return readFileSync17(userPath, "utf-8");
|
|
6613
6936
|
}
|
|
6614
6937
|
const basePath = resolve7(import.meta.dirname, "../templates/orchestrator-base.md");
|
|
6615
|
-
const base =
|
|
6938
|
+
const base = readFileSync17(basePath, "utf-8");
|
|
6616
6939
|
const modes = discoverOrchestratorModes(cwd);
|
|
6617
6940
|
const selected = modes.find((m) => m.name === mode) ?? modes.find((m) => m.name === "discovery");
|
|
6618
6941
|
if (!selected) {
|
|
6619
6942
|
throw new Error(`Unknown orchestrator mode '${mode}' and no fallback found. Available: ${modes.map((m) => m.name).join(", ")}`);
|
|
6620
6943
|
}
|
|
6621
|
-
const modeContent =
|
|
6944
|
+
const modeContent = readFileSync17(selected.filePath, "utf-8");
|
|
6622
6945
|
const modeBody = extractAgentBody(modeContent);
|
|
6623
6946
|
return base + "\n\n" + modeBody;
|
|
6624
6947
|
}
|
|
@@ -6636,7 +6959,7 @@ function buildCompletionContent(session) {
|
|
|
6636
6959
|
lines.push("");
|
|
6637
6960
|
}
|
|
6638
6961
|
const logsDirPath = logsDir(session.cwd, session.id);
|
|
6639
|
-
if (
|
|
6962
|
+
if (existsSync18(logsDirPath)) {
|
|
6640
6963
|
const logFiles = readdirSync10(logsDirPath).filter((f) => f.startsWith("cycle-") && f.endsWith(".md")).sort();
|
|
6641
6964
|
if (logFiles.length > 0) {
|
|
6642
6965
|
lines.push("### Cycle Logs\n");
|
|
@@ -6661,7 +6984,7 @@ function buildCompletionContent(session) {
|
|
|
6661
6984
|
while (j < entries.length && entries[j].idle && entries[j].mode === e.mode) j++;
|
|
6662
6985
|
const runEntries = entries.slice(i, j);
|
|
6663
6986
|
if (runEntries.length === 1) {
|
|
6664
|
-
const content =
|
|
6987
|
+
const content = readFileSync17(join16(logsDirPath, e.file), "utf-8").trim();
|
|
6665
6988
|
if (content) {
|
|
6666
6989
|
lines.push(content);
|
|
6667
6990
|
lines.push("");
|
|
@@ -6677,7 +7000,7 @@ function buildCompletionContent(session) {
|
|
|
6677
7000
|
}
|
|
6678
7001
|
i = j;
|
|
6679
7002
|
} else {
|
|
6680
|
-
const content =
|
|
7003
|
+
const content = readFileSync17(join16(logsDirPath, e.file), "utf-8").trim();
|
|
6681
7004
|
if (content) {
|
|
6682
7005
|
lines.push(content);
|
|
6683
7006
|
lines.push("");
|
|
@@ -6688,7 +7011,7 @@ function buildCompletionContent(session) {
|
|
|
6688
7011
|
}
|
|
6689
7012
|
}
|
|
6690
7013
|
const reportsDirPath = reportsDir(session.cwd, session.id);
|
|
6691
|
-
if (
|
|
7014
|
+
if (existsSync18(reportsDirPath)) {
|
|
6692
7015
|
const reportFiles = readdirSync10(reportsDirPath).filter((f) => f.endsWith(".md"));
|
|
6693
7016
|
if (reportFiles.length > 0) {
|
|
6694
7017
|
lines.push("### Detailed Reports\n");
|
|
@@ -6714,7 +7037,7 @@ ${session.context}
|
|
|
6714
7037
|
}
|
|
6715
7038
|
} else {
|
|
6716
7039
|
let ctxFiles = [];
|
|
6717
|
-
if (
|
|
7040
|
+
if (existsSync18(ctxDir)) {
|
|
6718
7041
|
ctxFiles = readdirSync10(ctxDir).filter((f) => f !== "CLAUDE.md");
|
|
6719
7042
|
}
|
|
6720
7043
|
if (ctxFiles.length > 0) {
|
|
@@ -6756,10 +7079,10 @@ ${agentLines}
|
|
|
6756
7079
|
}
|
|
6757
7080
|
}
|
|
6758
7081
|
const strategyFile = strategyPath(session.cwd, session.id);
|
|
6759
|
-
const strategyRef =
|
|
6760
|
-
const roadmapRef =
|
|
7082
|
+
const strategyRef = existsSync18(strategyFile) ? `@${relative3(session.cwd, strategyFile)}` : "(empty)";
|
|
7083
|
+
const roadmapRef = existsSync18(roadmapFile) ? `@${relative3(session.cwd, roadmapFile)}` : "(empty)";
|
|
6761
7084
|
const digestFile = digestPath(session.cwd, session.id);
|
|
6762
|
-
const digestRef =
|
|
7085
|
+
const digestRef = existsSync18(digestFile) ? `@${relative3(session.cwd, digestFile)}` : "(not yet created)";
|
|
6763
7086
|
const repos = detectRepos(session.cwd);
|
|
6764
7087
|
let repositoriesSection = "\n\n## Repositories\n";
|
|
6765
7088
|
if (repos.length === 0) {
|
|
@@ -6786,7 +7109,7 @@ ${agentLines}
|
|
|
6786
7109
|
}
|
|
6787
7110
|
}
|
|
6788
7111
|
const goalFile = goalPath(session.cwd, session.id);
|
|
6789
|
-
const goalContent =
|
|
7112
|
+
const goalContent = existsSync18(goalFile) ? readFileSync17(goalFile, "utf-8").trim() : session.task;
|
|
6790
7113
|
const modeContent = modeContentBuilders[mode]?.(session) ?? "";
|
|
6791
7114
|
return `## Goal
|
|
6792
7115
|
|
|
@@ -6813,7 +7136,7 @@ async function spawnOrchestrator(sessionId, cwd, windowId, message, forceMode) {
|
|
|
6813
7136
|
try {
|
|
6814
7137
|
execSync6("which claude", { stdio: "pipe", env: EXEC_ENV });
|
|
6815
7138
|
} catch {
|
|
6816
|
-
throw new Error("Claude CLI not found on PATH. Run `sis admin doctor` to diagnose.");
|
|
7139
|
+
throw new Error("Claude CLI not found on PATH. Run `sis admin check doctor` to diagnose.");
|
|
6817
7140
|
}
|
|
6818
7141
|
const session = getSession(cwd, sessionId);
|
|
6819
7142
|
const lastCycle = [...session.orchestratorCycles].reverse().find((c) => c.completedAt);
|
|
@@ -6823,9 +7146,9 @@ async function spawnOrchestrator(sessionId, cwd, windowId, message, forceMode) {
|
|
|
6823
7146
|
const agentPluginPath = resolve7(import.meta.dirname, "../templates/agent-plugin");
|
|
6824
7147
|
const agentTypes = discoverAgentTypes(agentPluginPath, session.cwd).filter((t2) => t2.source === "bundled");
|
|
6825
7148
|
const agentTypeLines = agentTypes.length > 0 ? agentTypes.map((t2) => {
|
|
6826
|
-
const
|
|
7149
|
+
const tag = t2.model ? `(agent, ${t2.model})` : "(agent)";
|
|
6827
7150
|
const desc = t2.description ? ` \u2014 ${t2.description}` : "";
|
|
6828
|
-
return `- \`${t2.qualifiedName}
|
|
7151
|
+
return `- \`${t2.qualifiedName}\` ${tag}${desc}`;
|
|
6829
7152
|
}).join("\n") : " (none)";
|
|
6830
7153
|
const sesDir = sessionDir(cwd, sessionId);
|
|
6831
7154
|
const substituteEnvVars = (text) => text.replace(/\$SISYPHUS_SESSION_DIR/g, sesDir).replace(/\$SISYPHUS_SESSION_ID/g, sessionId);
|
|
@@ -6835,7 +7158,9 @@ async function spawnOrchestrator(sessionId, cwd, windowId, message, forceMode) {
|
|
|
6835
7158
|
return `- \`${m.name}\`${desc}`;
|
|
6836
7159
|
}).join("\n");
|
|
6837
7160
|
const substitutedPrompt = substituteEnvVars(
|
|
6838
|
-
|
|
7161
|
+
injectHelp(
|
|
7162
|
+
basePrompt.replace("{{AGENT_TYPES}}", agentTypeLines).replace("{{ORCHESTRATOR_MODES}}", modeLines)
|
|
7163
|
+
)
|
|
6839
7164
|
);
|
|
6840
7165
|
const sessionEffort = session.effort != null ? session.effort : "high";
|
|
6841
7166
|
const systemPrompt = renderEffortMarkers(substitutedPrompt, sessionEffort);
|
|
@@ -7003,8 +7328,10 @@ var init_orchestrator = __esm({
|
|
|
7003
7328
|
"src/daemon/orchestrator.ts"() {
|
|
7004
7329
|
"use strict";
|
|
7005
7330
|
init_spawn_helpers();
|
|
7331
|
+
init_help_inject();
|
|
7006
7332
|
init_paths();
|
|
7007
7333
|
init_exec();
|
|
7334
|
+
init_digest_verbs();
|
|
7008
7335
|
init_types();
|
|
7009
7336
|
init_config();
|
|
7010
7337
|
init_shell();
|
|
@@ -7029,7 +7356,7 @@ var init_orchestrator = __esm({
|
|
|
7029
7356
|
});
|
|
7030
7357
|
|
|
7031
7358
|
// src/daemon/status-dots.ts
|
|
7032
|
-
import { readFileSync as
|
|
7359
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
7033
7360
|
function renderDots(dots) {
|
|
7034
7361
|
const sorted = [...dots].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
7035
7362
|
return sorted.map((d) => {
|
|
@@ -7040,7 +7367,7 @@ function renderDots(dots) {
|
|
|
7040
7367
|
function readClaudeState(paneId) {
|
|
7041
7368
|
const numericId = paneId.replace("%", "");
|
|
7042
7369
|
try {
|
|
7043
|
-
const content =
|
|
7370
|
+
const content = readFileSync18(`${CLAUDE_STATE_DIR}/${numericId}`, "utf-8").trim();
|
|
7044
7371
|
if (content === "idle" || content === "processing" || content === "stopped") {
|
|
7045
7372
|
return content;
|
|
7046
7373
|
}
|
|
@@ -7099,7 +7426,7 @@ function getDashboardWindowId(cwd) {
|
|
|
7099
7426
|
dashboardWindowCache.set(cwd, { windowId, checkedAt: now });
|
|
7100
7427
|
return windowId;
|
|
7101
7428
|
}
|
|
7102
|
-
function recomputeDots() {
|
|
7429
|
+
function recomputeDots(panesByWindow) {
|
|
7103
7430
|
if (!getTrackedEntries) return;
|
|
7104
7431
|
pruneCompleted();
|
|
7105
7432
|
sisyphusPhases.clear();
|
|
@@ -7129,7 +7456,7 @@ function recomputeDots() {
|
|
|
7129
7456
|
seenIds.add(sessionId);
|
|
7130
7457
|
try {
|
|
7131
7458
|
const session = getSession(cwd, sessionId);
|
|
7132
|
-
const livePanes = listPanes(windowId);
|
|
7459
|
+
const livePanes = panesByWindow?.get(windowId) ?? listPanes(windowId);
|
|
7133
7460
|
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
7134
7461
|
const phase = detectPhase(session, livePaneIds);
|
|
7135
7462
|
dots.push({ phase, createdAt: session.createdAt });
|
|
@@ -7147,11 +7474,14 @@ function recomputeDots() {
|
|
|
7147
7474
|
const dashboardWindowId = getDashboardWindowId(cwd);
|
|
7148
7475
|
if (dashboardWindowId) {
|
|
7149
7476
|
const rendered = dots.length > 0 ? " " + renderDots(dots) : "";
|
|
7150
|
-
|
|
7477
|
+
if (lastDotsByWindow.get(dashboardWindowId) !== rendered) {
|
|
7478
|
+
setWindowOption(dashboardWindowId, "@sisyphus_dots", rendered);
|
|
7479
|
+
lastDotsByWindow.set(dashboardWindowId, rendered);
|
|
7480
|
+
}
|
|
7151
7481
|
}
|
|
7152
7482
|
}
|
|
7153
7483
|
}
|
|
7154
|
-
var CLAUDE_STATE_DIR, DOT_MAP, sisyphusPhases, getTrackedEntries, COMPLETED_TTL_MS, completedSessions, dashboardWindowCache, CACHE_TTL_MS;
|
|
7484
|
+
var CLAUDE_STATE_DIR, DOT_MAP, sisyphusPhases, getTrackedEntries, COMPLETED_TTL_MS, completedSessions, dashboardWindowCache, CACHE_TTL_MS, lastDotsByWindow;
|
|
7155
7485
|
var init_status_dots = __esm({
|
|
7156
7486
|
"src/daemon/status-dots.ts"() {
|
|
7157
7487
|
"use strict";
|
|
@@ -7179,6 +7509,7 @@ var init_status_dots = __esm({
|
|
|
7179
7509
|
completedSessions = /* @__PURE__ */ new Map();
|
|
7180
7510
|
dashboardWindowCache = /* @__PURE__ */ new Map();
|
|
7181
7511
|
CACHE_TTL_MS = 3e4;
|
|
7512
|
+
lastDotsByWindow = /* @__PURE__ */ new Map();
|
|
7182
7513
|
}
|
|
7183
7514
|
});
|
|
7184
7515
|
|
|
@@ -7220,9 +7551,9 @@ var init_manifest = __esm({
|
|
|
7220
7551
|
});
|
|
7221
7552
|
|
|
7222
7553
|
// src/shared/session-export.ts
|
|
7223
|
-
import { execFile as
|
|
7224
|
-
import { promisify } from "util";
|
|
7225
|
-
import { existsSync as
|
|
7554
|
+
import { execFile as execFile3 } from "child_process";
|
|
7555
|
+
import { promisify as promisify2 } from "util";
|
|
7556
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, mkdirSync as mkdirSync10, symlinkSync, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
7226
7557
|
import { homedir as homedir6 } from "os";
|
|
7227
7558
|
import { join as join17 } from "path";
|
|
7228
7559
|
function sanitizeName(name) {
|
|
@@ -7234,7 +7565,7 @@ function buildOutputPath(label, dir) {
|
|
|
7234
7565
|
const base = `sisyphus-${label}-${date}`;
|
|
7235
7566
|
let candidate = join17(dir, `${base}.zip`);
|
|
7236
7567
|
let counter = 1;
|
|
7237
|
-
while (
|
|
7568
|
+
while (existsSync19(candidate)) {
|
|
7238
7569
|
counter++;
|
|
7239
7570
|
candidate = join17(dir, `${base}-${counter}.zip`);
|
|
7240
7571
|
}
|
|
@@ -7297,16 +7628,16 @@ async function exportSessionToZip(sessionId, cwd, options) {
|
|
|
7297
7628
|
const reveal = options?.reveal ?? true;
|
|
7298
7629
|
const sessDir = sessionDir(cwd, sessionId);
|
|
7299
7630
|
const histDir = historySessionDir(sessionId);
|
|
7300
|
-
const sessExists =
|
|
7301
|
-
const histExists =
|
|
7631
|
+
const sessExists = existsSync19(sessDir);
|
|
7632
|
+
const histExists = existsSync19(histDir);
|
|
7302
7633
|
if (!sessExists && !histExists) {
|
|
7303
7634
|
throw new Error(`No data found for session ${sessionId}`);
|
|
7304
7635
|
}
|
|
7305
7636
|
let label = sessionId.slice(0, 8);
|
|
7306
7637
|
const stPath = statePath(cwd, sessionId);
|
|
7307
|
-
if (
|
|
7638
|
+
if (existsSync19(stPath)) {
|
|
7308
7639
|
try {
|
|
7309
|
-
const state = JSON.parse(
|
|
7640
|
+
const state = JSON.parse(readFileSync19(stPath, "utf-8"));
|
|
7310
7641
|
if (state.name) {
|
|
7311
7642
|
label = sanitizeName(state.name);
|
|
7312
7643
|
}
|
|
@@ -7326,24 +7657,24 @@ async function exportSessionToZip(sessionId, cwd, options) {
|
|
|
7326
7657
|
symlinkSync(histDir, join17(tmpDir, "history"));
|
|
7327
7658
|
}
|
|
7328
7659
|
const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
|
|
7329
|
-
await
|
|
7660
|
+
await execFileAsync2("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
|
|
7330
7661
|
} finally {
|
|
7331
7662
|
rmSync5(tmpDir, { recursive: true, force: true });
|
|
7332
7663
|
}
|
|
7333
7664
|
if (reveal) {
|
|
7334
7665
|
try {
|
|
7335
|
-
await
|
|
7666
|
+
await execFileAsync2("open", ["-R", outputPath]);
|
|
7336
7667
|
} catch {
|
|
7337
7668
|
}
|
|
7338
7669
|
}
|
|
7339
7670
|
return outputPath;
|
|
7340
7671
|
}
|
|
7341
|
-
var
|
|
7672
|
+
var execFileAsync2;
|
|
7342
7673
|
var init_session_export = __esm({
|
|
7343
7674
|
"src/shared/session-export.ts"() {
|
|
7344
7675
|
"use strict";
|
|
7345
7676
|
init_paths();
|
|
7346
|
-
|
|
7677
|
+
execFileAsync2 = promisify2(execFile3);
|
|
7347
7678
|
}
|
|
7348
7679
|
});
|
|
7349
7680
|
|
|
@@ -7427,12 +7758,12 @@ var init_uploader = __esm({
|
|
|
7427
7758
|
});
|
|
7428
7759
|
|
|
7429
7760
|
// src/shared/version.ts
|
|
7430
|
-
import { readFileSync as
|
|
7761
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
7431
7762
|
import { resolve as resolve8 } from "path";
|
|
7432
7763
|
function readSisyphusVersion() {
|
|
7433
7764
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
7434
7765
|
try {
|
|
7435
|
-
const raw =
|
|
7766
|
+
const raw = readFileSync20(resolve8(import.meta.dirname, rel), "utf-8");
|
|
7436
7767
|
const pkg = JSON.parse(raw);
|
|
7437
7768
|
if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
|
|
7438
7769
|
} catch {
|
|
@@ -7470,14 +7801,19 @@ function formatDuration2(startOrMs, endIso) {
|
|
|
7470
7801
|
if (minutes > 0) return `${minutes}m${seconds}s`;
|
|
7471
7802
|
return `${seconds}s`;
|
|
7472
7803
|
}
|
|
7804
|
+
function colorEnabled() {
|
|
7805
|
+
if (process.env["FORCE_COLOR"] === "1") return true;
|
|
7806
|
+
if (process.env["NO_COLOR"] !== void 0) return false;
|
|
7807
|
+
if (process.env["TERM"] === "dumb") return false;
|
|
7808
|
+
return process.stdout.isTTY === true;
|
|
7809
|
+
}
|
|
7473
7810
|
function wrap(open, close = "\x1B[0m") {
|
|
7474
|
-
return (s) =>
|
|
7811
|
+
return (s) => colorEnabled() ? `${open}${s}${close}` : s;
|
|
7475
7812
|
}
|
|
7476
|
-
var
|
|
7813
|
+
var bold, dim, red, green, yellow, cyan, gray, magenta, white;
|
|
7477
7814
|
var init_format = __esm({
|
|
7478
7815
|
"src/shared/format.ts"() {
|
|
7479
7816
|
"use strict";
|
|
7480
|
-
COLOR_ENABLED = process.env["FORCE_COLOR"] === "1" || process.stdout.isTTY === true && process.env["NO_COLOR"] === void 0 && process.env["TERM"] !== "dumb";
|
|
7481
7817
|
bold = wrap("\x1B[1m");
|
|
7482
7818
|
dim = wrap("\x1B[2m");
|
|
7483
7819
|
red = wrap("\x1B[31m");
|
|
@@ -7491,7 +7827,7 @@ var init_format = __esm({
|
|
|
7491
7827
|
});
|
|
7492
7828
|
|
|
7493
7829
|
// src/cli/deploy/creds.ts
|
|
7494
|
-
import { chmodSync, existsSync as
|
|
7830
|
+
import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync21 } from "fs";
|
|
7495
7831
|
import { createInterface } from "readline";
|
|
7496
7832
|
function isValidProvider(value) {
|
|
7497
7833
|
return PROVIDERS.includes(value);
|
|
@@ -7532,12 +7868,12 @@ var init_pricing = __esm({
|
|
|
7532
7868
|
});
|
|
7533
7869
|
|
|
7534
7870
|
// src/cli/deploy/runtime.ts
|
|
7535
|
-
import { existsSync as
|
|
7871
|
+
import { existsSync as existsSync21, readFileSync as readFileSync22, unlinkSync as unlinkSync3 } from "fs";
|
|
7536
7872
|
function readRuntimeState(provider) {
|
|
7537
7873
|
const path = deployRuntimePath(provider);
|
|
7538
|
-
if (!
|
|
7874
|
+
if (!existsSync21(path)) return null;
|
|
7539
7875
|
try {
|
|
7540
|
-
return JSON.parse(
|
|
7876
|
+
return JSON.parse(readFileSync22(path, "utf-8"));
|
|
7541
7877
|
} catch {
|
|
7542
7878
|
return null;
|
|
7543
7879
|
}
|
|
@@ -7559,15 +7895,15 @@ var init_tailnet = __esm({
|
|
|
7559
7895
|
});
|
|
7560
7896
|
|
|
7561
7897
|
// src/cli/deploy/templates.ts
|
|
7562
|
-
import { existsSync as
|
|
7898
|
+
import { existsSync as existsSync22 } from "fs";
|
|
7563
7899
|
import { dirname as dirname6, resolve as resolve9 } from "path";
|
|
7564
7900
|
import { fileURLToPath } from "url";
|
|
7565
7901
|
function deployRoot() {
|
|
7566
7902
|
const here = dirname6(fileURLToPath(import.meta.url));
|
|
7567
7903
|
const bundled = resolve9(here, "..", "deploy");
|
|
7568
|
-
if (
|
|
7904
|
+
if (existsSync22(bundled)) return bundled;
|
|
7569
7905
|
const sourceRoot = resolve9(here, "..", "..", "..", "deploy");
|
|
7570
|
-
if (
|
|
7906
|
+
if (existsSync22(sourceRoot)) return sourceRoot;
|
|
7571
7907
|
throw new Error(
|
|
7572
7908
|
`Could not locate deploy/ templates. Looked at:
|
|
7573
7909
|
${bundled}
|
|
@@ -7596,7 +7932,7 @@ var init_tailscale = __esm({
|
|
|
7596
7932
|
|
|
7597
7933
|
// src/cli/deploy/runner.ts
|
|
7598
7934
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
7599
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
7935
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync23 } from "fs";
|
|
7600
7936
|
function readOutputs(provider) {
|
|
7601
7937
|
const result = spawnSync("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
|
|
7602
7938
|
cwd: providerModuleDir(provider),
|
|
@@ -7622,7 +7958,7 @@ function readOutputs(provider) {
|
|
|
7622
7958
|
}
|
|
7623
7959
|
}
|
|
7624
7960
|
function isProvisioned(provider) {
|
|
7625
|
-
if (!
|
|
7961
|
+
if (!existsSync23(deployStatePath(provider))) return false;
|
|
7626
7962
|
return readOutputs(provider) !== null;
|
|
7627
7963
|
}
|
|
7628
7964
|
function effectiveSshTarget(provider) {
|
|
@@ -7741,7 +8077,7 @@ var init_grove = __esm({
|
|
|
7741
8077
|
|
|
7742
8078
|
// src/cli/cloud/repo.ts
|
|
7743
8079
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
7744
|
-
import { existsSync as
|
|
8080
|
+
import { existsSync as existsSync24 } from "fs";
|
|
7745
8081
|
import { basename as basename5, join as join18 } from "path";
|
|
7746
8082
|
function captureGit(args, cwd) {
|
|
7747
8083
|
const result = spawnSync3("git", args, {
|
|
@@ -7782,10 +8118,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
|
|
|
7782
8118
|
];
|
|
7783
8119
|
}
|
|
7784
8120
|
function detectPackageManager(toplevel) {
|
|
7785
|
-
if (
|
|
7786
|
-
if (
|
|
7787
|
-
if (
|
|
7788
|
-
if (
|
|
8121
|
+
if (existsSync24(join18(toplevel, "pnpm-lock.yaml"))) return "pnpm";
|
|
8122
|
+
if (existsSync24(join18(toplevel, "bun.lockb"))) return "bun";
|
|
8123
|
+
if (existsSync24(join18(toplevel, "yarn.lock"))) return "yarn";
|
|
8124
|
+
if (existsSync24(join18(toplevel, "package-lock.json"))) return "npm";
|
|
7789
8125
|
return null;
|
|
7790
8126
|
}
|
|
7791
8127
|
function packageManagerInstallCmd(pm) {
|
|
@@ -7952,7 +8288,7 @@ async function cloudInstall(provider, repo, cwd) {
|
|
|
7952
8288
|
}
|
|
7953
8289
|
async function cloudSession(provider, repo) {
|
|
7954
8290
|
const remoteDir = boxRepoPath(repo);
|
|
7955
|
-
const cmd = `sis
|
|
8291
|
+
const cmd = `sis diagnostic home-init ${shellQuote(repo)} ${shellQuoteHomePath(remoteDir)}`;
|
|
7956
8292
|
console.log(`\u2192 initializing tmux home session "${repo}" on box...`);
|
|
7957
8293
|
const result = runOnBox(provider, cmd);
|
|
7958
8294
|
if (result.exitCode !== 0) {
|
|
@@ -7986,7 +8322,7 @@ __export(cloud_handoff_exports, {
|
|
|
7986
8322
|
triggerForceHandoff: () => triggerForceHandoff
|
|
7987
8323
|
});
|
|
7988
8324
|
import { spawn as spawn5 } from "child_process";
|
|
7989
|
-
import { existsSync as
|
|
8325
|
+
import { existsSync as existsSync25 } from "fs";
|
|
7990
8326
|
import { join as join19 } from "path";
|
|
7991
8327
|
function runRsync2(args) {
|
|
7992
8328
|
return new Promise((resolve13) => {
|
|
@@ -8026,7 +8362,7 @@ async function syncSessionState(cwd, sessionId, repo, target, provider) {
|
|
|
8026
8362
|
const candidates = ["config.json", "orchestrator.md", "orchestrator-settings.json"];
|
|
8027
8363
|
for (const name of candidates) {
|
|
8028
8364
|
const localPath = join19(localProject, name);
|
|
8029
|
-
if (!
|
|
8365
|
+
if (!existsSync25(localPath)) continue;
|
|
8030
8366
|
const remotePath = `${boxRepoPath(repo)}/.sisyphus/${name}`;
|
|
8031
8367
|
const args = [
|
|
8032
8368
|
"-avz",
|
|
@@ -8087,7 +8423,7 @@ async function pushToCloud(sessionId, cwd) {
|
|
|
8087
8423
|
}
|
|
8088
8424
|
const message = handoff.message;
|
|
8089
8425
|
const remoteRepoDir = boxRepoPath(repo);
|
|
8090
|
-
const baseCmd = message ? `sis session resume ${shellQuote(sessionId)} ${shellQuote(message)}` : `sis session resume ${shellQuote(sessionId)}`;
|
|
8426
|
+
const baseCmd = message ? `sis session lifecycle resume ${shellQuote(sessionId)} ${shellQuote(message)}` : `sis session lifecycle resume ${shellQuote(sessionId)}`;
|
|
8091
8427
|
const resumeCmd = `cd ${shellQuoteHomePath(remoteRepoDir)} && ${baseCmd}`;
|
|
8092
8428
|
console.log(`[sisyphus] handoff: ssh sis resume`);
|
|
8093
8429
|
const resumeResult = runOnBox(provider, resumeCmd);
|
|
@@ -8193,14 +8529,14 @@ var init_cloud_handoff = __esm({
|
|
|
8193
8529
|
|
|
8194
8530
|
// src/daemon/session-manager.ts
|
|
8195
8531
|
import { v4 as uuidv4 } from "uuid";
|
|
8196
|
-
import { existsSync as
|
|
8532
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync11, rmSync as rmSync6 } from "fs";
|
|
8197
8533
|
function truncate(s, max) {
|
|
8198
8534
|
return s.length <= max ? s : s.slice(0, max) + "...";
|
|
8199
8535
|
}
|
|
8200
8536
|
function readGoal(cwd, sessionId, fallback) {
|
|
8201
8537
|
try {
|
|
8202
8538
|
const p = goalPath(cwd, sessionId);
|
|
8203
|
-
if (
|
|
8539
|
+
if (existsSync26(p)) return readFileSync24(p, "utf-8").trim();
|
|
8204
8540
|
} catch {
|
|
8205
8541
|
}
|
|
8206
8542
|
return fallback;
|
|
@@ -8369,7 +8705,7 @@ async function startSession(task, cwd, context, name, effort) {
|
|
|
8369
8705
|
async function cloneSession(sourceId, cwd, goal, context, name, strategy) {
|
|
8370
8706
|
const sourceSession = getSession(cwd, sourceId);
|
|
8371
8707
|
if (sourceSession.status === "completed") {
|
|
8372
|
-
throw new Error("Cannot clone completed session. Use `sis session continue` to resume it first.");
|
|
8708
|
+
throw new Error("Cannot clone completed session. Use `sis session lifecycle continue` to resume it first.");
|
|
8373
8709
|
}
|
|
8374
8710
|
const cloneId = uuidv4();
|
|
8375
8711
|
if (name && !NAME_PATTERN.test(name)) {
|
|
@@ -8439,7 +8775,7 @@ It is the other session's responsibility. You do not need to monitor it.
|
|
|
8439
8775
|
function pruneOldSessions(cwd) {
|
|
8440
8776
|
try {
|
|
8441
8777
|
const dir = sessionsDir(cwd);
|
|
8442
|
-
if (!
|
|
8778
|
+
if (!existsSync26(dir)) return;
|
|
8443
8779
|
const entries = readdirSync11(dir, { withFileTypes: true });
|
|
8444
8780
|
const candidates = [];
|
|
8445
8781
|
for (const entry of entries) {
|
|
@@ -8484,7 +8820,7 @@ async function reconnectSession(sessionId, cwd) {
|
|
|
8484
8820
|
const session = getSession(cwd, sessionId);
|
|
8485
8821
|
const tmuxName = session.tmuxSessionName ?? tmuxSessionName(cwd, session.name ?? sessionId.slice(0, 8));
|
|
8486
8822
|
if (!sessionNameTaken(tmuxName)) {
|
|
8487
|
-
throw new Error(`No tmux session named "${tmuxName}" exists. Use \`sis session resume\` to create a new one.`);
|
|
8823
|
+
throw new Error(`No tmux session named "${tmuxName}" exists. Use \`sis session lifecycle resume\` to create a new one.`);
|
|
8488
8824
|
}
|
|
8489
8825
|
const tmuxSessId = resolveSessionId(tmuxName);
|
|
8490
8826
|
if (!tmuxSessId) {
|
|
@@ -8575,9 +8911,18 @@ async function resumeSession(sessionId, cwd, message) {
|
|
|
8575
8911
|
function getSessionStatus(cwd, sessionId) {
|
|
8576
8912
|
return getSession(cwd, sessionId);
|
|
8577
8913
|
}
|
|
8914
|
+
function countSessions(cwd) {
|
|
8915
|
+
const dir = sessionsDir(cwd);
|
|
8916
|
+
if (!existsSync26(dir)) return 0;
|
|
8917
|
+
let n = 0;
|
|
8918
|
+
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
8919
|
+
if (entry.isDirectory()) n++;
|
|
8920
|
+
}
|
|
8921
|
+
return n;
|
|
8922
|
+
}
|
|
8578
8923
|
function listSessions(cwd) {
|
|
8579
8924
|
const dir = sessionsDir(cwd);
|
|
8580
|
-
if (!
|
|
8925
|
+
if (!existsSync26(dir)) return [];
|
|
8581
8926
|
const entries = readdirSync11(dir, { withFileTypes: true });
|
|
8582
8927
|
const sessions = [];
|
|
8583
8928
|
for (const entry of entries) {
|
|
@@ -8650,8 +8995,8 @@ function onAllAgentsDone2(sessionId, cwd, windowId) {
|
|
|
8650
8995
|
Agents: ${truncate(spawnedThisCycle, 200)}`;
|
|
8651
8996
|
try {
|
|
8652
8997
|
const logPath2 = cycleLogPath(cwd, sessionId, cycleNumber);
|
|
8653
|
-
if (
|
|
8654
|
-
const log =
|
|
8998
|
+
if (existsSync26(logPath2)) {
|
|
8999
|
+
const log = readFileSync24(logPath2, "utf-8").trim();
|
|
8655
9000
|
if (log) cycleCtx += `
|
|
8656
9001
|
Cycle log: ${truncate(log, 200)}`;
|
|
8657
9002
|
}
|
|
@@ -8676,6 +9021,20 @@ Cycle log: ${truncate(log, 200)}`;
|
|
|
8676
9021
|
await performHandoff2(sessionId, cwd);
|
|
8677
9022
|
return;
|
|
8678
9023
|
}
|
|
9024
|
+
const sessionForOrphanResolve = getSession(cwd, sessionId);
|
|
9025
|
+
const supersededAgentIds = sessionForOrphanResolve.agents.filter((a) => a.status !== "running").map((a) => a.id);
|
|
9026
|
+
await Promise.all([
|
|
9027
|
+
clearSessionOrphan(cwd, sessionId),
|
|
9028
|
+
resolveOrchestratorOrphanAsks(cwd, sessionId, "respawn"),
|
|
9029
|
+
...supersededAgentIds.map(
|
|
9030
|
+
(agentId) => resolveAgentOrphanAsks(cwd, sessionId, agentId, "superseded").catch((err) => {
|
|
9031
|
+
console.warn(
|
|
9032
|
+
`[sisyphus] resolveAgentOrphanAsks (superseded) failed for ${agentId}:`,
|
|
9033
|
+
err instanceof Error ? err.message : err
|
|
9034
|
+
);
|
|
9035
|
+
})
|
|
9036
|
+
)
|
|
9037
|
+
]);
|
|
8679
9038
|
let activeWindowId = windowId;
|
|
8680
9039
|
const tmuxName = freshSession.tmuxSessionName;
|
|
8681
9040
|
const existingTmuxSessId = freshSession.tmuxSessionId;
|
|
@@ -8797,7 +9156,7 @@ async function handleSubmit(cwd, sessionId, agentId, report, windowId) {
|
|
|
8797
9156
|
function formatPendingAskError(verb, askedBy, open) {
|
|
8798
9157
|
const lines = open.map((a) => ` - ${a.askId} (${a.status})${a.title ? ": " + a.title : ""}`);
|
|
8799
9158
|
const who = askedBy === ORCHESTRATOR_ASKED_BY ? "orchestrator" : `agent ${askedBy}`;
|
|
8800
|
-
const recovery = verb === "yield" ? `Resolve before yielding: \`sis ask poll <askId>\` blocks until the user answers, then process the response and yield with a continuation prompt that names the answered branch.` : `Resolve before submitting: \`sis ask poll <askId>\` blocks until the user answers, parse the response, then call \`sis agent submit\` with your final report.`;
|
|
9159
|
+
const recovery = verb === "yield" ? `Resolve before yielding: \`sis ask state poll <askId>\` blocks until the user answers, then process the response and yield with a continuation prompt that names the answered branch.` : `Resolve before submitting: \`sis ask state poll <askId>\` blocks until the user answers, parse the response, then call \`sis agent submit\` with your final report.`;
|
|
8801
9160
|
return `Cannot ${verb}: ${who} owns ${open.length} open deck${open.length === 1 ? "" : "s"}:
|
|
8802
9161
|
${lines.join("\n")}
|
|
8803
9162
|
|
|
@@ -9231,8 +9590,8 @@ function digestTranscript(opts) {
|
|
|
9231
9590
|
const role = entry.message?.role !== void 0 ? entry.message.role : entry.type;
|
|
9232
9591
|
const text = formatContent(entry.message?.content);
|
|
9233
9592
|
if (!text) continue;
|
|
9234
|
-
const
|
|
9235
|
-
kept.push({ ts
|
|
9593
|
+
const ts = entry.timestamp !== void 0 ? entry.timestamp : "";
|
|
9594
|
+
kept.push({ ts, role, text });
|
|
9236
9595
|
}
|
|
9237
9596
|
let totalBytes = 0;
|
|
9238
9597
|
const selected = [];
|
|
@@ -9279,11 +9638,10 @@ var init_transcript_digest = __esm({
|
|
|
9279
9638
|
});
|
|
9280
9639
|
|
|
9281
9640
|
// src/daemon/ask-visual.ts
|
|
9282
|
-
import { spawn as spawn6 } from "child_process";
|
|
9283
9641
|
import {
|
|
9284
9642
|
closeSync as closeSync2,
|
|
9285
9643
|
constants,
|
|
9286
|
-
existsSync as
|
|
9644
|
+
existsSync as existsSync27,
|
|
9287
9645
|
fstatSync as fstatSync2,
|
|
9288
9646
|
lstatSync,
|
|
9289
9647
|
openSync as openSync2,
|
|
@@ -9295,16 +9653,17 @@ import { resolve as resolve10, dirname as dirname7, isAbsolute, sep } from "path
|
|
|
9295
9653
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9296
9654
|
import { z as z3 } from "zod";
|
|
9297
9655
|
import { tool } from "@r-cli/sdk";
|
|
9656
|
+
import { renderMarkdown, checkMarkdown } from "@crouton-kit/humanloop";
|
|
9298
9657
|
async function generateVisualForQuestion(opts) {
|
|
9299
9658
|
const meta = readMeta(opts.cwd, opts.sessionId, opts.askId);
|
|
9300
9659
|
if (!meta) return { ok: false, error: `ask not found: ${opts.askId}` };
|
|
9301
9660
|
const decisions = readDecisions(opts.cwd, opts.sessionId, opts.askId);
|
|
9302
|
-
if (!decisions) return { ok: false, error: "
|
|
9661
|
+
if (!decisions) return { ok: false, error: "deck.json missing" };
|
|
9303
9662
|
const question = decisions.interactions.find((q) => q.id === opts.qid);
|
|
9304
9663
|
if (!question) return { ok: false, error: `qid ${opts.qid} not found in decisions` };
|
|
9305
9664
|
const mdPath = askVisualMarkdownPath(opts.cwd, opts.sessionId, opts.askId, opts.qid);
|
|
9306
9665
|
const ansiPath = askVisualAnsiPath(opts.cwd, opts.sessionId, opts.askId, opts.qid);
|
|
9307
|
-
if (!opts.force &&
|
|
9666
|
+
if (!opts.force && existsSync27(mdPath) && existsSync27(ansiPath)) {
|
|
9308
9667
|
return { ok: true, markdownPath: mdPath, ansiPath, turns: 0 };
|
|
9309
9668
|
}
|
|
9310
9669
|
if (opts.force) {
|
|
@@ -9333,7 +9692,7 @@ async function generateVisualForQuestion(opts) {
|
|
|
9333
9692
|
);
|
|
9334
9693
|
const attachVisualTool = tool(
|
|
9335
9694
|
"attach_visual",
|
|
9336
|
-
"Submit final
|
|
9695
|
+
"Submit final directive-flavored markdown for this question. Validated and rendered to ANSI via the humanloop SDK (no subprocess).",
|
|
9337
9696
|
{ content: z3.string().min(1) },
|
|
9338
9697
|
async (args) => {
|
|
9339
9698
|
const r = await attachVisualHandler({ content: args.content, mdPath, ansiPath, cols: opts.cols });
|
|
@@ -9378,7 +9737,7 @@ function buildUserPrompt(q, askedBy, ctx) {
|
|
|
9378
9737
|
}
|
|
9379
9738
|
function readSystemPrompt() {
|
|
9380
9739
|
if (cachedSystemPrompt !== void 0) return cachedSystemPrompt;
|
|
9381
|
-
const found = SYSTEM_PROMPT_CANDIDATES.find((p) =>
|
|
9740
|
+
const found = SYSTEM_PROMPT_CANDIDATES.find((p) => existsSync27(p));
|
|
9382
9741
|
if (found === void 0) {
|
|
9383
9742
|
throw new Error(
|
|
9384
9743
|
`termrender-haiku-system.md not found in any candidate location: ${SYSTEM_PROMPT_CANDIDATES.join(", ")}`
|
|
@@ -9447,48 +9806,20 @@ function readFileHandler(realSessionCwd, requestedPath) {
|
|
|
9447
9806
|
function errorResult(msg) {
|
|
9448
9807
|
return { content: [{ type: "text", text: msg }], isError: true };
|
|
9449
9808
|
}
|
|
9450
|
-
function spawnAsync(cmd, args, input, timeoutMs) {
|
|
9451
|
-
return new Promise((resolve13) => {
|
|
9452
|
-
const chunks = [];
|
|
9453
|
-
const errChunks = [];
|
|
9454
|
-
const proc = spawn6(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: execEnv() });
|
|
9455
|
-
const timer = setTimeout(() => {
|
|
9456
|
-
proc.kill();
|
|
9457
|
-
resolve13({ stdout: "", stderr: "", status: null, error: new Error(`timed out after ${timeoutMs}ms`) });
|
|
9458
|
-
}, timeoutMs);
|
|
9459
|
-
proc.stdout.on("data", (d) => chunks.push(d));
|
|
9460
|
-
proc.stderr.on("data", (d) => errChunks.push(d));
|
|
9461
|
-
proc.on("error", (err) => {
|
|
9462
|
-
clearTimeout(timer);
|
|
9463
|
-
resolve13({ stdout: "", stderr: "", status: null, error: err });
|
|
9464
|
-
});
|
|
9465
|
-
proc.on("close", (code) => {
|
|
9466
|
-
clearTimeout(timer);
|
|
9467
|
-
resolve13({ stdout: Buffer.concat(chunks).toString("utf-8"), stderr: Buffer.concat(errChunks).toString("utf-8"), status: code });
|
|
9468
|
-
});
|
|
9469
|
-
proc.stdin.end(input, "utf-8");
|
|
9470
|
-
});
|
|
9471
|
-
}
|
|
9472
9809
|
async function attachVisualHandler(args) {
|
|
9473
|
-
const check =
|
|
9474
|
-
if (check.
|
|
9475
|
-
const msg = `
|
|
9810
|
+
const check = checkMarkdown(args.content);
|
|
9811
|
+
if (!check.ok) {
|
|
9812
|
+
const msg = `directive check rejected the content: ${check.error}`;
|
|
9476
9813
|
return { ok: false, error: msg, toolResult: errorResult(msg) };
|
|
9477
9814
|
}
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
const msg = `termrender render failed: ${render.error.message}`;
|
|
9485
|
-
return { ok: false, error: msg, toolResult: errorResult(msg) };
|
|
9486
|
-
}
|
|
9487
|
-
if (render.status !== 0) {
|
|
9488
|
-
const msg = `termrender render failed: ${render.stderr.trim()}`;
|
|
9815
|
+
let ansi;
|
|
9816
|
+
try {
|
|
9817
|
+
const r = { lines: renderMarkdown(args.content, args.cols) };
|
|
9818
|
+
ansi = r.lines.join("\n");
|
|
9819
|
+
} catch (e) {
|
|
9820
|
+
const msg = `render failed: ${e.message}`;
|
|
9489
9821
|
return { ok: false, error: msg, toolResult: errorResult(msg) };
|
|
9490
9822
|
}
|
|
9491
|
-
const ansi = render.stdout;
|
|
9492
9823
|
const ansiBytes = Buffer.byteLength(ansi, "utf-8");
|
|
9493
9824
|
if (ansiBytes > ANSI_CAP) {
|
|
9494
9825
|
const msg = `rendered ANSI exceeds ${ANSI_CAP} byte cap (got ${ansiBytes})`;
|
|
@@ -9534,7 +9865,6 @@ var READ_FILE_CAP, ANSI_CAP, SYSTEM_PROMPT_CANDIDATES, cachedSystemPrompt;
|
|
|
9534
9865
|
var init_ask_visual = __esm({
|
|
9535
9866
|
"src/daemon/ask-visual.ts"() {
|
|
9536
9867
|
"use strict";
|
|
9537
|
-
init_env();
|
|
9538
9868
|
init_haiku();
|
|
9539
9869
|
init_transcript_digest();
|
|
9540
9870
|
init_ask_store();
|
|
@@ -9554,8 +9884,9 @@ var init_ask_visual = __esm({
|
|
|
9554
9884
|
|
|
9555
9885
|
// src/daemon/server.ts
|
|
9556
9886
|
import { createServer } from "net";
|
|
9557
|
-
import { unlinkSync as unlinkSync4, existsSync as
|
|
9558
|
-
import { join as join21 } from "path";
|
|
9887
|
+
import { unlinkSync as unlinkSync4, existsSync as existsSync28, writeFileSync as writeFileSync15, readFileSync as readFileSync26, mkdirSync as mkdirSync13, readdirSync as readdirSync12, rmSync as rmSync8, chmodSync as chmodSync2 } from "fs";
|
|
9888
|
+
import { join as join21, basename as basename6, dirname as dirname8 } from "path";
|
|
9889
|
+
import { scanInbox } from "@crouton-kit/humanloop";
|
|
9559
9890
|
function setCompositor(c) {
|
|
9560
9891
|
compositor = c;
|
|
9561
9892
|
}
|
|
@@ -9573,9 +9904,9 @@ function persistSessionRegistry() {
|
|
|
9573
9904
|
}
|
|
9574
9905
|
function loadSessionRegistry() {
|
|
9575
9906
|
const p = registryPath();
|
|
9576
|
-
if (!
|
|
9907
|
+
if (!existsSync28(p)) return {};
|
|
9577
9908
|
try {
|
|
9578
|
-
return JSON.parse(
|
|
9909
|
+
return JSON.parse(readFileSync26(p, "utf-8"));
|
|
9579
9910
|
} catch (err) {
|
|
9580
9911
|
console.warn("[sisyphus] Failed to parse session registry:", err instanceof Error ? err.message : err);
|
|
9581
9912
|
return {};
|
|
@@ -9605,7 +9936,14 @@ function registerSessionTmux(sessionId, tmuxSession, windowId, tmuxSessionId) {
|
|
|
9605
9936
|
}
|
|
9606
9937
|
}
|
|
9607
9938
|
function unknownSessionError(sessionId) {
|
|
9608
|
-
return {
|
|
9939
|
+
return {
|
|
9940
|
+
ok: false,
|
|
9941
|
+
error: errNotFound("unknown_session", {
|
|
9942
|
+
message: `Unknown session: ${sessionId}. Run \`sis session inspect list --all\` to see available sessions.`,
|
|
9943
|
+
received: sessionId,
|
|
9944
|
+
next: "sis session inspect list --all"
|
|
9945
|
+
})
|
|
9946
|
+
};
|
|
9609
9947
|
}
|
|
9610
9948
|
function collectAllSessionIds() {
|
|
9611
9949
|
const idToCwd = /* @__PURE__ */ new Map();
|
|
@@ -9622,7 +9960,7 @@ function collectAllSessionIds() {
|
|
|
9622
9960
|
scannedCwds.add(cwd);
|
|
9623
9961
|
try {
|
|
9624
9962
|
const dir = sessionsDir(cwd);
|
|
9625
|
-
if (!
|
|
9963
|
+
if (!existsSync28(dir)) continue;
|
|
9626
9964
|
for (const entry of readdirSync12(dir, { withFileTypes: true })) {
|
|
9627
9965
|
if (entry.isDirectory() && !idToCwd.has(entry.name)) {
|
|
9628
9966
|
idToCwd.set(entry.name, cwd);
|
|
@@ -9647,9 +9985,16 @@ function resolvePartialSessionId(partial) {
|
|
|
9647
9985
|
return { id };
|
|
9648
9986
|
}
|
|
9649
9987
|
if (matches.length > 1) {
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9988
|
+
return {
|
|
9989
|
+
error: {
|
|
9990
|
+
ok: false,
|
|
9991
|
+
error: errAmbiguous("ambiguous_session", {
|
|
9992
|
+
message: `Ambiguous session prefix "${partial}" matches ${matches.length} sessions`,
|
|
9993
|
+
received: partial,
|
|
9994
|
+
candidates: matches
|
|
9995
|
+
})
|
|
9996
|
+
}
|
|
9997
|
+
};
|
|
9653
9998
|
}
|
|
9654
9999
|
return { id: partial };
|
|
9655
10000
|
}
|
|
@@ -9664,7 +10009,13 @@ async function handleRequest(req) {
|
|
|
9664
10009
|
try {
|
|
9665
10010
|
if ("sessionId" in req && req.sessionId) {
|
|
9666
10011
|
if (!validateSessionId(req.sessionId)) {
|
|
9667
|
-
return {
|
|
10012
|
+
return {
|
|
10013
|
+
ok: false,
|
|
10014
|
+
error: errUsage("invalid_session_id", {
|
|
10015
|
+
message: "Invalid session ID: must contain only alphanumeric characters, hyphens, and underscores",
|
|
10016
|
+
received: req.sessionId
|
|
10017
|
+
})
|
|
10018
|
+
};
|
|
9668
10019
|
}
|
|
9669
10020
|
const resolved = resolvePartialSessionId(req.sessionId);
|
|
9670
10021
|
if ("error" in resolved) return resolved.error;
|
|
@@ -9709,7 +10060,13 @@ async function handleRequest(req) {
|
|
|
9709
10060
|
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
9710
10061
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
9711
10062
|
if (req.repo && req.repo !== "." && !validateRepoName(req.repo)) {
|
|
9712
|
-
return {
|
|
10063
|
+
return {
|
|
10064
|
+
ok: false,
|
|
10065
|
+
error: errUsage("invalid_repo_name", {
|
|
10066
|
+
message: 'Invalid repo name: must be a simple directory name without path separators or ".."',
|
|
10067
|
+
received: req.repo
|
|
10068
|
+
})
|
|
10069
|
+
};
|
|
9713
10070
|
}
|
|
9714
10071
|
const result = await handleSpawn(req.sessionId, tracking.cwd, req.agentType, req.name, req.instruction, req.repo);
|
|
9715
10072
|
return { ok: true, data: { agentId: result.agentId } };
|
|
@@ -9717,7 +10074,13 @@ async function handleRequest(req) {
|
|
|
9717
10074
|
case "submit": {
|
|
9718
10075
|
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
9719
10076
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
9720
|
-
if (!tracking.windowId) return {
|
|
10077
|
+
if (!tracking.windowId) return {
|
|
10078
|
+
ok: false,
|
|
10079
|
+
error: errNotFound("no_tmux_window", {
|
|
10080
|
+
message: `No tmux window found for session: ${req.sessionId}`,
|
|
10081
|
+
received: req.sessionId
|
|
10082
|
+
})
|
|
10083
|
+
};
|
|
9721
10084
|
await handleSubmit(tracking.cwd, req.sessionId, req.agentId, req.report, tracking.windowId);
|
|
9722
10085
|
return { ok: true };
|
|
9723
10086
|
}
|
|
@@ -9737,7 +10100,13 @@ async function handleRequest(req) {
|
|
|
9737
10100
|
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
9738
10101
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
9739
10102
|
const result = await handleAwait(tracking.cwd, req.sessionId, req.agentId);
|
|
9740
|
-
if (!result) return {
|
|
10103
|
+
if (!result) return {
|
|
10104
|
+
ok: false,
|
|
10105
|
+
error: errNotFound("unknown_agent", {
|
|
10106
|
+
message: `Unknown agent: ${req.agentId} in session ${req.sessionId}`,
|
|
10107
|
+
received: req.agentId
|
|
10108
|
+
})
|
|
10109
|
+
};
|
|
9741
10110
|
return {
|
|
9742
10111
|
ok: true,
|
|
9743
10112
|
data: {
|
|
@@ -9811,7 +10180,7 @@ async function handleRequest(req) {
|
|
|
9811
10180
|
for (const tracking of sessionTrackingMap.values()) {
|
|
9812
10181
|
if (seenCwds.has(tracking.cwd)) continue;
|
|
9813
10182
|
seenCwds.add(tracking.cwd);
|
|
9814
|
-
totalCount +=
|
|
10183
|
+
totalCount += countSessions(tracking.cwd);
|
|
9815
10184
|
}
|
|
9816
10185
|
if (totalCount > allSessions.length) {
|
|
9817
10186
|
return { ok: true, data: { sessions: allSessions, totalCount, filtered: true } };
|
|
@@ -9823,12 +10192,19 @@ async function handleRequest(req) {
|
|
|
9823
10192
|
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
9824
10193
|
if (!tracking) {
|
|
9825
10194
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9826
|
-
if (
|
|
10195
|
+
if (existsSync28(stateFile)) {
|
|
9827
10196
|
tracking = { cwd: req.cwd, messageCounter: 0 };
|
|
9828
10197
|
sessionTrackingMap.set(req.sessionId, tracking);
|
|
9829
10198
|
persistSessionRegistry();
|
|
9830
10199
|
} else {
|
|
9831
|
-
return {
|
|
10200
|
+
return {
|
|
10201
|
+
ok: false,
|
|
10202
|
+
error: errNotFound("unknown_session", {
|
|
10203
|
+
message: `Unknown session: ${req.sessionId}. No state.json found at ${stateFile}. Run \`sis session inspect list --all\` to see available sessions.`,
|
|
10204
|
+
received: req.sessionId,
|
|
10205
|
+
next: "sis session inspect list --all"
|
|
10206
|
+
})
|
|
10207
|
+
};
|
|
9832
10208
|
}
|
|
9833
10209
|
}
|
|
9834
10210
|
const session = await resumeSession(req.sessionId, tracking.cwd, req.message);
|
|
@@ -9838,8 +10214,15 @@ async function handleRequest(req) {
|
|
|
9838
10214
|
}
|
|
9839
10215
|
case "clear-orphan": {
|
|
9840
10216
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9841
|
-
if (!
|
|
9842
|
-
return {
|
|
10217
|
+
if (!existsSync28(stateFile)) {
|
|
10218
|
+
return {
|
|
10219
|
+
ok: false,
|
|
10220
|
+
error: errNotFound("unknown_session", {
|
|
10221
|
+
message: `Unknown session: ${req.sessionId}. No state.json at ${stateFile}.`,
|
|
10222
|
+
received: req.sessionId,
|
|
10223
|
+
next: "sis session inspect list --all"
|
|
10224
|
+
})
|
|
10225
|
+
};
|
|
9843
10226
|
}
|
|
9844
10227
|
await Promise.all([
|
|
9845
10228
|
clearSessionOrphan(req.cwd, req.sessionId),
|
|
@@ -9889,7 +10272,7 @@ async function handleRequest(req) {
|
|
|
9889
10272
|
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
9890
10273
|
if (!tracking) {
|
|
9891
10274
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9892
|
-
if (
|
|
10275
|
+
if (existsSync28(stateFile)) {
|
|
9893
10276
|
registerSessionCwd(req.sessionId, req.cwd);
|
|
9894
10277
|
tracking = sessionTrackingMap.get(req.sessionId);
|
|
9895
10278
|
} else {
|
|
@@ -9903,7 +10286,7 @@ async function handleRequest(req) {
|
|
|
9903
10286
|
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
9904
10287
|
if (!tracking) {
|
|
9905
10288
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9906
|
-
if (
|
|
10289
|
+
if (existsSync28(stateFile)) {
|
|
9907
10290
|
registerSessionCwd(req.sessionId, req.cwd);
|
|
9908
10291
|
tracking = sessionTrackingMap.get(req.sessionId);
|
|
9909
10292
|
} else {
|
|
@@ -9920,7 +10303,7 @@ async function handleRequest(req) {
|
|
|
9920
10303
|
let tracking = sessionTrackingMap.get(req.sessionId);
|
|
9921
10304
|
if (!tracking) {
|
|
9922
10305
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9923
|
-
if (
|
|
10306
|
+
if (existsSync28(stateFile)) {
|
|
9924
10307
|
tracking = { cwd: req.cwd, messageCounter: 0 };
|
|
9925
10308
|
sessionTrackingMap.set(req.sessionId, tracking);
|
|
9926
10309
|
persistSessionRegistry();
|
|
@@ -9990,7 +10373,7 @@ async function handleRequest(req) {
|
|
|
9990
10373
|
}
|
|
9991
10374
|
case "set-upload-status": {
|
|
9992
10375
|
const stateFile = `${req.cwd}/.sisyphus/sessions/${req.sessionId}/state.json`;
|
|
9993
|
-
if (!
|
|
10376
|
+
if (!existsSync28(stateFile)) {
|
|
9994
10377
|
return unknownSessionError(req.sessionId);
|
|
9995
10378
|
}
|
|
9996
10379
|
try {
|
|
@@ -10002,7 +10385,12 @@ async function handleRequest(req) {
|
|
|
10002
10385
|
});
|
|
10003
10386
|
return { ok: true };
|
|
10004
10387
|
} catch (err) {
|
|
10005
|
-
return {
|
|
10388
|
+
return {
|
|
10389
|
+
ok: false,
|
|
10390
|
+
error: errPermanent("rpc_failed", {
|
|
10391
|
+
message: `Failed to persist upload status: ${err instanceof Error ? err.message : String(err)}`
|
|
10392
|
+
})
|
|
10393
|
+
};
|
|
10006
10394
|
}
|
|
10007
10395
|
}
|
|
10008
10396
|
case "message": {
|
|
@@ -10048,13 +10436,32 @@ async function handleRequest(req) {
|
|
|
10048
10436
|
const liveCycle = [...session.orchestratorCycles].reverse().find((c) => !c.completedAt);
|
|
10049
10437
|
paneId = liveCycle?.paneId ?? session.orchestratorCycles.at(-1)?.paneId;
|
|
10050
10438
|
targetLabel = "orchestrator";
|
|
10051
|
-
if (!paneId) return {
|
|
10439
|
+
if (!paneId) return {
|
|
10440
|
+
ok: false,
|
|
10441
|
+
error: errNotFound("no_orchestrator_pane", {
|
|
10442
|
+
message: "No orchestrator pane found for this session",
|
|
10443
|
+
received: req.sessionId
|
|
10444
|
+
})
|
|
10445
|
+
};
|
|
10052
10446
|
} else {
|
|
10053
10447
|
const agentId = req.target.agentId;
|
|
10054
10448
|
const ag = session.agents.find((a) => a.id === agentId);
|
|
10055
|
-
if (!ag) return {
|
|
10449
|
+
if (!ag) return {
|
|
10450
|
+
ok: false,
|
|
10451
|
+
error: errNotFound("unknown_agent", {
|
|
10452
|
+
message: `Unknown agent: ${agentId} in session ${req.sessionId}`,
|
|
10453
|
+
received: agentId
|
|
10454
|
+
})
|
|
10455
|
+
};
|
|
10056
10456
|
if (ag.status !== "running") {
|
|
10057
|
-
return {
|
|
10457
|
+
return {
|
|
10458
|
+
ok: false,
|
|
10459
|
+
error: errConflict("agent_not_running", {
|
|
10460
|
+
message: `Agent ${agentId} is ${ag.status}, not running \u2014 cannot tell`,
|
|
10461
|
+
received: ag.status,
|
|
10462
|
+
expected: "running"
|
|
10463
|
+
})
|
|
10464
|
+
};
|
|
10058
10465
|
}
|
|
10059
10466
|
paneId = ag.paneId;
|
|
10060
10467
|
targetLabel = agentId;
|
|
@@ -10062,7 +10469,12 @@ async function handleRequest(req) {
|
|
|
10062
10469
|
try {
|
|
10063
10470
|
pasteToPane(paneId, req.text, req.submit);
|
|
10064
10471
|
} catch (err) {
|
|
10065
|
-
return {
|
|
10472
|
+
return {
|
|
10473
|
+
ok: false,
|
|
10474
|
+
error: errPermanent("rpc_failed", {
|
|
10475
|
+
message: err instanceof Error ? err.message : String(err)
|
|
10476
|
+
})
|
|
10477
|
+
};
|
|
10066
10478
|
}
|
|
10067
10479
|
tracking.messageCounter += 1;
|
|
10068
10480
|
const id = `tell-${String(tracking.messageCounter).padStart(3, "0")}`;
|
|
@@ -10093,7 +10505,7 @@ async function handleRequest(req) {
|
|
|
10093
10505
|
return { ok: true, data: companion };
|
|
10094
10506
|
}
|
|
10095
10507
|
case "register-segment": {
|
|
10096
|
-
if (!compositor) return { ok: false, error: "Compositor not initialized" };
|
|
10508
|
+
if (!compositor) return { ok: false, error: errTransient("compositor_uninit", { message: "Compositor not initialized" }) };
|
|
10097
10509
|
compositor.registerExternal({
|
|
10098
10510
|
id: req.id,
|
|
10099
10511
|
side: req.side,
|
|
@@ -10104,26 +10516,41 @@ async function handleRequest(req) {
|
|
|
10104
10516
|
return { ok: true };
|
|
10105
10517
|
}
|
|
10106
10518
|
case "update-segment": {
|
|
10107
|
-
if (!compositor) return { ok: false, error: "Compositor not initialized" };
|
|
10519
|
+
if (!compositor) return { ok: false, error: errTransient("compositor_uninit", { message: "Compositor not initialized" }) };
|
|
10108
10520
|
try {
|
|
10109
10521
|
compositor.updateExternal(req.id, req.content);
|
|
10110
10522
|
return { ok: true };
|
|
10111
10523
|
} catch (e) {
|
|
10112
|
-
return {
|
|
10524
|
+
return {
|
|
10525
|
+
ok: false,
|
|
10526
|
+
error: errPermanent("rpc_failed", { message: e.message })
|
|
10527
|
+
};
|
|
10113
10528
|
}
|
|
10114
10529
|
}
|
|
10115
10530
|
case "unregister-segment": {
|
|
10116
|
-
if (!compositor) return { ok: false, error: "Compositor not initialized" };
|
|
10531
|
+
if (!compositor) return { ok: false, error: errTransient("compositor_uninit", { message: "Compositor not initialized" }) };
|
|
10117
10532
|
compositor.unregisterExternal(req.id);
|
|
10118
10533
|
return { ok: true };
|
|
10119
10534
|
}
|
|
10120
10535
|
case "ask-generate-visual": {
|
|
10121
10536
|
const ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
10122
|
-
if (!ID_RE.test(req.askId)) return {
|
|
10123
|
-
|
|
10537
|
+
if (!ID_RE.test(req.askId)) return {
|
|
10538
|
+
ok: false,
|
|
10539
|
+
error: errUsage("invalid_ask_id", { message: `Invalid askId: ${req.askId}`, received: req.askId })
|
|
10540
|
+
};
|
|
10541
|
+
if (!ID_RE.test(req.qid)) return {
|
|
10542
|
+
ok: false,
|
|
10543
|
+
error: errUsage("invalid_qid", { message: `Invalid qid: ${req.qid}`, received: req.qid })
|
|
10544
|
+
};
|
|
10124
10545
|
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
10125
10546
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
10126
|
-
if (!tracking.cwd) return {
|
|
10547
|
+
if (!tracking.cwd) return {
|
|
10548
|
+
ok: false,
|
|
10549
|
+
error: errUsage("no_cwd_registered", {
|
|
10550
|
+
message: `No cwd registered for session: ${req.sessionId}`,
|
|
10551
|
+
received: req.sessionId
|
|
10552
|
+
})
|
|
10553
|
+
};
|
|
10127
10554
|
const result = await generateVisualForQuestion({
|
|
10128
10555
|
cwd: tracking.cwd,
|
|
10129
10556
|
sessionId: req.sessionId,
|
|
@@ -10135,62 +10562,31 @@ async function handleRequest(req) {
|
|
|
10135
10562
|
if (result.ok) {
|
|
10136
10563
|
return { ok: true, data: { markdownPath: result.markdownPath, ansiPath: result.ansiPath, turns: result.turns } };
|
|
10137
10564
|
}
|
|
10138
|
-
return {
|
|
10565
|
+
return {
|
|
10566
|
+
ok: false,
|
|
10567
|
+
error: errPermanent("rpc_failed", { message: result.error })
|
|
10568
|
+
};
|
|
10139
10569
|
}
|
|
10140
10570
|
case "inbox-list": {
|
|
10141
|
-
const
|
|
10571
|
+
const askDirs = [];
|
|
10142
10572
|
for (const [sessionId, tracking] of sessionTrackingMap) {
|
|
10143
10573
|
if (!tracking.cwd) continue;
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
for (const askId of askIds) {
|
|
10153
|
-
try {
|
|
10154
|
-
const meta = readMeta(tracking.cwd, sessionId, askId);
|
|
10155
|
-
if (!meta) continue;
|
|
10156
|
-
if (meta.status === "answered") continue;
|
|
10157
|
-
let title = meta.title;
|
|
10158
|
-
let subtitle = meta.subtitle;
|
|
10159
|
-
let kind = meta.kind;
|
|
10160
|
-
if (title === void 0 || kind === void 0) {
|
|
10161
|
-
try {
|
|
10162
|
-
const decisions = readDecisions(tracking.cwd, sessionId, askId);
|
|
10163
|
-
if (decisions) {
|
|
10164
|
-
const q0 = decisions.interactions[0];
|
|
10165
|
-
if (title === void 0) title = decisions.title !== void 0 ? decisions.title : q0?.title;
|
|
10166
|
-
if (subtitle === void 0) subtitle = q0?.subtitle;
|
|
10167
|
-
if (kind === void 0) kind = q0?.kind;
|
|
10168
|
-
}
|
|
10169
|
-
} catch (_err) {
|
|
10170
|
-
}
|
|
10171
|
-
}
|
|
10172
|
-
items.push({
|
|
10173
|
-
sessionId,
|
|
10174
|
-
sessionName,
|
|
10175
|
-
cwd: tracking.cwd,
|
|
10176
|
-
askId,
|
|
10177
|
-
askedBy: meta.askedBy,
|
|
10178
|
-
askedAt: meta.askedAt,
|
|
10179
|
-
status: meta.status,
|
|
10180
|
-
blocking: meta.blocking,
|
|
10181
|
-
orphaned: meta.orphaned,
|
|
10182
|
-
title,
|
|
10183
|
-
subtitle,
|
|
10184
|
-
blockedSince: meta.status === "in-progress" ? meta.startedAt ?? meta.askedAt : meta.askedAt,
|
|
10185
|
-
kind
|
|
10186
|
-
});
|
|
10187
|
-
} catch (err) {
|
|
10188
|
-
console.warn(`[sisyphus] inbox-list: readMeta failed for ${sessionId}/${askId}:`, err);
|
|
10189
|
-
}
|
|
10190
|
-
}
|
|
10574
|
+
askDirs.push(askDir(tracking.cwd, sessionId));
|
|
10575
|
+
}
|
|
10576
|
+
let items;
|
|
10577
|
+
try {
|
|
10578
|
+
items = scanInbox(askDirs);
|
|
10579
|
+
} catch (err) {
|
|
10580
|
+
console.warn("[sisyphus] inbox-list: scanInbox failed:", err);
|
|
10581
|
+
items = [];
|
|
10191
10582
|
}
|
|
10192
|
-
items.
|
|
10193
|
-
|
|
10583
|
+
const itemsWithName = items.map((item) => {
|
|
10584
|
+
const sessionId = basename6(dirname8(dirname8(dirname8(item.dir))));
|
|
10585
|
+
const tracking = sessionTrackingMap.get(sessionId);
|
|
10586
|
+
const sessionName = tracking?.name ?? item.source?.sessionName;
|
|
10587
|
+
return { ...item, sessionName };
|
|
10588
|
+
});
|
|
10589
|
+
return { ok: true, data: { items: itemsWithName } };
|
|
10194
10590
|
}
|
|
10195
10591
|
case "cloud-handoff": {
|
|
10196
10592
|
const tracking = sessionTrackingMap.get(req.sessionId);
|
|
@@ -10198,23 +10594,45 @@ async function handleRequest(req) {
|
|
|
10198
10594
|
const session = getSession(tracking.cwd, req.sessionId);
|
|
10199
10595
|
if (session.handoff?.sentAt) {
|
|
10200
10596
|
const where = session.handoff.target ? `${session.handoff.target.provider}:${session.handoff.target.repo}` : "cloud";
|
|
10201
|
-
return {
|
|
10597
|
+
return {
|
|
10598
|
+
ok: false,
|
|
10599
|
+
error: errConflict("already_on_cloud", {
|
|
10600
|
+
message: `Session ${req.sessionId} is already on ${where}. Use \`sis cloud handoff pull\` first.`,
|
|
10601
|
+
received: req.sessionId,
|
|
10602
|
+
next: "sis cloud handoff pull"
|
|
10603
|
+
})
|
|
10604
|
+
};
|
|
10202
10605
|
}
|
|
10203
10606
|
if (session.handoff && !req.force) {
|
|
10204
|
-
return {
|
|
10607
|
+
return {
|
|
10608
|
+
ok: false,
|
|
10609
|
+
error: errConflict("handoff_already_queued", {
|
|
10610
|
+
message: `Session ${req.sessionId} already has a queued handoff. Pass --force to override or run \`sis cloud handoff cancel ${req.sessionId}\` to clear.`,
|
|
10611
|
+
received: req.sessionId
|
|
10612
|
+
})
|
|
10613
|
+
};
|
|
10205
10614
|
}
|
|
10206
10615
|
const { resolveProvider: resolveProvider2, defaultRepoName: defaultRepoName2, checkRepoName: checkRepoName2, triggerForceHandoff: triggerForceHandoff2 } = await Promise.resolve().then(() => (init_cloud_handoff(), cloud_handoff_exports));
|
|
10207
10616
|
let provider;
|
|
10208
10617
|
try {
|
|
10209
10618
|
provider = resolveProvider2(req.provider);
|
|
10210
10619
|
} catch (err) {
|
|
10211
|
-
return {
|
|
10620
|
+
return {
|
|
10621
|
+
ok: false,
|
|
10622
|
+
error: errPermanent("rpc_failed", { message: err instanceof Error ? err.message : String(err) })
|
|
10623
|
+
};
|
|
10212
10624
|
}
|
|
10213
10625
|
const repo = req.repo ?? defaultRepoName2(tracking.cwd);
|
|
10214
10626
|
try {
|
|
10215
10627
|
checkRepoName2(repo);
|
|
10216
10628
|
} catch (err) {
|
|
10217
|
-
return {
|
|
10629
|
+
return {
|
|
10630
|
+
ok: false,
|
|
10631
|
+
error: errUsage("invalid_repo_name", {
|
|
10632
|
+
message: err instanceof Error ? err.message : String(err),
|
|
10633
|
+
received: repo
|
|
10634
|
+
})
|
|
10635
|
+
};
|
|
10218
10636
|
}
|
|
10219
10637
|
await updateSession(tracking.cwd, req.sessionId, {
|
|
10220
10638
|
handoff: {
|
|
@@ -10234,10 +10652,23 @@ async function handleRequest(req) {
|
|
|
10234
10652
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
10235
10653
|
const session = getSession(tracking.cwd, req.sessionId);
|
|
10236
10654
|
if (!session.handoff) {
|
|
10237
|
-
return {
|
|
10655
|
+
return {
|
|
10656
|
+
ok: false,
|
|
10657
|
+
error: errNotFound("no_queued_handoff", {
|
|
10658
|
+
message: `Session ${req.sessionId} has no queued handoff.`,
|
|
10659
|
+
received: req.sessionId
|
|
10660
|
+
})
|
|
10661
|
+
};
|
|
10238
10662
|
}
|
|
10239
10663
|
if (session.handoff.sentAt) {
|
|
10240
|
-
return {
|
|
10664
|
+
return {
|
|
10665
|
+
ok: false,
|
|
10666
|
+
error: errConflict("already_shipped", {
|
|
10667
|
+
message: `Session ${req.sessionId} has already shipped to cloud \u2014 use \`sis cloud handoff pull\` to bring it back.`,
|
|
10668
|
+
received: req.sessionId,
|
|
10669
|
+
next: "sis cloud handoff pull"
|
|
10670
|
+
})
|
|
10671
|
+
};
|
|
10241
10672
|
}
|
|
10242
10673
|
await updateSession(tracking.cwd, req.sessionId, { handoff: void 0 });
|
|
10243
10674
|
return { ok: true, data: { cancelled: true } };
|
|
@@ -10247,7 +10678,13 @@ async function handleRequest(req) {
|
|
|
10247
10678
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
10248
10679
|
const session = getSession(tracking.cwd, req.sessionId);
|
|
10249
10680
|
if (!session.handoff?.sentAt) {
|
|
10250
|
-
return {
|
|
10681
|
+
return {
|
|
10682
|
+
ok: false,
|
|
10683
|
+
error: errConflict("not_on_cloud", {
|
|
10684
|
+
message: `Session ${req.sessionId} was never sent to cloud; nothing to reclaim.`,
|
|
10685
|
+
received: req.sessionId
|
|
10686
|
+
})
|
|
10687
|
+
};
|
|
10251
10688
|
}
|
|
10252
10689
|
await updateSession(tracking.cwd, req.sessionId, {
|
|
10253
10690
|
handoff: { ...session.handoff, reclaimedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
@@ -10259,7 +10696,14 @@ async function handleRequest(req) {
|
|
|
10259
10696
|
if (!tracking) return unknownSessionError(req.sessionId);
|
|
10260
10697
|
const session = getSession(tracking.cwd, req.sessionId);
|
|
10261
10698
|
if (session.handoff?.sentAt) {
|
|
10262
|
-
return {
|
|
10699
|
+
return {
|
|
10700
|
+
ok: false,
|
|
10701
|
+
error: errConflict("already_on_cloud", {
|
|
10702
|
+
message: `Session ${req.sessionId} already lives on cloud; nothing to quiesce locally.`,
|
|
10703
|
+
received: req.sessionId,
|
|
10704
|
+
next: "sis cloud handoff pull"
|
|
10705
|
+
})
|
|
10706
|
+
};
|
|
10263
10707
|
}
|
|
10264
10708
|
await updateSession(tracking.cwd, req.sessionId, {
|
|
10265
10709
|
handoff: {
|
|
@@ -10275,17 +10719,23 @@ async function handleRequest(req) {
|
|
|
10275
10719
|
return { ok: true, data: { queued: true, force: false } };
|
|
10276
10720
|
}
|
|
10277
10721
|
default:
|
|
10278
|
-
return {
|
|
10722
|
+
return {
|
|
10723
|
+
ok: false,
|
|
10724
|
+
error: errUsage("unknown_request_type", {
|
|
10725
|
+
message: `Unknown request type: ${req.type}`,
|
|
10726
|
+
received: req.type
|
|
10727
|
+
})
|
|
10728
|
+
};
|
|
10279
10729
|
}
|
|
10280
10730
|
} catch (err) {
|
|
10281
10731
|
const message = err instanceof Error ? err.message : String(err);
|
|
10282
|
-
return { ok: false, error: message };
|
|
10732
|
+
return { ok: false, error: errPermanent("internal_error", { message }) };
|
|
10283
10733
|
}
|
|
10284
10734
|
}
|
|
10285
10735
|
function startServer() {
|
|
10286
10736
|
return new Promise((resolve13, reject) => {
|
|
10287
10737
|
const sock = socketPath();
|
|
10288
|
-
if (
|
|
10738
|
+
if (existsSync28(sock)) {
|
|
10289
10739
|
unlinkSync4(sock);
|
|
10290
10740
|
}
|
|
10291
10741
|
server = createServer((conn) => {
|
|
@@ -10300,7 +10750,7 @@ function startServer() {
|
|
|
10300
10750
|
try {
|
|
10301
10751
|
req = JSON.parse(line);
|
|
10302
10752
|
} catch {
|
|
10303
|
-
conn.write(JSON.stringify({ ok: false, error: "Invalid JSON" }) + "\n");
|
|
10753
|
+
conn.write(JSON.stringify({ ok: false, error: errUsage("invalid_json", { message: "Invalid JSON" }) }) + "\n");
|
|
10304
10754
|
continue;
|
|
10305
10755
|
}
|
|
10306
10756
|
handleRequest(req).then((res) => {
|
|
@@ -10310,7 +10760,7 @@ function startServer() {
|
|
|
10310
10760
|
}).catch((err) => {
|
|
10311
10761
|
console.warn("[sisyphus] Unhandled request error:", err instanceof Error ? err.message : err);
|
|
10312
10762
|
if (!conn.destroyed) {
|
|
10313
|
-
conn.write(JSON.stringify({ ok: false, error: "Internal server error" }) + "\n");
|
|
10763
|
+
conn.write(JSON.stringify({ ok: false, error: errPermanent("internal_error", { message: "Internal server error" }) }) + "\n");
|
|
10314
10764
|
}
|
|
10315
10765
|
});
|
|
10316
10766
|
}
|
|
@@ -10340,7 +10790,7 @@ function stopServer() {
|
|
|
10340
10790
|
}
|
|
10341
10791
|
server.close(() => {
|
|
10342
10792
|
const sock = socketPath();
|
|
10343
|
-
if (
|
|
10793
|
+
if (existsSync28(sock)) {
|
|
10344
10794
|
unlinkSync4(sock);
|
|
10345
10795
|
}
|
|
10346
10796
|
server = null;
|
|
@@ -10353,6 +10803,7 @@ var init_server = __esm({
|
|
|
10353
10803
|
"src/daemon/server.ts"() {
|
|
10354
10804
|
"use strict";
|
|
10355
10805
|
init_paths();
|
|
10806
|
+
init_protocol();
|
|
10356
10807
|
init_shell();
|
|
10357
10808
|
init_session_manager();
|
|
10358
10809
|
init_companion();
|
|
@@ -10367,6 +10818,7 @@ var init_server = __esm({
|
|
|
10367
10818
|
init_orchestrator();
|
|
10368
10819
|
init_agent();
|
|
10369
10820
|
init_tmux();
|
|
10821
|
+
init_paths();
|
|
10370
10822
|
server = null;
|
|
10371
10823
|
compositor = null;
|
|
10372
10824
|
sessionTrackingMap = /* @__PURE__ */ new Map();
|
|
@@ -10378,9 +10830,10 @@ init_paths();
|
|
|
10378
10830
|
init_config();
|
|
10379
10831
|
init_server();
|
|
10380
10832
|
init_orphan_sweep();
|
|
10381
|
-
import { mkdirSync as mkdirSync15, readFileSync as
|
|
10833
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync29, writeFileSync as writeFileSync17, unlinkSync as unlinkSync6, existsSync as existsSync32 } from "fs";
|
|
10382
10834
|
import { execSync as execSync8 } from "child_process";
|
|
10383
10835
|
import { setTimeout as sleep } from "timers/promises";
|
|
10836
|
+
import { Command } from "commander";
|
|
10384
10837
|
|
|
10385
10838
|
// src/daemon/heartbeat-asks.ts
|
|
10386
10839
|
init_ask_store();
|
|
@@ -10388,7 +10841,7 @@ init_state();
|
|
|
10388
10841
|
init_server();
|
|
10389
10842
|
init_paths();
|
|
10390
10843
|
import { ulid as ulid3 } from "ulid";
|
|
10391
|
-
import { existsSync as
|
|
10844
|
+
import { existsSync as existsSync29 } from "fs";
|
|
10392
10845
|
var HEARTBEAT_ASKED_BY2 = "system:heartbeat";
|
|
10393
10846
|
var HEARTBEAT_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
10394
10847
|
var HEARTBEAT_SCAN_INTERVAL_MS = 15 * 60 * 1e3;
|
|
@@ -10443,17 +10896,47 @@ async function emitHeartbeatAsk(cwd, sessionId, original) {
|
|
|
10443
10896
|
});
|
|
10444
10897
|
writeDecisions(cwd, sessionId, askId, deck);
|
|
10445
10898
|
await updateMeta(cwd, sessionId, original.askId, {
|
|
10446
|
-
heartbeatNotifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10899
|
+
heartbeatNotifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10900
|
+
heartbeatAskId: askId
|
|
10447
10901
|
});
|
|
10448
10902
|
}
|
|
10903
|
+
function isModeGateStale(deck, currentMode) {
|
|
10904
|
+
const source = deck.source;
|
|
10905
|
+
const chain = source?.modeChain;
|
|
10906
|
+
if (!chain || chain.length === 0) return false;
|
|
10907
|
+
if (!currentMode) return false;
|
|
10908
|
+
return !chain.some((e) => e.mode === currentMode);
|
|
10909
|
+
}
|
|
10449
10910
|
async function scanSessionForStaleAsks(cwd, sessionId) {
|
|
10450
10911
|
const now = Date.now();
|
|
10912
|
+
let currentMode;
|
|
10913
|
+
try {
|
|
10914
|
+
const session = getSession(cwd, sessionId);
|
|
10915
|
+
currentMode = session.orchestratorCycles[session.orchestratorCycles.length - 1]?.mode;
|
|
10916
|
+
} catch {
|
|
10917
|
+
}
|
|
10451
10918
|
for (const askId of listAsks(cwd, sessionId)) {
|
|
10452
10919
|
try {
|
|
10453
10920
|
const meta = readMeta(cwd, sessionId, askId);
|
|
10454
10921
|
if (!meta) continue;
|
|
10455
10922
|
if (meta.status === "answered") continue;
|
|
10456
10923
|
if (meta.orphaned) continue;
|
|
10924
|
+
if (meta.modeTransition === true) {
|
|
10925
|
+
const deck = readDecisions(cwd, sessionId, askId);
|
|
10926
|
+
if (deck && isModeGateStale(deck, currentMode)) {
|
|
10927
|
+
writeOutput(cwd, sessionId, askId, [{
|
|
10928
|
+
id: "mode-transition",
|
|
10929
|
+
selectedOptionId: "ack",
|
|
10930
|
+
freetext: "auto-resolved: session advanced past mode-transition"
|
|
10931
|
+
}]);
|
|
10932
|
+
await updateMeta(cwd, sessionId, askId, {
|
|
10933
|
+
status: "answered",
|
|
10934
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10935
|
+
});
|
|
10936
|
+
console.log(`[sisyphus] mode-gate auto-resolved ask ${askId} for session ${sessionId} (currentMode: ${currentMode})`);
|
|
10937
|
+
}
|
|
10938
|
+
continue;
|
|
10939
|
+
}
|
|
10457
10940
|
if (meta.heartbeatNotifiedAt) continue;
|
|
10458
10941
|
if (meta.askedBy === HEARTBEAT_ASKED_BY2) continue;
|
|
10459
10942
|
const askedAtMs = new Date(meta.askedAt).getTime();
|
|
@@ -10471,7 +10954,7 @@ async function scanSessionForStaleAsks(cwd, sessionId) {
|
|
|
10471
10954
|
async function scanAllSessionsForStaleAsks() {
|
|
10472
10955
|
const reg = loadSessionRegistry();
|
|
10473
10956
|
for (const [sessionId, cwd] of Object.entries(reg)) {
|
|
10474
|
-
if (!
|
|
10957
|
+
if (!existsSync29(statePath(cwd, sessionId))) continue;
|
|
10475
10958
|
try {
|
|
10476
10959
|
await scanSessionForStaleAsks(cwd, sessionId);
|
|
10477
10960
|
} catch (err) {
|
|
@@ -10534,17 +11017,15 @@ var DEFAULT_STATUS_BAR_CONFIG = {
|
|
|
10534
11017
|
init_tmux();
|
|
10535
11018
|
init_status_dots();
|
|
10536
11019
|
init_companion();
|
|
10537
|
-
|
|
10538
|
-
init_shell();
|
|
10539
|
-
import { readFileSync as readFileSync26, existsSync as existsSync29 } from "fs";
|
|
11020
|
+
import { readFileSync as readFileSync27, existsSync as existsSync30 } from "fs";
|
|
10540
11021
|
import { homedir as homedir8 } from "os";
|
|
10541
11022
|
import { join as join22 } from "path";
|
|
10542
11023
|
var STATUS_BAR_BG = "#1d1e21";
|
|
10543
11024
|
var SESSION_ORDER_PATH = join22(homedir8(), ".config", "tmux", "session-order");
|
|
10544
11025
|
function getSessionOrder() {
|
|
10545
11026
|
try {
|
|
10546
|
-
if (!
|
|
10547
|
-
return
|
|
11027
|
+
if (!existsSync30(SESSION_ORDER_PATH)) return [];
|
|
11028
|
+
return readFileSync27(SESSION_ORDER_PATH, "utf-8").split("\n").filter(Boolean);
|
|
10548
11029
|
} catch {
|
|
10549
11030
|
return [];
|
|
10550
11031
|
}
|
|
@@ -10554,17 +11035,6 @@ var STATE_PRIORITY = {
|
|
|
10554
11035
|
stopped: 2,
|
|
10555
11036
|
idle: 1
|
|
10556
11037
|
};
|
|
10557
|
-
function listWindowsForSession(sessionName) {
|
|
10558
|
-
const output = execSafe(`tmux list-windows -t ${shellQuote(sessionName)} -F "#{window_index} #{window_id} #{window_name}"`);
|
|
10559
|
-
if (!output) return [];
|
|
10560
|
-
return output.split("\n").filter(Boolean).map((line) => {
|
|
10561
|
-
const parts = line.split(" ");
|
|
10562
|
-
const index = parseInt(parts[0], 10);
|
|
10563
|
-
const id = parts[1];
|
|
10564
|
-
const name = parts.slice(2).join(" ");
|
|
10565
|
-
return { index, id, name };
|
|
10566
|
-
});
|
|
10567
|
-
}
|
|
10568
11038
|
function leftArrow(fromBg, toBg) {
|
|
10569
11039
|
return `#[fg=${fromBg}]#[bg=${toBg}]\uE0B0`;
|
|
10570
11040
|
}
|
|
@@ -10585,6 +11055,11 @@ var Compositor = class {
|
|
|
10585
11055
|
segments = /* @__PURE__ */ new Map();
|
|
10586
11056
|
external = /* @__PURE__ */ new Map();
|
|
10587
11057
|
config;
|
|
11058
|
+
// Per-session diff cache: skip setSessionOption when the rendered string is
|
|
11059
|
+
// unchanged. With 12 sessions at 5s polling this is the difference between
|
|
11060
|
+
// 24 tmux subprocess spawns per render and 0 on a stable status bar.
|
|
11061
|
+
lastLeftBySession = /* @__PURE__ */ new Map();
|
|
11062
|
+
lastRightBySession = /* @__PURE__ */ new Map();
|
|
10588
11063
|
constructor(config) {
|
|
10589
11064
|
this.config = config;
|
|
10590
11065
|
}
|
|
@@ -10602,18 +11077,35 @@ var Compositor = class {
|
|
|
10602
11077
|
unregisterExternal(id) {
|
|
10603
11078
|
this.external.delete(id);
|
|
10604
11079
|
}
|
|
10605
|
-
render() {
|
|
10606
|
-
const ctx = this.buildContext();
|
|
11080
|
+
render(panesByWindow) {
|
|
11081
|
+
const ctx = this.buildContext(panesByWindow);
|
|
11082
|
+
const liveNames = /* @__PURE__ */ new Set();
|
|
10607
11083
|
for (const session of ctx.allSessions) {
|
|
11084
|
+
liveNames.add(session.name);
|
|
10608
11085
|
const sessionCtx = this.buildSessionContext(ctx, session.name);
|
|
10609
|
-
|
|
10610
|
-
|
|
11086
|
+
const left = this.composeLeft(sessionCtx);
|
|
11087
|
+
if (this.lastLeftBySession.get(session.name) !== left) {
|
|
11088
|
+
setSessionOption(session.name, "@sisyphus_left", left);
|
|
11089
|
+
this.lastLeftBySession.set(session.name, left);
|
|
11090
|
+
}
|
|
11091
|
+
const right = this.composeRight(sessionCtx);
|
|
11092
|
+
if (this.lastRightBySession.get(session.name) !== right) {
|
|
11093
|
+
setSessionOption(session.name, "@sisyphus_right", right);
|
|
11094
|
+
this.lastRightBySession.set(session.name, right);
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
for (const name of this.lastLeftBySession.keys()) {
|
|
11098
|
+
if (!liveNames.has(name)) this.lastLeftBySession.delete(name);
|
|
11099
|
+
}
|
|
11100
|
+
for (const name of this.lastRightBySession.keys()) {
|
|
11101
|
+
if (!liveNames.has(name)) this.lastRightBySession.delete(name);
|
|
10611
11102
|
}
|
|
10612
11103
|
}
|
|
10613
|
-
buildContext() {
|
|
11104
|
+
buildContext(panesByWindow) {
|
|
10614
11105
|
const allPanes = listAllPanes();
|
|
10615
11106
|
const allSessionEntries = listAllSessions();
|
|
10616
11107
|
const allSessions = allSessionEntries.map((e) => ({ name: e.name }));
|
|
11108
|
+
const panes = panesByWindow ?? listAllPanesByWindow();
|
|
10617
11109
|
const sessionStates = /* @__PURE__ */ new Map();
|
|
10618
11110
|
for (const { sessionName, paneId } of allPanes) {
|
|
10619
11111
|
const state = readClaudeState(paneId);
|
|
@@ -10626,10 +11118,7 @@ var Compositor = class {
|
|
|
10626
11118
|
const sisyphusPhases2 = getSisyphusPhases();
|
|
10627
11119
|
const sessionOrder = getSessionOrder();
|
|
10628
11120
|
const companion = loadCompanion();
|
|
10629
|
-
const windowsBySession =
|
|
10630
|
-
for (const session of allSessions) {
|
|
10631
|
-
windowsBySession.set(session.name, listWindowsForSession(session.name));
|
|
10632
|
-
}
|
|
11121
|
+
const windowsBySession = listAllWindowsBySession();
|
|
10633
11122
|
return {
|
|
10634
11123
|
allSessions,
|
|
10635
11124
|
allPanes,
|
|
@@ -10639,6 +11128,7 @@ var Compositor = class {
|
|
|
10639
11128
|
companion,
|
|
10640
11129
|
config: this.config,
|
|
10641
11130
|
windowsBySession,
|
|
11131
|
+
panesByWindow: panes,
|
|
10642
11132
|
prevBg: STATUS_BAR_BG,
|
|
10643
11133
|
currentSession: ""
|
|
10644
11134
|
// overwritten per-session in buildSessionContext
|
|
@@ -10928,7 +11418,6 @@ function createCompanionSegment() {
|
|
|
10928
11418
|
}
|
|
10929
11419
|
|
|
10930
11420
|
// src/daemon/segments/windows.ts
|
|
10931
|
-
init_tmux();
|
|
10932
11421
|
init_status_dots();
|
|
10933
11422
|
function createWindowsSegment() {
|
|
10934
11423
|
return {
|
|
@@ -10947,7 +11436,7 @@ function createWindowsSegment() {
|
|
|
10947
11436
|
if (!windows || windows.length === 0) return { content: "" };
|
|
10948
11437
|
const windowDots = /* @__PURE__ */ new Map();
|
|
10949
11438
|
for (const win of windows) {
|
|
10950
|
-
const panes =
|
|
11439
|
+
const panes = ctx.panesByWindow.get(win.id) ?? [];
|
|
10951
11440
|
let dots = "";
|
|
10952
11441
|
for (const { paneId } of panes) {
|
|
10953
11442
|
const state = readClaudeState(paneId);
|
|
@@ -11061,8 +11550,8 @@ function buildEntries() {
|
|
|
11061
11550
|
return entries;
|
|
11062
11551
|
}
|
|
11063
11552
|
function toTsv(entries) {
|
|
11064
|
-
const
|
|
11065
|
-
const lines = [`#ts:${
|
|
11553
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
11554
|
+
const lines = [`#ts:${ts}`];
|
|
11066
11555
|
for (const e of entries) {
|
|
11067
11556
|
lines.push(`${e.type} ${e.tmuxName} ${e.cwd} ${e.phase ?? "-"} ${e.tmuxSessionId}`);
|
|
11068
11557
|
}
|
|
@@ -11080,8 +11569,8 @@ function writeManifest() {
|
|
|
11080
11569
|
atomicWrite(sessionsManifestPath(), toJson(entries));
|
|
11081
11570
|
}
|
|
11082
11571
|
function writeEmptyManifest() {
|
|
11083
|
-
const
|
|
11084
|
-
atomicWrite(sessionsManifestTsvPath(), `#ts:${
|
|
11572
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
11573
|
+
atomicWrite(sessionsManifestTsvPath(), `#ts:${ts}
|
|
11085
11574
|
`);
|
|
11086
11575
|
atomicWrite(sessionsManifestPath(), JSON.stringify({ updatedAt: Date.now(), sessions: [] }, null, 2));
|
|
11087
11576
|
}
|
|
@@ -11209,7 +11698,7 @@ async function checkAndApply() {
|
|
|
11209
11698
|
console.error("[sisyphus] Auto-update check failed:", err);
|
|
11210
11699
|
}
|
|
11211
11700
|
}
|
|
11212
|
-
var UPDATE_INTERVAL_MS =
|
|
11701
|
+
var UPDATE_INTERVAL_MS = 15 * 60 * 1e3;
|
|
11213
11702
|
var updateTimer = null;
|
|
11214
11703
|
function startPeriodicUpdateCheck() {
|
|
11215
11704
|
if (isLinkedInstall()) return;
|
|
@@ -11226,7 +11715,7 @@ function stopPeriodicUpdateCheck() {
|
|
|
11226
11715
|
}
|
|
11227
11716
|
|
|
11228
11717
|
// src/daemon/plugin-install.ts
|
|
11229
|
-
import { copyFileSync as copyFileSync6, mkdirSync as mkdirSync14, readdirSync as readdirSync13, statSync as statSync4, existsSync as
|
|
11718
|
+
import { copyFileSync as copyFileSync6, mkdirSync as mkdirSync14, readdirSync as readdirSync13, statSync as statSync4, existsSync as existsSync31, readFileSync as readFileSync28, chmodSync as chmodSync3 } from "fs";
|
|
11230
11719
|
import { join as join23, resolve as resolve12 } from "path";
|
|
11231
11720
|
import { homedir as homedir9 } from "os";
|
|
11232
11721
|
var PLUGIN_NAME = "sisyphus-tmux";
|
|
@@ -11240,7 +11729,7 @@ function copyDir(src, dest) {
|
|
|
11240
11729
|
copyDir(srcPath, destPath);
|
|
11241
11730
|
} else {
|
|
11242
11731
|
const srcMtime = statSync4(srcPath).mtimeMs;
|
|
11243
|
-
const destMtime =
|
|
11732
|
+
const destMtime = existsSync31(destPath) ? statSync4(destPath).mtimeMs : 0;
|
|
11244
11733
|
if (srcMtime > destMtime) {
|
|
11245
11734
|
copyFileSync6(srcPath, destPath);
|
|
11246
11735
|
}
|
|
@@ -11250,16 +11739,16 @@ function copyDir(src, dest) {
|
|
|
11250
11739
|
function pluginNeedsUpdate(sourceDir) {
|
|
11251
11740
|
const srcHooks = join23(sourceDir, "hooks", "hooks.json");
|
|
11252
11741
|
const destHooks = join23(INSTALL_DIR, "hooks", "hooks.json");
|
|
11253
|
-
if (!
|
|
11742
|
+
if (!existsSync31(destHooks)) return true;
|
|
11254
11743
|
try {
|
|
11255
|
-
return
|
|
11744
|
+
return readFileSync28(srcHooks, "utf-8") !== readFileSync28(destHooks, "utf-8");
|
|
11256
11745
|
} catch {
|
|
11257
11746
|
return true;
|
|
11258
11747
|
}
|
|
11259
11748
|
}
|
|
11260
11749
|
function installPlugin() {
|
|
11261
11750
|
const sourceDir = resolve12(import.meta.dirname, "../templates/sisyphus-tmux-plugin");
|
|
11262
|
-
if (!
|
|
11751
|
+
if (!existsSync31(sourceDir)) {
|
|
11263
11752
|
console.error(`[plugin-install] Source dir not found: ${sourceDir}`);
|
|
11264
11753
|
return;
|
|
11265
11754
|
}
|
|
@@ -11267,7 +11756,7 @@ function installPlugin() {
|
|
|
11267
11756
|
try {
|
|
11268
11757
|
copyDir(sourceDir, INSTALL_DIR);
|
|
11269
11758
|
const hookScript = join23(INSTALL_DIR, "hooks", "tmux-state.sh");
|
|
11270
|
-
if (
|
|
11759
|
+
if (existsSync31(hookScript)) {
|
|
11271
11760
|
try {
|
|
11272
11761
|
chmodSync3(hookScript, 493);
|
|
11273
11762
|
} catch {
|
|
@@ -11322,18 +11811,13 @@ if (nodeVersion < 22) {
|
|
|
11322
11811
|
console.error(`[sisyphus] Node.js v22+ required (current: v${process.versions.node})`);
|
|
11323
11812
|
process.exit(1);
|
|
11324
11813
|
}
|
|
11325
|
-
var ts = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
11326
|
-
var origLog = console.log.bind(console);
|
|
11327
|
-
var origError = console.error.bind(console);
|
|
11328
|
-
console.log = (...args) => origLog(`[${ts()}]`, ...args);
|
|
11329
|
-
console.error = (...args) => origError(`[${ts()}]`, ...args);
|
|
11330
11814
|
function ensureDirs() {
|
|
11331
11815
|
mkdirSync15(globalDir(), { recursive: true });
|
|
11332
11816
|
}
|
|
11333
11817
|
function readPid() {
|
|
11334
11818
|
const pidFile = daemonPidPath();
|
|
11335
11819
|
try {
|
|
11336
|
-
const pid = parseInt(
|
|
11820
|
+
const pid = parseInt(readFileSync29(pidFile, "utf-8").trim(), 10);
|
|
11337
11821
|
return pid && isProcessAlive(pid) ? pid : null;
|
|
11338
11822
|
} catch {
|
|
11339
11823
|
return null;
|
|
@@ -11394,6 +11878,94 @@ function stopDaemon() {
|
|
|
11394
11878
|
releasePidLock();
|
|
11395
11879
|
return true;
|
|
11396
11880
|
}
|
|
11881
|
+
async function recoverOneSession(sessionId, cwd, tmuxIdSet, tmuxNameToId, panesByWindow) {
|
|
11882
|
+
const stateFile = statePath(cwd, sessionId);
|
|
11883
|
+
if (!existsSync32(stateFile)) return false;
|
|
11884
|
+
let session;
|
|
11885
|
+
try {
|
|
11886
|
+
session = JSON.parse(readFileSync29(stateFile, "utf-8"));
|
|
11887
|
+
} catch {
|
|
11888
|
+
console.error(`[sisyphus] Failed to read session state for ${sessionId}, skipping`);
|
|
11889
|
+
return false;
|
|
11890
|
+
}
|
|
11891
|
+
if (session.status !== "active" && session.status !== "paused") return false;
|
|
11892
|
+
registerSessionCwd(sessionId, cwd);
|
|
11893
|
+
resetAgentCounterFromState(sessionId, session.agents ?? []);
|
|
11894
|
+
if (!session.tmuxSessionName) return true;
|
|
11895
|
+
let sessionAlive = false;
|
|
11896
|
+
let currentTmuxId = session.tmuxSessionId;
|
|
11897
|
+
if (currentTmuxId && tmuxIdSet.has(currentTmuxId)) {
|
|
11898
|
+
sessionAlive = true;
|
|
11899
|
+
} else {
|
|
11900
|
+
currentTmuxId = void 0;
|
|
11901
|
+
}
|
|
11902
|
+
if (!sessionAlive) {
|
|
11903
|
+
const resolved = tmuxNameToId.get(session.tmuxSessionName);
|
|
11904
|
+
if (resolved) {
|
|
11905
|
+
currentTmuxId = resolved;
|
|
11906
|
+
sessionAlive = true;
|
|
11907
|
+
await updateSessionTmux(cwd, sessionId, session.tmuxSessionName, session.tmuxWindowId ?? "", currentTmuxId);
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
if (!sessionAlive) {
|
|
11911
|
+
if (session.status === "active") {
|
|
11912
|
+
await updateSessionStatus(cwd, sessionId, "paused");
|
|
11913
|
+
await updateSession(cwd, sessionId, { tmuxSessionId: void 0 });
|
|
11914
|
+
console.log(`[sisyphus] Session ${sessionId} paused: tmux session no longer exists`);
|
|
11915
|
+
}
|
|
11916
|
+
return true;
|
|
11917
|
+
}
|
|
11918
|
+
let windowId = session.tmuxWindowId;
|
|
11919
|
+
if (!windowId) {
|
|
11920
|
+
windowId = getFirstWindowId(currentTmuxId) ?? getFirstWindowId(session.tmuxSessionName) ?? void 0;
|
|
11921
|
+
if (windowId) {
|
|
11922
|
+
await updateSessionTmux(cwd, sessionId, session.tmuxSessionName, windowId, currentTmuxId);
|
|
11923
|
+
console.log(`[sisyphus] Discovered missing windowId ${windowId} for session ${sessionId}`);
|
|
11924
|
+
}
|
|
11925
|
+
}
|
|
11926
|
+
const livePanes = windowId ? panesByWindow.get(windowId) ?? listPanes(windowId) : [];
|
|
11927
|
+
if (livePanes.length === 0) {
|
|
11928
|
+
if (session.status === "active") {
|
|
11929
|
+
await updateSessionStatus(cwd, sessionId, "paused");
|
|
11930
|
+
console.log(`[sisyphus] Session ${sessionId} paused: tmux window no longer exists`);
|
|
11931
|
+
}
|
|
11932
|
+
return true;
|
|
11933
|
+
}
|
|
11934
|
+
initSessionMeta(currentTmuxId, cwd, sessionId);
|
|
11935
|
+
registerSessionTmux(sessionId, session.tmuxSessionName, windowId, currentTmuxId);
|
|
11936
|
+
setWindowId(sessionId, windowId);
|
|
11937
|
+
trackSession(sessionId, cwd, currentTmuxId, session.tmuxSessionName);
|
|
11938
|
+
updateTrackedWindow(sessionId, windowId);
|
|
11939
|
+
initTimers(sessionId, session);
|
|
11940
|
+
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
11941
|
+
const lastIncompleteCycle = [...session.orchestratorCycles].reverse().find((c) => !c.completedAt && c.paneId);
|
|
11942
|
+
if (lastIncompleteCycle?.paneId) {
|
|
11943
|
+
setOrchestratorPaneId(sessionId, lastIncompleteCycle.paneId);
|
|
11944
|
+
if (livePaneIds.has(lastIncompleteCycle.paneId)) {
|
|
11945
|
+
registerPane(lastIncompleteCycle.paneId, sessionId, "orchestrator");
|
|
11946
|
+
}
|
|
11947
|
+
}
|
|
11948
|
+
for (const agent of session.agents) {
|
|
11949
|
+
if (agent.status === "running" && agent.paneId && livePaneIds.has(agent.paneId)) {
|
|
11950
|
+
registerPane(agent.paneId, sessionId, "agent", agent.id);
|
|
11951
|
+
}
|
|
11952
|
+
}
|
|
11953
|
+
console.log(`[sisyphus] Reconnected session ${sessionId} to tmux window ${session.tmuxWindowId}`);
|
|
11954
|
+
if (session.status === "active" && session.agents.length > 0) {
|
|
11955
|
+
const hasRunningAgents = session.agents.some((a) => a.status === "running");
|
|
11956
|
+
if (!hasRunningAgents) {
|
|
11957
|
+
const orchestratorPaneId = getOrchestratorPaneId(sessionId);
|
|
11958
|
+
const orchestratorAlive = orchestratorPaneId && livePaneIds.has(orchestratorPaneId);
|
|
11959
|
+
if (!orchestratorAlive) {
|
|
11960
|
+
await orphanOrchestrator(cwd, sessionId, "orchestrator lost while daemon was down", "daemon-startup-stuck");
|
|
11961
|
+
await completeOrchestratorCycle(cwd, sessionId);
|
|
11962
|
+
console.log(`[sisyphus] Detected stuck session ${sessionId} on recovery: triggering orchestrator respawn`);
|
|
11963
|
+
await onAllAgentsDone2(sessionId, cwd, session.tmuxWindowId);
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
11966
|
+
}
|
|
11967
|
+
return true;
|
|
11968
|
+
}
|
|
11397
11969
|
async function recoverSessions() {
|
|
11398
11970
|
const registry = loadSessionRegistry();
|
|
11399
11971
|
const entries = Object.entries(registry);
|
|
@@ -11401,104 +11973,14 @@ async function recoverSessions() {
|
|
|
11401
11973
|
console.log("[sisyphus] No sessions to recover");
|
|
11402
11974
|
return;
|
|
11403
11975
|
}
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
if (session.status === "active" || session.status === "paused") {
|
|
11413
|
-
registerSessionCwd(sessionId, cwd);
|
|
11414
|
-
resetAgentCounterFromState(sessionId, session.agents ?? []);
|
|
11415
|
-
if (session.tmuxSessionName) {
|
|
11416
|
-
let sessionAlive = false;
|
|
11417
|
-
let currentTmuxId = session.tmuxSessionId;
|
|
11418
|
-
if (currentTmuxId) {
|
|
11419
|
-
sessionAlive = sessionExistsById(currentTmuxId);
|
|
11420
|
-
if (!sessionAlive) {
|
|
11421
|
-
currentTmuxId = void 0;
|
|
11422
|
-
}
|
|
11423
|
-
}
|
|
11424
|
-
if (!sessionAlive && session.tmuxSessionName) {
|
|
11425
|
-
if (sessionNameTaken(session.tmuxSessionName)) {
|
|
11426
|
-
currentTmuxId = resolveSessionId(session.tmuxSessionName) ?? void 0;
|
|
11427
|
-
sessionAlive = !!currentTmuxId;
|
|
11428
|
-
if (currentTmuxId) {
|
|
11429
|
-
await updateSessionTmux(cwd, sessionId, session.tmuxSessionName, session.tmuxWindowId ?? "", currentTmuxId);
|
|
11430
|
-
}
|
|
11431
|
-
}
|
|
11432
|
-
}
|
|
11433
|
-
if (!sessionAlive) {
|
|
11434
|
-
if (session.status === "active") {
|
|
11435
|
-
await updateSessionStatus(cwd, sessionId, "paused");
|
|
11436
|
-
await updateSession(cwd, sessionId, { tmuxSessionId: void 0 });
|
|
11437
|
-
console.log(`[sisyphus] Session ${sessionId} paused: tmux session no longer exists`);
|
|
11438
|
-
}
|
|
11439
|
-
recovered++;
|
|
11440
|
-
continue;
|
|
11441
|
-
}
|
|
11442
|
-
let windowId = session.tmuxWindowId;
|
|
11443
|
-
if (!windowId) {
|
|
11444
|
-
windowId = getFirstWindowId(currentTmuxId) ?? getFirstWindowId(session.tmuxSessionName) ?? void 0;
|
|
11445
|
-
if (windowId) {
|
|
11446
|
-
await updateSessionTmux(cwd, sessionId, session.tmuxSessionName, windowId, currentTmuxId);
|
|
11447
|
-
console.log(`[sisyphus] Discovered missing windowId ${windowId} for session ${sessionId}`);
|
|
11448
|
-
}
|
|
11449
|
-
}
|
|
11450
|
-
const livePanes = windowId ? listPanes(windowId) : [];
|
|
11451
|
-
if (livePanes.length > 0) {
|
|
11452
|
-
initSessionMeta(currentTmuxId, cwd, sessionId);
|
|
11453
|
-
registerSessionTmux(sessionId, session.tmuxSessionName, windowId, currentTmuxId);
|
|
11454
|
-
setWindowId(sessionId, windowId);
|
|
11455
|
-
trackSession(sessionId, cwd, currentTmuxId, session.tmuxSessionName);
|
|
11456
|
-
updateTrackedWindow(sessionId, windowId);
|
|
11457
|
-
initTimers(sessionId, session);
|
|
11458
|
-
const lastIncompleteCycle = [...session.orchestratorCycles].reverse().find((c) => !c.completedAt && c.paneId);
|
|
11459
|
-
if (lastIncompleteCycle?.paneId) {
|
|
11460
|
-
setOrchestratorPaneId(sessionId, lastIncompleteCycle.paneId);
|
|
11461
|
-
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
11462
|
-
if (livePaneIds.has(lastIncompleteCycle.paneId)) {
|
|
11463
|
-
registerPane(lastIncompleteCycle.paneId, sessionId, "orchestrator");
|
|
11464
|
-
}
|
|
11465
|
-
}
|
|
11466
|
-
for (const agent of session.agents) {
|
|
11467
|
-
if (agent.status === "running" && agent.paneId) {
|
|
11468
|
-
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
11469
|
-
if (livePaneIds.has(agent.paneId)) {
|
|
11470
|
-
registerPane(agent.paneId, sessionId, "agent", agent.id);
|
|
11471
|
-
}
|
|
11472
|
-
}
|
|
11473
|
-
}
|
|
11474
|
-
console.log(`[sisyphus] Reconnected session ${sessionId} to tmux window ${session.tmuxWindowId}`);
|
|
11475
|
-
if (session.status === "active" && session.agents.length > 0) {
|
|
11476
|
-
const hasRunningAgents = session.agents.some((a) => a.status === "running");
|
|
11477
|
-
if (!hasRunningAgents) {
|
|
11478
|
-
const livePaneIds = new Set(livePanes.map((p) => p.paneId));
|
|
11479
|
-
const orchestratorPaneId = getOrchestratorPaneId(sessionId);
|
|
11480
|
-
const orchestratorAlive = orchestratorPaneId && livePaneIds.has(orchestratorPaneId);
|
|
11481
|
-
if (!orchestratorAlive) {
|
|
11482
|
-
await orphanOrchestrator(cwd, sessionId, "orchestrator lost while daemon was down", "daemon-startup-stuck");
|
|
11483
|
-
await completeOrchestratorCycle(cwd, sessionId);
|
|
11484
|
-
console.log(`[sisyphus] Detected stuck session ${sessionId} on recovery: triggering orchestrator respawn`);
|
|
11485
|
-
await onAllAgentsDone2(sessionId, cwd, session.tmuxWindowId);
|
|
11486
|
-
}
|
|
11487
|
-
}
|
|
11488
|
-
}
|
|
11489
|
-
} else {
|
|
11490
|
-
if (session.status === "active") {
|
|
11491
|
-
await updateSessionStatus(cwd, sessionId, "paused");
|
|
11492
|
-
console.log(`[sisyphus] Session ${sessionId} paused: tmux window no longer exists`);
|
|
11493
|
-
}
|
|
11494
|
-
}
|
|
11495
|
-
}
|
|
11496
|
-
recovered++;
|
|
11497
|
-
}
|
|
11498
|
-
} catch {
|
|
11499
|
-
console.error(`[sisyphus] Failed to read session state for ${sessionId}, skipping`);
|
|
11500
|
-
}
|
|
11501
|
-
}
|
|
11976
|
+
const allTmuxSessions = listAllSessions();
|
|
11977
|
+
const tmuxIdSet = new Set(allTmuxSessions.map((s) => s.sessionId));
|
|
11978
|
+
const tmuxNameToId = new Map(allTmuxSessions.map((s) => [s.name, s.sessionId]));
|
|
11979
|
+
const panesByWindow = listAllPanesByWindow();
|
|
11980
|
+
const results = await Promise.all(
|
|
11981
|
+
entries.map(([sessionId, cwd]) => recoverOneSession(sessionId, cwd, tmuxIdSet, tmuxNameToId, panesByWindow))
|
|
11982
|
+
);
|
|
11983
|
+
const recovered = results.filter(Boolean).length;
|
|
11502
11984
|
console.log(`[sisyphus] Recovered ${recovered} session(s) from registry`);
|
|
11503
11985
|
}
|
|
11504
11986
|
async function startDaemon() {
|
|
@@ -11507,9 +11989,6 @@ async function startDaemon() {
|
|
|
11507
11989
|
startLogRotator();
|
|
11508
11990
|
installPlugin();
|
|
11509
11991
|
const config = loadConfig(process.cwd());
|
|
11510
|
-
if (config.autoUpdate !== false) {
|
|
11511
|
-
await checkAndApply();
|
|
11512
|
-
}
|
|
11513
11992
|
acquirePidLock();
|
|
11514
11993
|
const statusBarConfig = { ...DEFAULT_STATUS_BAR_CONFIG };
|
|
11515
11994
|
statusBarConfig.colors = { ...DEFAULT_STATUS_BAR_CONFIG.colors, ...config.statusBar?.colors };
|
|
@@ -11527,36 +12006,45 @@ async function startDaemon() {
|
|
|
11527
12006
|
compositor2.register(createWindowsSegment());
|
|
11528
12007
|
compositor2.register(createSessionNameSegment());
|
|
11529
12008
|
setCompositor(compositor2);
|
|
11530
|
-
|
|
11531
|
-
|
|
12009
|
+
let tickCounter = 0;
|
|
12010
|
+
const renderEvery = Math.max(1, config.statusBarRenderTicks ?? 4);
|
|
12011
|
+
setDotsCallback((panesByWindow) => {
|
|
11532
12012
|
try {
|
|
11533
12013
|
writeManifest();
|
|
11534
12014
|
} catch {
|
|
11535
12015
|
}
|
|
12016
|
+
tickCounter += 1;
|
|
12017
|
+
if (tickCounter % renderEvery !== 0) return;
|
|
12018
|
+
recomputeDots(panesByWindow);
|
|
11536
12019
|
try {
|
|
11537
|
-
compositor2.render();
|
|
12020
|
+
compositor2.render(panesByWindow);
|
|
11538
12021
|
} catch {
|
|
11539
12022
|
}
|
|
11540
12023
|
});
|
|
11541
12024
|
} else {
|
|
11542
|
-
|
|
11543
|
-
|
|
12025
|
+
let tickCounter = 0;
|
|
12026
|
+
const renderEvery = Math.max(1, config.statusBarRenderTicks ?? 4);
|
|
12027
|
+
setDotsCallback((panesByWindow) => {
|
|
11544
12028
|
try {
|
|
11545
12029
|
writeManifest();
|
|
11546
12030
|
} catch {
|
|
11547
12031
|
}
|
|
12032
|
+
tickCounter += 1;
|
|
12033
|
+
if (tickCounter % renderEvery !== 0) return;
|
|
12034
|
+
recomputeDots(panesByWindow);
|
|
11548
12035
|
});
|
|
11549
12036
|
}
|
|
11550
12037
|
setRespawnCallback(onAllAgentsDone2);
|
|
11551
12038
|
setTrackedEntriesProvider(getTrackedSessionEntries);
|
|
11552
12039
|
await startServer();
|
|
11553
12040
|
startMonitor(config.pollIntervalMs);
|
|
11554
|
-
await recoverSessions();
|
|
11555
|
-
await sweepOrphans();
|
|
11556
|
-
startHeartbeatScanner();
|
|
11557
12041
|
if (config.autoUpdate !== false) {
|
|
12042
|
+
void checkAndApply();
|
|
11558
12043
|
startPeriodicUpdateCheck();
|
|
11559
12044
|
}
|
|
12045
|
+
await recoverSessions();
|
|
12046
|
+
await sweepOrphans();
|
|
12047
|
+
startHeartbeatScanner();
|
|
11560
12048
|
const shutdown = async () => {
|
|
11561
12049
|
console.log("[sisyphus] Shutting down...");
|
|
11562
12050
|
stopLogRotator();
|
|
@@ -11581,50 +12069,110 @@ async function startDaemon() {
|
|
|
11581
12069
|
process.on("SIGINT", shutdown);
|
|
11582
12070
|
}
|
|
11583
12071
|
process.title = "sisyphusd";
|
|
11584
|
-
var
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
12072
|
+
var isHelpInvocation = process.argv.includes("--help") || process.argv[2] === "help";
|
|
12073
|
+
if (!isHelpInvocation) {
|
|
12074
|
+
const ts = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
12075
|
+
const origLog = console.log.bind(console);
|
|
12076
|
+
const origError = console.error.bind(console);
|
|
12077
|
+
console.log = (...args) => origLog(`[${ts()}]`, ...args);
|
|
12078
|
+
console.error = (...args) => origError(`[${ts()}]`, ...args);
|
|
12079
|
+
}
|
|
12080
|
+
var rootHelpText = `sisyphusd: long-lived orchestration daemon for sis sessions.
|
|
12081
|
+
|
|
12082
|
+
Subtrees
|
|
12083
|
+
start boot the daemon and recover sessions | use when no daemon is running for this user
|
|
12084
|
+
stop terminate the running daemon | use to free the IPC socket or before an upgrade
|
|
12085
|
+
restart stop, then start | use after a binary upgrade or config change
|
|
12086
|
+
|
|
12087
|
+
Globals
|
|
12088
|
+
--help print this message
|
|
12089
|
+
|
|
12090
|
+
I/O contract: subprocess control (no JSON; this is a process-lifecycle tool, not an agent surface).
|
|
12091
|
+
Exit 0 on success, non-zero on failure. Diagnostic output streams to stderr; structured payloads are absent \u2014 for session/agent state, use \`sis session inspect status\` and friends.
|
|
12092
|
+
`;
|
|
12093
|
+
var program = new Command().name("sisyphusd").description("Long-lived orchestration daemon for sis sessions.").helpOption("--help", "print this message").allowExcessArguments(false).showSuggestionAfterError(false).exitOverride((err) => {
|
|
12094
|
+
if (err.code === "commander.unknownCommand" || err.code === "commander.unknownOption" || err.code === "commander.excessArguments") {
|
|
12095
|
+
const offender = process.argv[2];
|
|
12096
|
+
if (offender) {
|
|
12097
|
+
process.stderr.write(`error: unknown command '${offender}'
|
|
12098
|
+
`);
|
|
12099
|
+
}
|
|
12100
|
+
process.exit(2);
|
|
12101
|
+
}
|
|
12102
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.help") {
|
|
12103
|
+
process.exit(0);
|
|
12104
|
+
}
|
|
12105
|
+
process.exit(err.exitCode ?? 1);
|
|
12106
|
+
}).action(async () => {
|
|
12107
|
+
await startDaemon();
|
|
12108
|
+
});
|
|
12109
|
+
program.helpInformation = () => rootHelpText;
|
|
12110
|
+
program.command("help").description("Print sisyphusd help").helpOption("--help", "print this message").action(() => {
|
|
12111
|
+
process.stdout.write(rootHelpText);
|
|
12112
|
+
process.exit(0);
|
|
12113
|
+
});
|
|
12114
|
+
program.command("start").description("Boot the daemon and recover sessions").helpOption("--help", "display help for command").addHelpText("after", `
|
|
12115
|
+
start: launch the sisyphus daemon process.
|
|
12116
|
+
|
|
12117
|
+
Input
|
|
12118
|
+
None.
|
|
12119
|
+
|
|
12120
|
+
Output (subprocess control; diagnostic text to stderr; no JSON \u2014 daemon-control carve-out)
|
|
12121
|
+
Progress messages stream to stderr. Blocks until the daemon event loop is running and sessions are recovered.
|
|
12122
|
+
|
|
12123
|
+
Effects
|
|
12124
|
+
Acquires the IPC socket. Writes pid file. Recovers active sessions from registry. Starts pane monitor and heartbeat scanner.
|
|
12125
|
+
|
|
12126
|
+
Exit codes: 0 ok | non-zero on failure (diagnostic on stderr).
|
|
12127
|
+
`).action(async () => {
|
|
12128
|
+
await startDaemon();
|
|
12129
|
+
});
|
|
12130
|
+
program.command("stop").description("Terminate the running daemon").helpOption("--help", "display help for command").addHelpText("after", `
|
|
12131
|
+
stop: terminate the running daemon process.
|
|
12132
|
+
|
|
12133
|
+
Input
|
|
12134
|
+
None.
|
|
12135
|
+
|
|
12136
|
+
Output (subprocess control; diagnostic text to stderr; no JSON \u2014 daemon-control carve-out)
|
|
12137
|
+
Reports daemon pid and stop status to stderr. Waits up to 5s for graceful SIGTERM; falls back to SIGKILL.
|
|
12138
|
+
|
|
12139
|
+
Effects
|
|
12140
|
+
Sends SIGTERM (then SIGKILL if needed). Removes pid file. Releases IPC socket.
|
|
12141
|
+
|
|
12142
|
+
Exit codes: 0 ok | non-zero on failure (diagnostic on stderr).
|
|
12143
|
+
`).action(() => {
|
|
12144
|
+
stopDaemon();
|
|
12145
|
+
});
|
|
12146
|
+
program.command("restart").description("Stop, then start the daemon").helpOption("--help", "display help for command").addHelpText("after", `
|
|
12147
|
+
restart: stop the running daemon, then start a fresh one.
|
|
12148
|
+
|
|
12149
|
+
Input
|
|
12150
|
+
None.
|
|
12151
|
+
|
|
12152
|
+
Output (subprocess control; diagnostic text to stderr; no JSON \u2014 daemon-control carve-out)
|
|
12153
|
+
Reports stop and start progress to stderr. When launchd manages the daemon, exits after confirming respawn.
|
|
12154
|
+
|
|
12155
|
+
Effects
|
|
12156
|
+
Stops the running daemon (SIGTERM/SIGKILL). If launchd-managed, exits and lets launchd respawn via KeepAlive. Otherwise starts the daemon in-process.
|
|
12157
|
+
|
|
12158
|
+
Exit codes: 0 ok | non-zero on failure (diagnostic on stderr).
|
|
12159
|
+
`).action(async () => {
|
|
12160
|
+
stopDaemon();
|
|
12161
|
+
if (isLaunchdManaged()) {
|
|
12162
|
+
for (let i = 0; i < 6; i++) {
|
|
12163
|
+
await sleep(500);
|
|
12164
|
+
const respawnedPid = readPid();
|
|
12165
|
+
if (respawnedPid) {
|
|
12166
|
+
console.log(`[sisyphus] Daemon restarted (pid ${respawnedPid}) by process manager`);
|
|
11602
12167
|
process.exit(0);
|
|
11603
12168
|
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
case "start":
|
|
11608
|
-
case void 0:
|
|
11609
|
-
await startDaemon();
|
|
11610
|
-
break;
|
|
11611
|
-
case "help":
|
|
11612
|
-
case "--help":
|
|
11613
|
-
case "-h":
|
|
11614
|
-
console.log("Usage: sisyphusd [command]");
|
|
11615
|
-
console.log("");
|
|
11616
|
-
console.log("Commands:");
|
|
11617
|
-
console.log(" start Start the daemon (default if no command given)");
|
|
11618
|
-
console.log(" stop Stop the running daemon");
|
|
11619
|
-
console.log(" restart Stop and restart the daemon");
|
|
11620
|
-
console.log(" help Show this help message");
|
|
11621
|
-
break;
|
|
11622
|
-
default:
|
|
11623
|
-
console.error(`[sisyphus] Unknown command: ${command}`);
|
|
11624
|
-
console.error("Usage: sisyphusd [start|stop|restart|help]");
|
|
11625
|
-
process.exit(1);
|
|
12169
|
+
}
|
|
12170
|
+
console.log("[sisyphus] Daemon will be restarted by process manager");
|
|
12171
|
+
process.exit(0);
|
|
11626
12172
|
}
|
|
11627
|
-
|
|
12173
|
+
await startDaemon();
|
|
12174
|
+
});
|
|
12175
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
11628
12176
|
console.error("[sisyphus] Fatal error:", err);
|
|
11629
12177
|
process.exit(1);
|
|
11630
12178
|
});
|