opencode-ralph-rlm 0.1.6 → 0.1.8
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 +33 -0
- package/dist/ralph-rlm.js +89 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -265,6 +265,28 @@ If you are setting up a repo from scratch, use this sequence:
|
|
|
265
265
|
|
|
266
266
|
If setup is incomplete, auto-start is skipped and the plugin emits a warning with next actions.
|
|
267
267
|
|
|
268
|
+
## Recommended OpenCode agent setup
|
|
269
|
+
|
|
270
|
+
The plugin already manages the core loop roles (`main` / `ralph` / `worker` / `subagent`).
|
|
271
|
+
Use OpenCode agents to control posture and delegation, not to replace loop orchestration.
|
|
272
|
+
|
|
273
|
+
Recommended split:
|
|
274
|
+
|
|
275
|
+
- `supervisor` (primary): your default top-level operator for safe orchestration.
|
|
276
|
+
- built-in `plan` (primary): dry analysis/planning without edits.
|
|
277
|
+
- built-in `build` (primary): full manual implementation when needed.
|
|
278
|
+
- project subagents (optional): focused review/docs/security helpers.
|
|
279
|
+
|
|
280
|
+
This repo now includes project-local agent files under `.opencode/agents/`:
|
|
281
|
+
|
|
282
|
+
- `.opencode/agents/supervisor.md`
|
|
283
|
+
- `.opencode/agents/ralph-reviewer.md`
|
|
284
|
+
- `.opencode/agents/docs-writer.md`
|
|
285
|
+
- `.opencode/agents/security-auditor.md`
|
|
286
|
+
|
|
287
|
+
These profiles intentionally keep loop ownership in `ralph-rlm`.
|
|
288
|
+
Do not model Ralph strategist/worker as OpenCode primary/subagent replacements.
|
|
289
|
+
|
|
268
290
|
|
|
269
291
|
## Protocol files
|
|
270
292
|
|
|
@@ -416,6 +438,7 @@ Bind the current session as the supervisor and optionally start attempt 1 immedi
|
|
|
416
438
|
Stop supervision for the current process. This prevents further auto-loop orchestration until restarted.
|
|
417
439
|
|
|
418
440
|
- Use this when you want to pause/stop Ralph from spawning more sessions.
|
|
441
|
+
- Use this after verification passes and the user confirms they are done, or when the user asks to stop the loop.
|
|
419
442
|
- Resume later with `ralph_create_supervisor_session(restart_if_done=true)`.
|
|
420
443
|
|
|
421
444
|
#### `ralph_supervision_status()`
|
|
@@ -550,6 +573,16 @@ args:
|
|
|
550
573
|
post_to_conversation boolean optional Post to main conversation (default: true)
|
|
551
574
|
```
|
|
552
575
|
|
|
576
|
+
#### `ralph_peek_worker(maxLines?, post_to_conversation?)`
|
|
577
|
+
|
|
578
|
+
Snapshot the active worker's `CURRENT_STATE.md` and optionally post it into the main conversation for quick "peek" access in the TUI.
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
args:
|
|
582
|
+
maxLines number optional Max lines to include (default: 120)
|
|
583
|
+
post_to_conversation boolean optional Post to main conversation (default: true)
|
|
584
|
+
```
|
|
585
|
+
|
|
553
586
|
#### `ralph_ask(question, context?, timeout_minutes?)`
|
|
554
587
|
|
|
555
588
|
Ask a question and **block** until you respond via `ralph_respond()`. The question is written to `.opencode/pending_input.json`, a toast appears in the main session, and the main conversation is prompted with the question ID and response instruction. The calling session polls every 5 seconds.
|
package/dist/ralph-rlm.js
CHANGED
|
@@ -23509,6 +23509,7 @@ var DEFAULT_TEMPLATES = {
|
|
|
23509
23509
|
" When you receive one, call ralph_respond(id, answer) to unblock the session.",
|
|
23510
23510
|
"- Use ralph_doctor() to check setup, ralph_bootstrap_plan() to generate PLAN/TODOS,",
|
|
23511
23511
|
" ralph_create_supervisor_session() to bind/start explicitly, ralph_pause_supervision()/ralph_resume_supervision() to control execution, and ralph_end_supervision() to stop.",
|
|
23512
|
+
"- End supervision when verification has passed and the user confirms they are done, or when the user explicitly asks to stop the loop.",
|
|
23512
23513
|
"- Optional reviewer flow: worker marks readiness with ralph_request_review(); supervisor runs ralph_run_reviewer().",
|
|
23513
23514
|
"- Monitor progress in SUPERVISOR_LOG.md, CONVERSATION.md, or via toast notifications."
|
|
23514
23515
|
].join(`
|
|
@@ -24102,7 +24103,21 @@ var RalphRLM = async ({ client, $, worktree }) => {
|
|
|
24102
24103
|
- ${nowISO()} rotated
|
|
24103
24104
|
`)).catch(() => {});
|
|
24104
24105
|
};
|
|
24106
|
+
const DEDUPE_WINDOW_MS = 5000;
|
|
24107
|
+
const recentNotices = new Map;
|
|
24108
|
+
const shouldDedupe = (key) => {
|
|
24109
|
+
const now2 = Date.now();
|
|
24110
|
+
const last3 = recentNotices.get(key);
|
|
24111
|
+
if (last3 && now2 - last3 < DEDUPE_WINDOW_MS)
|
|
24112
|
+
return true;
|
|
24113
|
+
if (recentNotices.size > 200)
|
|
24114
|
+
recentNotices.clear();
|
|
24115
|
+
recentNotices.set(key, now2);
|
|
24116
|
+
return false;
|
|
24117
|
+
};
|
|
24105
24118
|
const appendConversationEntry = async (source, message) => {
|
|
24119
|
+
if (shouldDedupe(`conv|${source}|${message}`))
|
|
24120
|
+
return;
|
|
24106
24121
|
const cfg = await run(getConfig());
|
|
24107
24122
|
await rotateConversationLogIfNeeded(cfg);
|
|
24108
24123
|
const ts = nowISO();
|
|
@@ -24113,6 +24128,8 @@ var RalphRLM = async ({ client, $, worktree }) => {
|
|
|
24113
24128
|
});
|
|
24114
24129
|
};
|
|
24115
24130
|
const notifySupervisor = async (source, message, level = "info", postToConversation = true, originSessionId) => {
|
|
24131
|
+
if (shouldDedupe(`sup|${source}|${level}|${message}`))
|
|
24132
|
+
return;
|
|
24116
24133
|
const cfg = await run(getConfig());
|
|
24117
24134
|
if (!messagePassesVerbosity(cfg.statusVerbosity, level))
|
|
24118
24135
|
return;
|
|
@@ -24137,6 +24154,34 @@ var RalphRLM = async ({ client, $, worktree }) => {
|
|
|
24137
24154
|
}).catch(() => {});
|
|
24138
24155
|
}
|
|
24139
24156
|
};
|
|
24157
|
+
const detectProjectDefaults = (root) => exports_Effect.gen(function* () {
|
|
24158
|
+
const j = (f) => NodePath.join(root, f);
|
|
24159
|
+
const hasBunLock = (yield* fileExists(j("bun.lockb"))) || (yield* fileExists(j("bun.lock")));
|
|
24160
|
+
if (hasBunLock)
|
|
24161
|
+
return { verify: ["bun", "run", "verify"], install: "bun install" };
|
|
24162
|
+
const hasYarnLock = yield* fileExists(j("yarn.lock"));
|
|
24163
|
+
if (hasYarnLock)
|
|
24164
|
+
return { verify: ["yarn", "test"], install: "yarn install" };
|
|
24165
|
+
const hasPnpmLock = yield* fileExists(j("pnpm-lock.yaml"));
|
|
24166
|
+
if (hasPnpmLock)
|
|
24167
|
+
return { verify: ["pnpm", "test"], install: "pnpm install" };
|
|
24168
|
+
const hasPkg = yield* fileExists(j("package.json"));
|
|
24169
|
+
if (hasPkg)
|
|
24170
|
+
return { verify: ["npm", "test"], install: "npm install" };
|
|
24171
|
+
const hasCargo = yield* fileExists(j("Cargo.toml"));
|
|
24172
|
+
if (hasCargo)
|
|
24173
|
+
return { verify: ["cargo", "test"], install: "cargo build" };
|
|
24174
|
+
const hasPy = yield* fileExists(j("pyproject.toml"));
|
|
24175
|
+
const hasReq = yield* fileExists(j("requirements.txt"));
|
|
24176
|
+
if (hasReq)
|
|
24177
|
+
return { verify: ["python", "-m", "pytest"], install: "pip install -r requirements.txt" };
|
|
24178
|
+
if (hasPy)
|
|
24179
|
+
return { verify: ["python", "-m", "pytest"], install: "pip install ." };
|
|
24180
|
+
const hasMake = yield* fileExists(j("Makefile"));
|
|
24181
|
+
if (hasMake)
|
|
24182
|
+
return { verify: ["make", "test"], install: "make" };
|
|
24183
|
+
return { verify: ["bun", "run", "verify"], install: "bun install" };
|
|
24184
|
+
});
|
|
24140
24185
|
const checkSetup = async (root, cfg) => {
|
|
24141
24186
|
const diagnostics = {
|
|
24142
24187
|
ready: true,
|
|
@@ -24148,7 +24193,8 @@ var RalphRLM = async ({ client, $, worktree }) => {
|
|
|
24148
24193
|
if (!cfg.verify || cfg.verify.command.length === 0) {
|
|
24149
24194
|
diagnostics.ready = false;
|
|
24150
24195
|
diagnostics.issues.push("Missing verify.command in .opencode/ralph.json.");
|
|
24151
|
-
|
|
24196
|
+
const defaults = await run(detectProjectDefaults(root));
|
|
24197
|
+
diagnostics.suggestions.push(`Set verify.command, e.g. ${JSON.stringify(defaults.verify)}.`);
|
|
24152
24198
|
}
|
|
24153
24199
|
const planExists = await run(fileExists(j(FILES.PLAN)));
|
|
24154
24200
|
if (!planExists) {
|
|
@@ -24502,6 +24548,41 @@ ${args2.nextStep}
|
|
|
24502
24548
|
return run(runVerify(root));
|
|
24503
24549
|
}
|
|
24504
24550
|
});
|
|
24551
|
+
const tool_ralph_peek_worker = tool({
|
|
24552
|
+
description: "Snapshot the active RLM worker's CURRENT_STATE.md and optionally post it into the main conversation.",
|
|
24553
|
+
args: {
|
|
24554
|
+
maxLines: tool.schema.number().int().min(20).max(400).optional(),
|
|
24555
|
+
post_to_conversation: tool.schema.boolean().optional().describe("Whether to post the peek into the main conversation (default: true).")
|
|
24556
|
+
},
|
|
24557
|
+
async execute(args2, ctx) {
|
|
24558
|
+
const root = ctx.worktree ?? worktree;
|
|
24559
|
+
const currPath = NodePath.join(root, FILES.CURR);
|
|
24560
|
+
const ok = await run(fileExists(currPath));
|
|
24561
|
+
if (!ok)
|
|
24562
|
+
return JSON.stringify({ ok: false, missing: FILES.CURR }, null, 2);
|
|
24563
|
+
const raw = await run(readFile(currPath).pipe(exports_Effect.orElseSucceed(() => "")));
|
|
24564
|
+
const text = clampLines(raw, args2.maxLines ?? 120);
|
|
24565
|
+
const attempt = supervisor.attempt;
|
|
24566
|
+
const workerId = supervisor.currentWorkerSessionId;
|
|
24567
|
+
const header = `Worker peek${attempt ? ` (attempt ${attempt})` : ""}${workerId ? ` \u2014 ${workerId}` : ""}`;
|
|
24568
|
+
await notifySupervisor("peek", header, "info", false, ctx.sessionID);
|
|
24569
|
+
const postToConv = args2.post_to_conversation !== false;
|
|
24570
|
+
if (postToConv && supervisor.sessionId && supervisor.sessionId !== ctx.sessionID) {
|
|
24571
|
+
await client.session.promptAsync({
|
|
24572
|
+
path: { id: supervisor.sessionId },
|
|
24573
|
+
body: { parts: [{ type: "text", text: `[peek] ${header}
|
|
24574
|
+
|
|
24575
|
+
${text}` }] }
|
|
24576
|
+
}).catch(() => {});
|
|
24577
|
+
}
|
|
24578
|
+
return JSON.stringify({
|
|
24579
|
+
ok: true,
|
|
24580
|
+
attempt: attempt || null,
|
|
24581
|
+
workerSessionId: workerId ?? null,
|
|
24582
|
+
text
|
|
24583
|
+
}, null, 2);
|
|
24584
|
+
}
|
|
24585
|
+
});
|
|
24505
24586
|
const tool_subagent_peek = tool({
|
|
24506
24587
|
description: "Read file-first state from a sub-agent directory (.opencode/agents/<name>/).",
|
|
24507
24588
|
args: {
|
|
@@ -24777,6 +24858,7 @@ No pending questions found.`));
|
|
|
24777
24858
|
const diagnosticsBefore = await checkSetup(root, cfg);
|
|
24778
24859
|
const actions = [];
|
|
24779
24860
|
if (args2.autofix) {
|
|
24861
|
+
const defaults = await run(detectProjectDefaults(root));
|
|
24780
24862
|
const configPath = NodePath.join(root, ".opencode", "ralph.json");
|
|
24781
24863
|
const configExists = await run(fileExists(configPath));
|
|
24782
24864
|
if (!configExists) {
|
|
@@ -24786,7 +24868,7 @@ No pending questions found.`));
|
|
|
24786
24868
|
statusVerbosity: "normal",
|
|
24787
24869
|
maxAttempts: 25,
|
|
24788
24870
|
heartbeatMinutes: 15,
|
|
24789
|
-
verify: { command:
|
|
24871
|
+
verify: { command: defaults.verify, cwd: "." },
|
|
24790
24872
|
gateDestructiveToolsUntilContextLoaded: true,
|
|
24791
24873
|
maxRlmSliceLines: 200,
|
|
24792
24874
|
requireGrepBeforeLargeSlice: true,
|
|
@@ -24814,8 +24896,8 @@ No pending questions found.`));
|
|
|
24814
24896
|
"# Project Agent Rules",
|
|
24815
24897
|
"",
|
|
24816
24898
|
"## Build and verify",
|
|
24817
|
-
|
|
24818
|
-
|
|
24899
|
+
`- Install: ${defaults.install}`,
|
|
24900
|
+
`- Verify: ${defaults.verify.join(" ")}`,
|
|
24819
24901
|
"",
|
|
24820
24902
|
"## Loop note",
|
|
24821
24903
|
"- This project uses ralph-rlm.",
|
|
@@ -25149,13 +25231,14 @@ Set a new goal and run again.
|
|
|
25149
25231
|
const configPath = NodePath.join(root, ".opencode", "ralph.json");
|
|
25150
25232
|
const configExists = await run(fileExists(configPath));
|
|
25151
25233
|
if (!configExists) {
|
|
25234
|
+
const defaults = await run(detectProjectDefaults(root));
|
|
25152
25235
|
const defaultCfg = {
|
|
25153
25236
|
enabled: true,
|
|
25154
25237
|
autoStartOnMainIdle: false,
|
|
25155
25238
|
statusVerbosity: "normal",
|
|
25156
25239
|
maxAttempts: 25,
|
|
25157
25240
|
heartbeatMinutes: 15,
|
|
25158
|
-
verify: { command:
|
|
25241
|
+
verify: { command: defaults.verify, cwd: "." },
|
|
25159
25242
|
gateDestructiveToolsUntilContextLoaded: true,
|
|
25160
25243
|
maxRlmSliceLines: 200,
|
|
25161
25244
|
requireGrepBeforeLargeSlice: true,
|
|
@@ -25592,6 +25675,7 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
|
|
|
25592
25675
|
ralph_update_rlm_instructions: tool_ralph_update_rlm_instructions,
|
|
25593
25676
|
ralph_rollover: tool_ralph_rollover,
|
|
25594
25677
|
ralph_verify: tool_ralph_verify,
|
|
25678
|
+
ralph_peek_worker: tool_ralph_peek_worker,
|
|
25595
25679
|
ralph_spawn_worker: tool_ralph_spawn_worker,
|
|
25596
25680
|
subagent_peek: tool_subagent_peek,
|
|
25597
25681
|
subagent_spawn: tool_subagent_spawn,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ralph-rlm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|