opencode-ralph-rlm 0.1.8 → 0.1.9
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 +4 -1
- package/dist/ralph-rlm.js +98 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,7 +221,8 @@ Create `.opencode/ralph.json`. All fields are optional — the plugin runs with
|
|
|
221
221
|
| `statusVerbosity` | `"normal"` | Supervisor status emission level: `minimal` (warnings/errors), `normal`, or `verbose`. |
|
|
222
222
|
| `maxAttempts` | `20` | Hard stop after this many failed verify attempts. |
|
|
223
223
|
| `heartbeatMinutes` | `15` | Warn if active strategist/worker has no progress for this many minutes. |
|
|
224
|
-
| `
|
|
224
|
+
| `verifyTimeoutMinutes` | `0` | Timeout for verify command in minutes. `0` disables timeouts. |
|
|
225
|
+
| `verify.command` | - | Shell command to run as an array, e.g. `["bun", "run", "verify"]`. If omitted, verify always returns `unknown`. |
|
|
225
226
|
| `verify.cwd` | `"."` | Working directory for the verify command, relative to the repo root. |
|
|
226
227
|
| `gateDestructiveToolsUntilContextLoaded` | `true` | Block `write`, `edit`, `bash`, etc. until `ralph_load_context()` has been called in the current attempt. |
|
|
227
228
|
| `maxRlmSliceLines` | `200` | Maximum lines a single `rlm_slice` call may return. |
|
|
@@ -439,6 +440,8 @@ Stop supervision for the current process. This prevents further auto-loop orches
|
|
|
439
440
|
|
|
440
441
|
- Use this when you want to pause/stop Ralph from spawning more sessions.
|
|
441
442
|
- Use this after verification passes and the user confirms they are done, or when the user asks to stop the loop.
|
|
443
|
+
- Active child sessions are aborted and any running verify command is stopped.
|
|
444
|
+
- Pass `delete_sessions: true` to delete child sessions after aborting them.
|
|
442
445
|
- Resume later with `ralph_create_supervisor_session(restart_if_done=true)`.
|
|
443
446
|
|
|
444
447
|
#### `ralph_supervision_status()`
|
package/dist/ralph-rlm.js
CHANGED
|
@@ -23417,6 +23417,7 @@ var RalphConfigSchema = exports_Schema.Struct({
|
|
|
23417
23417
|
statusVerbosity: exports_Schema.optional(exports_Schema.Union(exports_Schema.Literal("minimal"), exports_Schema.Literal("normal"), exports_Schema.Literal("verbose"))),
|
|
23418
23418
|
maxAttempts: exports_Schema.optional(exports_Schema.Number),
|
|
23419
23419
|
heartbeatMinutes: exports_Schema.optional(exports_Schema.Number),
|
|
23420
|
+
verifyTimeoutMinutes: exports_Schema.optional(exports_Schema.Number),
|
|
23420
23421
|
verify: exports_Schema.optional(VerifyConfigSchema),
|
|
23421
23422
|
gateDestructiveToolsUntilContextLoaded: exports_Schema.optional(exports_Schema.Boolean),
|
|
23422
23423
|
maxRlmSliceLines: exports_Schema.optional(exports_Schema.Number),
|
|
@@ -23455,6 +23456,7 @@ var CONFIG_DEFAULTS = {
|
|
|
23455
23456
|
statusVerbosity: "normal",
|
|
23456
23457
|
maxAttempts: 20,
|
|
23457
23458
|
heartbeatMinutes: 15,
|
|
23459
|
+
verifyTimeoutMinutes: 0,
|
|
23458
23460
|
gateDestructiveToolsUntilContextLoaded: true,
|
|
23459
23461
|
maxRlmSliceLines: 200,
|
|
23460
23462
|
requireGrepBeforeLargeSlice: true,
|
|
@@ -23480,6 +23482,7 @@ function resolveConfig(raw) {
|
|
|
23480
23482
|
statusVerbosity: raw.statusVerbosity ?? CONFIG_DEFAULTS.statusVerbosity,
|
|
23481
23483
|
maxAttempts: toBoundedInt(raw.maxAttempts, CONFIG_DEFAULTS.maxAttempts, 1, 500),
|
|
23482
23484
|
heartbeatMinutes: toBoundedInt(raw.heartbeatMinutes, CONFIG_DEFAULTS.heartbeatMinutes, 1, 240),
|
|
23485
|
+
verifyTimeoutMinutes: toBoundedInt(raw.verifyTimeoutMinutes, CONFIG_DEFAULTS.verifyTimeoutMinutes, 0, 240),
|
|
23483
23486
|
...verify !== undefined ? { verify } : {},
|
|
23484
23487
|
gateDestructiveToolsUntilContextLoaded: raw.gateDestructiveToolsUntilContextLoaded ?? CONFIG_DEFAULTS.gateDestructiveToolsUntilContextLoaded,
|
|
23485
23488
|
maxRlmSliceLines,
|
|
@@ -23762,7 +23765,26 @@ var writePendingInput = async (root, data) => {
|
|
|
23762
23765
|
await NodeFs.writeFile(p, JSON.stringify(data, null, 2), "utf8");
|
|
23763
23766
|
};
|
|
23764
23767
|
var sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23765
|
-
|
|
23768
|
+
var activeCommands = new Set;
|
|
23769
|
+
var stopCommand = (cmd, reason) => {
|
|
23770
|
+
if (cmd.child.killed)
|
|
23771
|
+
return;
|
|
23772
|
+
cmd.timedOut = cmd.timedOut || reason === "timeout";
|
|
23773
|
+
try {
|
|
23774
|
+
cmd.child.kill();
|
|
23775
|
+
} catch {}
|
|
23776
|
+
cmd.killTimer = setTimeout(() => {
|
|
23777
|
+
try {
|
|
23778
|
+
cmd.child.kill("SIGKILL");
|
|
23779
|
+
} catch {}
|
|
23780
|
+
}, 2000);
|
|
23781
|
+
};
|
|
23782
|
+
var stopAllCommands = (reason) => {
|
|
23783
|
+
for (const cmd of activeCommands) {
|
|
23784
|
+
stopCommand(cmd, reason);
|
|
23785
|
+
}
|
|
23786
|
+
};
|
|
23787
|
+
async function runCommand(command, cwd, options) {
|
|
23766
23788
|
return await new Promise((resolve) => {
|
|
23767
23789
|
const child = spawn(command[0] ?? "", command.slice(1), {
|
|
23768
23790
|
cwd,
|
|
@@ -23772,6 +23794,18 @@ async function runCommand(command, cwd) {
|
|
|
23772
23794
|
});
|
|
23773
23795
|
let stdout = "";
|
|
23774
23796
|
let stderr = "";
|
|
23797
|
+
const entry = {
|
|
23798
|
+
child,
|
|
23799
|
+
label: options?.label ?? command.join(" "),
|
|
23800
|
+
startedAt: Date.now(),
|
|
23801
|
+
timedOut: false
|
|
23802
|
+
};
|
|
23803
|
+
activeCommands.add(entry);
|
|
23804
|
+
if (options?.timeoutMs && options.timeoutMs > 0) {
|
|
23805
|
+
entry.timeoutTimer = setTimeout(() => {
|
|
23806
|
+
stopCommand(entry, "timeout");
|
|
23807
|
+
}, options.timeoutMs);
|
|
23808
|
+
}
|
|
23775
23809
|
child.stdout?.on("data", (chunk3) => {
|
|
23776
23810
|
stdout += String(chunk3);
|
|
23777
23811
|
});
|
|
@@ -23779,11 +23813,23 @@ async function runCommand(command, cwd) {
|
|
|
23779
23813
|
stderr += String(chunk3);
|
|
23780
23814
|
});
|
|
23781
23815
|
child.on("error", (err) => {
|
|
23816
|
+
if (entry.timeoutTimer)
|
|
23817
|
+
clearTimeout(entry.timeoutTimer);
|
|
23818
|
+
if (entry.killTimer)
|
|
23819
|
+
clearTimeout(entry.killTimer);
|
|
23820
|
+
activeCommands.delete(entry);
|
|
23782
23821
|
resolve({ ok: false, code: null, stdout, stderr: `${stderr}
|
|
23783
23822
|
${String(err)}`.trim() });
|
|
23784
23823
|
});
|
|
23785
23824
|
child.on("close", (code) => {
|
|
23786
|
-
|
|
23825
|
+
if (entry.timeoutTimer)
|
|
23826
|
+
clearTimeout(entry.timeoutTimer);
|
|
23827
|
+
if (entry.killTimer)
|
|
23828
|
+
clearTimeout(entry.killTimer);
|
|
23829
|
+
activeCommands.delete(entry);
|
|
23830
|
+
const timeoutNote = entry.timedOut ? `
|
|
23831
|
+
[ralph] command timed out` : "";
|
|
23832
|
+
resolve({ ok: code === 0 && !entry.timedOut, code, stdout, stderr: `${stderr}${timeoutNote}`.trim() });
|
|
23787
23833
|
});
|
|
23788
23834
|
});
|
|
23789
23835
|
}
|
|
@@ -24295,9 +24341,11 @@ var RalphRLM = async ({ client, $, worktree }) => {
|
|
|
24295
24341
|
}
|
|
24296
24342
|
const verifyCmd = cfg.verify.command;
|
|
24297
24343
|
const cwd = NodePath.join(root, cfg.verify.cwd ?? ".");
|
|
24344
|
+
const timeoutMs = cfg.verifyTimeoutMinutes > 0 ? cfg.verifyTimeoutMinutes * 60000 : null;
|
|
24298
24345
|
return yield* exports_Effect.tryPromise({
|
|
24299
24346
|
try: async () => {
|
|
24300
|
-
const
|
|
24347
|
+
const options = timeoutMs ? { timeoutMs, label: "verify" } : { label: "verify" };
|
|
24348
|
+
const result = await runCommand(verifyCmd, cwd, options);
|
|
24301
24349
|
if (result.ok) {
|
|
24302
24350
|
return JSON.stringify({ verdict: "pass", output: result.stdout }, null, 2);
|
|
24303
24351
|
}
|
|
@@ -25024,6 +25072,7 @@ No pending questions found.`));
|
|
|
25024
25072
|
description: "Stop Ralph supervision for this process. Prevents further auto-loop orchestration until restarted.",
|
|
25025
25073
|
args: {
|
|
25026
25074
|
reason: tool.schema.string().optional().describe("Optional reason for ending supervision."),
|
|
25075
|
+
delete_sessions: tool.schema.boolean().optional().describe("Also delete child sessions after aborting them (default false)."),
|
|
25027
25076
|
clear_binding: tool.schema.boolean().optional().describe("Clear supervisor session binding after stop (default false).")
|
|
25028
25077
|
},
|
|
25029
25078
|
async execute(args2, ctx) {
|
|
@@ -25031,6 +25080,15 @@ No pending questions found.`));
|
|
|
25031
25080
|
const reason = args2.reason?.trim();
|
|
25032
25081
|
supervisor.done = true;
|
|
25033
25082
|
supervisor.paused = true;
|
|
25083
|
+
const sessionsToAbort = Array.from(sessionMap.keys());
|
|
25084
|
+
for (const id of sessionsToAbort) {
|
|
25085
|
+
await client.session.abort({ path: { id } }).catch(() => {});
|
|
25086
|
+
if (args2.delete_sessions) {
|
|
25087
|
+
await client.session.delete({ path: { id } }).catch(() => {});
|
|
25088
|
+
}
|
|
25089
|
+
}
|
|
25090
|
+
stopAllCommands("supervision-ended");
|
|
25091
|
+
sessionMap.clear();
|
|
25034
25092
|
supervisor.currentRalphSessionId = undefined;
|
|
25035
25093
|
supervisor.currentWorkerSessionId = undefined;
|
|
25036
25094
|
supervisor.activeReviewerName = undefined;
|
|
@@ -25451,6 +25509,8 @@ Set a new goal and run again.
|
|
|
25451
25509
|
}
|
|
25452
25510
|
});
|
|
25453
25511
|
const emitHeartbeatWarnings = async () => {
|
|
25512
|
+
if (supervisor.done || supervisor.paused)
|
|
25513
|
+
return;
|
|
25454
25514
|
const cfg = await run(getConfig());
|
|
25455
25515
|
const thresholdMs = cfg.heartbeatMinutes * 60000;
|
|
25456
25516
|
const now2 = Date.now();
|
|
@@ -25473,6 +25533,30 @@ Set a new goal and run again.
|
|
|
25473
25533
|
await maybeWarn(supervisor.currentRalphSessionId, "Strategist");
|
|
25474
25534
|
await maybeWarn(supervisor.currentWorkerSessionId, "Worker");
|
|
25475
25535
|
};
|
|
25536
|
+
const clearSessionTracking = async (sessionId, reason) => {
|
|
25537
|
+
const st = sessionMap.get(sessionId);
|
|
25538
|
+
sessionMap.delete(sessionId);
|
|
25539
|
+
let didUpdate = false;
|
|
25540
|
+
if (supervisor.currentRalphSessionId === sessionId) {
|
|
25541
|
+
supervisor.currentRalphSessionId = undefined;
|
|
25542
|
+
didUpdate = true;
|
|
25543
|
+
}
|
|
25544
|
+
if (supervisor.currentWorkerSessionId === sessionId) {
|
|
25545
|
+
supervisor.currentWorkerSessionId = undefined;
|
|
25546
|
+
didUpdate = true;
|
|
25547
|
+
}
|
|
25548
|
+
if (supervisor.activeReviewerSessionId === sessionId) {
|
|
25549
|
+
supervisor.activeReviewerSessionId = undefined;
|
|
25550
|
+
supervisor.activeReviewerName = undefined;
|
|
25551
|
+
supervisor.activeReviewerAttempt = undefined;
|
|
25552
|
+
supervisor.activeReviewerOutputPath = undefined;
|
|
25553
|
+
didUpdate = true;
|
|
25554
|
+
await persistReviewerState();
|
|
25555
|
+
}
|
|
25556
|
+
if (didUpdate && st) {
|
|
25557
|
+
await notifySupervisor(`${st.role}/attempt-${st.attempt}`, `${st.role} session ended (${reason}).`, "info", true, sessionId);
|
|
25558
|
+
}
|
|
25559
|
+
};
|
|
25476
25560
|
const runAndParseVerify = async () => {
|
|
25477
25561
|
const raw = await run(runVerify(worktree));
|
|
25478
25562
|
try {
|
|
@@ -25740,7 +25824,13 @@ ${templates.systemPromptAppend}` : base;
|
|
|
25740
25824
|
getSession(sessionID);
|
|
25741
25825
|
}
|
|
25742
25826
|
if (event?.type === "session.status" && sessionID) {
|
|
25827
|
+
if (supervisor.done || supervisor.paused)
|
|
25828
|
+
return;
|
|
25743
25829
|
const state = sessionMap.get(sessionID);
|
|
25830
|
+
if (state?.role === "worker" && supervisor.currentWorkerSessionId !== sessionID)
|
|
25831
|
+
return;
|
|
25832
|
+
if (state?.role === "ralph" && supervisor.currentRalphSessionId !== sessionID)
|
|
25833
|
+
return;
|
|
25744
25834
|
if (state) {
|
|
25745
25835
|
mutateSession(sessionID, (s) => {
|
|
25746
25836
|
s.lastProgressAt = Date.now();
|
|
@@ -25752,6 +25842,8 @@ ${templates.systemPromptAppend}` : base;
|
|
|
25752
25842
|
}
|
|
25753
25843
|
}
|
|
25754
25844
|
if (event?.type === "session.idle" && sessionID) {
|
|
25845
|
+
if (supervisor.done || supervisor.paused)
|
|
25846
|
+
return;
|
|
25755
25847
|
await emitHeartbeatWarnings().catch((err) => {
|
|
25756
25848
|
appLog("error", "heartbeat warning error", { error: String(err) });
|
|
25757
25849
|
});
|
|
@@ -25769,6 +25861,9 @@ ${templates.systemPromptAppend}` : base;
|
|
|
25769
25861
|
});
|
|
25770
25862
|
}
|
|
25771
25863
|
}
|
|
25864
|
+
if (sessionID && (event?.type === "session.closed" || event?.type === "session.ended" || event?.type === "session.deleted")) {
|
|
25865
|
+
await clearSessionTracking(sessionID, event.type);
|
|
25866
|
+
}
|
|
25772
25867
|
}
|
|
25773
25868
|
};
|
|
25774
25869
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ralph-rlm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "OpenCode plugin: Ralph outer loop + RLM inner loop. Iterative AI development with file-first discipline and sub-agent support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/ralph-rlm.js",
|