sisyphi 1.1.32 → 1.1.34
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 +1 -0
- package/dist/cli.js +132 -67
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +692 -322
- package/dist/daemon.js.map +1 -1
- package/dist/templates/agent-plugin/hooks/CLAUDE.md +4 -3
- package/dist/templates/agent-plugin/hooks/hooks.json +51 -0
- package/dist/tui.js +103 -41
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/agent-plugin/hooks/CLAUDE.md +4 -3
- package/templates/agent-plugin/hooks/hooks.json +51 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
-
|
|
2
|
-
-
|
|
3
|
-
-
|
|
1
|
+
- The bundled `hooks.json` here is the **manifest** — the daemon merges it with project (`.sisyphus/agent-plugin/hooks/hooks.json`) and user (`~/.sisyphus/agent-plugin/hooks/hooks.json`) layers at spawn time, then writes the merged result into the per-agent plugin dir. Script edits are invisible to running agents; **respawn required**.
|
|
2
|
+
- New hooks register declaratively: add a manifest entry with `agentTypes: [...]` (or `["all"]`) and the script gets copied automatically. No `src/daemon/agent.ts` edits needed for new agent-type-specific hooks. The merge logic lives in `src/daemon/extensions.ts` (`mergeHookManifests`, `collectReferencedHookScripts`).
|
|
3
|
+
- A bundled hook script is only copied into a spawned agent's plugin dir if the merged manifest references it for that agent type — scripts whose entries filter out (e.g. `plan-validate.sh` for a `review` agent) are skipped. Higher layers can suppress a bundled script by basename via `"disable": ["script.sh"]` at the manifest's top level.
|
|
4
|
+
- `interactive: true` in agent frontmatter triggers `condition: "non-interactive"` filtering — entries with that condition (currently the bundled `require-submit.sh` Stop hook) are dropped from the merged manifest for interactive agents.
|
|
4
5
|
- Scripts receive no `{{placeholder}}` substitution — placeholders appear as literal text, unlike `.md` templates.
|
|
5
6
|
- Prompt hooks (`userPrompt`, `systemPrompt`) write raw text to stdout. Pre-tool hooks (e.g. `intercept-send-message.sh`) write `{"decision":"block","reason":"..."}` or exit 0 — wrong format silently does nothing.
|
|
6
7
|
- Claude Code invokes hooks unconditionally, not only in sisyphus sessions. Guard: `if [ -z "$SISYPHUS_SESSION_ID" ]; then exit 0; fi`. Stop hooks also need `$SISYPHUS_AGENT_ID`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "SendMessage",
|
|
6
|
+
"agentTypes": ["all"],
|
|
7
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/intercept-send-message.sh" }]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"matcher": "Bash",
|
|
11
|
+
"agentTypes": ["all"],
|
|
12
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/ask-background-guard.sh" }]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Bash",
|
|
16
|
+
"agentTypes": ["plan"],
|
|
17
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/plan-validate.sh" }]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
21
|
+
"agentTypes": ["plan"],
|
|
22
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/plan-write-path.sh" }]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PostToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Task",
|
|
28
|
+
"agentTypes": ["all"],
|
|
29
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/register-bg-task.sh" }]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"Stop": [
|
|
33
|
+
{
|
|
34
|
+
"agentTypes": ["all"],
|
|
35
|
+
"condition": "non-interactive",
|
|
36
|
+
"hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/require-submit.sh" }]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"UserPromptSubmit": [
|
|
40
|
+
{ "agentTypes": ["problem"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/problem-user-prompt.sh" }] },
|
|
41
|
+
{ "agentTypes": ["plan"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/plan-user-prompt.sh" }] },
|
|
42
|
+
{ "agentTypes": ["spec"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/spec-user-prompt.sh" }] },
|
|
43
|
+
{ "agentTypes": ["review"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/review-user-prompt.sh" }] },
|
|
44
|
+
{ "agentTypes": ["review-plan"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/review-plan-user-prompt.sh" }] },
|
|
45
|
+
{ "agentTypes": ["debug"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/debug-user-prompt.sh" }] },
|
|
46
|
+
{ "agentTypes": ["operator"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/operator-user-prompt.sh" }] },
|
|
47
|
+
{ "agentTypes": ["test-spec"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/test-spec-user-prompt.sh" }] },
|
|
48
|
+
{ "agentTypes": ["explore"], "hooks": [{ "type": "command", "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/explore-user-prompt.sh" }] }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/tui.js
CHANGED
|
@@ -736,6 +736,35 @@ var init_atomic = __esm({
|
|
|
736
736
|
}
|
|
737
737
|
});
|
|
738
738
|
|
|
739
|
+
// src/shared/gitignore.ts
|
|
740
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
741
|
+
import { join as join8 } from "path";
|
|
742
|
+
var init_gitignore = __esm({
|
|
743
|
+
"src/shared/gitignore.ts"() {
|
|
744
|
+
"use strict";
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// src/shared/types.ts
|
|
749
|
+
var init_types = __esm({
|
|
750
|
+
"src/shared/types.ts"() {
|
|
751
|
+
"use strict";
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// src/daemon/state.ts
|
|
756
|
+
import { copyFileSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
757
|
+
import { join as join9 } from "path";
|
|
758
|
+
var init_state = __esm({
|
|
759
|
+
"src/daemon/state.ts"() {
|
|
760
|
+
"use strict";
|
|
761
|
+
init_atomic();
|
|
762
|
+
init_paths();
|
|
763
|
+
init_gitignore();
|
|
764
|
+
init_types();
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
739
768
|
// src/shared/shell.ts
|
|
740
769
|
function shellQuote(s) {
|
|
741
770
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
@@ -748,8 +777,8 @@ var init_shell = __esm({
|
|
|
748
777
|
|
|
749
778
|
// src/daemon/notify.ts
|
|
750
779
|
import { spawn, execFile as execFile2 } from "child_process";
|
|
751
|
-
import { writeFileSync as
|
|
752
|
-
import { join as
|
|
780
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
|
|
781
|
+
import { join as join10 } from "path";
|
|
753
782
|
import { homedir as homedir3 } from "os";
|
|
754
783
|
var TMUX_SOCKET, SWITCH_SCRIPT;
|
|
755
784
|
var init_notify = __esm({
|
|
@@ -799,35 +828,6 @@ var init_notify = __esm({
|
|
|
799
828
|
}
|
|
800
829
|
});
|
|
801
830
|
|
|
802
|
-
// src/shared/gitignore.ts
|
|
803
|
-
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
804
|
-
import { join as join9 } from "path";
|
|
805
|
-
var init_gitignore = __esm({
|
|
806
|
-
"src/shared/gitignore.ts"() {
|
|
807
|
-
"use strict";
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
// src/shared/types.ts
|
|
812
|
-
var init_types = __esm({
|
|
813
|
-
"src/shared/types.ts"() {
|
|
814
|
-
"use strict";
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
// src/daemon/state.ts
|
|
819
|
-
import { copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
820
|
-
import { join as join10 } from "path";
|
|
821
|
-
var init_state = __esm({
|
|
822
|
-
"src/daemon/state.ts"() {
|
|
823
|
-
"use strict";
|
|
824
|
-
init_atomic();
|
|
825
|
-
init_paths();
|
|
826
|
-
init_gitignore();
|
|
827
|
-
init_types();
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
|
|
831
831
|
// src/daemon/ask-store.ts
|
|
832
832
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "fs";
|
|
833
833
|
function readDecisions(cwd2, sessionId2, askId2) {
|
|
@@ -878,6 +878,7 @@ var init_ask_store = __esm({
|
|
|
878
878
|
init_paths();
|
|
879
879
|
init_config();
|
|
880
880
|
init_history();
|
|
881
|
+
init_state();
|
|
881
882
|
init_atomic();
|
|
882
883
|
init_notify();
|
|
883
884
|
init_state();
|
|
@@ -4114,7 +4115,7 @@ function findLatestReport(cwd2, sessionId2) {
|
|
|
4114
4115
|
return null;
|
|
4115
4116
|
}
|
|
4116
4117
|
}
|
|
4117
|
-
function goToSessionWindow(state2, actions) {
|
|
4118
|
+
async function goToSessionWindow(state2, actions) {
|
|
4118
4119
|
const session = state2.selectedSession;
|
|
4119
4120
|
if (!session || !state2.selectedSessionId) {
|
|
4120
4121
|
notify(state2, "No session selected");
|
|
@@ -4126,6 +4127,21 @@ function goToSessionWindow(state2, actions) {
|
|
|
4126
4127
|
actions.selectWindow(session.tmuxWindowId);
|
|
4127
4128
|
return;
|
|
4128
4129
|
}
|
|
4130
|
+
if (session.status !== "completed") {
|
|
4131
|
+
const sessionId2 = state2.selectedSessionId;
|
|
4132
|
+
const res = await actions.send({ type: "reconnect", sessionId: sessionId2, cwd: state2.cwd });
|
|
4133
|
+
if (res.ok) {
|
|
4134
|
+
const data = res.data ?? {};
|
|
4135
|
+
const tmuxName = data["tmuxSessionName"] ?? session.tmuxSessionName;
|
|
4136
|
+
const tmuxWin = data["tmuxWindowId"];
|
|
4137
|
+
const tmuxId = data["tmuxSessionId"];
|
|
4138
|
+
const switchTarget = tmuxId ?? tmuxName;
|
|
4139
|
+
if (switchTarget) actions.switchToSession(switchTarget);
|
|
4140
|
+
if (tmuxWin) actions.selectWindow(tmuxWin);
|
|
4141
|
+
notify(state2, `Reconnected ${tmuxName ?? sessionId2}`);
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4129
4145
|
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
4130
4146
|
const claudeSessionId = lastCycle?.claudeSessionId;
|
|
4131
4147
|
if (!claudeSessionId) {
|
|
@@ -4719,7 +4735,7 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
4719
4735
|
break;
|
|
4720
4736
|
}
|
|
4721
4737
|
case "go-to-window": {
|
|
4722
|
-
goToSessionWindow(state2, actions);
|
|
4738
|
+
void goToSessionWindow(state2, actions);
|
|
4723
4739
|
break;
|
|
4724
4740
|
}
|
|
4725
4741
|
case "clone-session": {
|
|
@@ -5088,7 +5104,7 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
5088
5104
|
return;
|
|
5089
5105
|
}
|
|
5090
5106
|
if (input === "w") {
|
|
5091
|
-
goToSessionWindow(state2, actions);
|
|
5107
|
+
void goToSessionWindow(state2, actions);
|
|
5092
5108
|
return;
|
|
5093
5109
|
}
|
|
5094
5110
|
if (input === "o") {
|
|
@@ -5277,6 +5293,35 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
5277
5293
|
requestRender();
|
|
5278
5294
|
return;
|
|
5279
5295
|
}
|
|
5296
|
+
if (input === "D") {
|
|
5297
|
+
const sessionId2 = state2.selectedSessionId;
|
|
5298
|
+
const session2 = state2.selectedSession;
|
|
5299
|
+
if (!sessionId2 || !session2) {
|
|
5300
|
+
notify(state2, "No session selected");
|
|
5301
|
+
return;
|
|
5302
|
+
}
|
|
5303
|
+
const next = !(session2.dangerousMode === true);
|
|
5304
|
+
session2.dangerousMode = next;
|
|
5305
|
+
requestRender();
|
|
5306
|
+
void (async () => {
|
|
5307
|
+
try {
|
|
5308
|
+
const res = await actions.send({ type: "set-dangerous-mode", sessionId: sessionId2, enabled: next });
|
|
5309
|
+
if (!res.ok) {
|
|
5310
|
+
if (state2.selectedSession === session2) session2.dangerousMode = !next;
|
|
5311
|
+
notify(state2, `Dangerous mode toggle failed: ${res.error}`);
|
|
5312
|
+
requestRender();
|
|
5313
|
+
return;
|
|
5314
|
+
}
|
|
5315
|
+
const flushed = res.data?.["flushed"] ?? 0;
|
|
5316
|
+
notify(state2, next ? flushed > 0 ? `DANGEROUS mode ON \u2014 ${flushed} pending ask(s) auto-resolved` : "DANGEROUS mode ON" : "DANGEROUS mode OFF");
|
|
5317
|
+
} catch (err) {
|
|
5318
|
+
if (state2.selectedSession === session2) session2.dangerousMode = !next;
|
|
5319
|
+
notify(state2, `Dangerous mode toggle failed: ${err.message}`);
|
|
5320
|
+
requestRender();
|
|
5321
|
+
}
|
|
5322
|
+
})();
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
5280
5325
|
if (input === "/") {
|
|
5281
5326
|
state2.mode = "search";
|
|
5282
5327
|
state2.searchText = "";
|
|
@@ -5470,17 +5515,30 @@ function listAllWindowIds() {
|
|
|
5470
5515
|
return /* @__PURE__ */ new Set();
|
|
5471
5516
|
}
|
|
5472
5517
|
}
|
|
5473
|
-
function registerDashboardWindow() {
|
|
5518
|
+
function registerDashboardWindow(cwd2) {
|
|
5474
5519
|
const wid = getWindowId();
|
|
5475
5520
|
const pane = process.env["TMUX_PANE"];
|
|
5521
|
+
let sessionTarget = null;
|
|
5476
5522
|
if (pane) {
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5523
|
+
sessionTarget = execSafe(`tmux display-message -t ${shellQuote(pane)} -p "#{session_id}"`)?.trim() || null;
|
|
5524
|
+
}
|
|
5525
|
+
if (sessionTarget) {
|
|
5526
|
+
execSafe(`tmux set-option -t ${shellQuote(sessionTarget)} @sisyphus_dashboard ${wid}`);
|
|
5527
|
+
} else {
|
|
5528
|
+
execSafe(`tmux set-option @sisyphus_dashboard ${wid}`);
|
|
5529
|
+
}
|
|
5530
|
+
if (cwd2) {
|
|
5531
|
+
const normalizedCwd = cwd2.replace(/\/+$/, "");
|
|
5532
|
+
const target = sessionTarget;
|
|
5533
|
+
const existing = target ? execSafe(`tmux show-options -t ${shellQuote(target)} -v @sisyphus_cwd`)?.trim() : execSafe(`tmux show-options -v @sisyphus_cwd`)?.trim();
|
|
5534
|
+
if (!existing) {
|
|
5535
|
+
if (target) {
|
|
5536
|
+
execSafe(`tmux set-option -t ${shellQuote(target)} @sisyphus_cwd ${shellQuote(normalizedCwd)}`);
|
|
5537
|
+
} else {
|
|
5538
|
+
execSafe(`tmux set-option @sisyphus_cwd ${shellQuote(normalizedCwd)}`);
|
|
5539
|
+
}
|
|
5481
5540
|
}
|
|
5482
5541
|
}
|
|
5483
|
-
execSafe(`tmux set-option @sisyphus_dashboard ${wid}`);
|
|
5484
5542
|
}
|
|
5485
5543
|
var companionPaneId = null;
|
|
5486
5544
|
function setupCompanionPlugin() {
|
|
@@ -8073,6 +8131,7 @@ init_render();
|
|
|
8073
8131
|
var B = ansiBold;
|
|
8074
8132
|
var D = ansiDim;
|
|
8075
8133
|
var SEP = D("\u2502 ");
|
|
8134
|
+
var DANGER_BADGE = "\x1B[1;41;97m DANGEROUS \x1B[0m";
|
|
8076
8135
|
function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
8077
8136
|
const { mode, focusPane, notification, error } = state2;
|
|
8078
8137
|
if (mode === "report-detail") return;
|
|
@@ -8110,6 +8169,9 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
|
8110
8169
|
}
|
|
8111
8170
|
content = contextFilePart + B("[enter]") + D(" select ") + B("[m]") + D("essage ") + B("[n]") + D("ew ") + B("[w]") + D(" tmux ") + SEP + B("[q]") + D("uit");
|
|
8112
8171
|
}
|
|
8172
|
+
if (state2.selectedSession?.dangerousMode === true) {
|
|
8173
|
+
content = `${DANGER_BADGE} ${content}`;
|
|
8174
|
+
}
|
|
8113
8175
|
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
8114
8176
|
}
|
|
8115
8177
|
|
|
@@ -8797,7 +8859,7 @@ if (askId && sessionId) {
|
|
|
8797
8859
|
await runSingleAsk2({ cwd, sessionId, askId });
|
|
8798
8860
|
process.exit(0);
|
|
8799
8861
|
}
|
|
8800
|
-
registerDashboardWindow();
|
|
8862
|
+
registerDashboardWindow(cwd);
|
|
8801
8863
|
var cleanup = setupTerminal();
|
|
8802
8864
|
var state = createAppState(cwd);
|
|
8803
8865
|
startApp(state, cleanup);
|