opencode-ralph-rlm 0.1.11 → 0.1.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.
Files changed (2) hide show
  1. package/dist/ralph-rlm.js +94 -13
  2. package/package.json +1 -1
package/dist/ralph-rlm.js CHANGED
@@ -23522,7 +23522,22 @@ var DEFAULT_TEMPLATES = {
23522
23522
  "- The supervisor owns the lifecycle only. It never edits files or runs rlm_grep/rlm_slice.",
23523
23523
  "- All implementation happens in RLM worker sessions spawned per attempt.",
23524
23524
  "- Strategy changes must be written to PLAN.md / RLM_INSTRUCTIONS.md so each new worker sees them.",
23525
- "- Stop the loop explicitly with ralph_end_supervision() when the user is done or verification passed."
23525
+ "- Stop the loop explicitly with ralph_end_supervision() when the user is done or verification passed.",
23526
+ "",
23527
+ "Onboarding checklist (supervisor):",
23528
+ "- If setup is incomplete, run ralph_doctor(autofix=true).",
23529
+ "- If PLAN.md is missing or weak, run ralph_quickstart_wizard(...) or ralph_bootstrap_plan(...) + ralph_validate_plan().",
23530
+ "- Start supervision with ralph_create_supervisor_session(start_loop=true) unless autoStartOnMainIdle is enabled.",
23531
+ "- Use ralph_supervision_status() to confirm current attempt and active sessions.",
23532
+ "",
23533
+ "Protocol files (purpose):",
23534
+ "- PLAN.md: goals, milestones, definition of done (authoritative).",
23535
+ "- RLM_INSTRUCTIONS.md: inner loop operating manual (authoritative, updated between attempts).",
23536
+ "- CURRENT_STATE.md: scratch for the current attempt only.",
23537
+ "- PREVIOUS_STATE.md: snapshot of last attempt's scratch.",
23538
+ "- NOTES_AND_LEARNINGS.md: durable learnings across attempts.",
23539
+ "- CONVERSATION.md / SUPERVISOR_LOG.md: status feed and timeline for the supervisor.",
23540
+ "- CONTEXT_FOR_RLM.md: large reference; workers must use rlm_grep/rlm_slice."
23526
23541
  ].join(`
23527
23542
  `),
23528
23543
  systemPromptAppend: "",
@@ -23536,7 +23551,12 @@ var DEFAULT_TEMPLATES = {
23536
23551
  "- NOTES_AND_LEARNINGS.md \u2014 append-only durable learnings",
23537
23552
  "- CONVERSATION.md \u2014 append-only supervisor-visible status feed",
23538
23553
  "- CONTEXT_FOR_RLM.md \u2014 large reference; access via rlm_grep + rlm_slice",
23539
- "- .opencode/agents/<name>/ \u2014 sub-agent state directories"
23554
+ "- .opencode/agents/<name>/ \u2014 sub-agent state directories",
23555
+ "",
23556
+ "## Loop control",
23557
+ "- ralph_create_supervisor_session(start_loop=true) to start",
23558
+ "- ralph_pause_supervision() / ralph_resume_supervision() to pause/resume",
23559
+ "- ralph_end_supervision() to stop and clean up"
23540
23560
  ].join(`
23541
23561
  `),
23542
23562
  continuePrompt: [
@@ -24238,6 +24258,27 @@ var RalphRLM = async ({ client, $, worktree }) => {
24238
24258
  }).catch(() => {});
24239
24259
  }
24240
24260
  };
24261
+ const sendPromptWithFallback = async (sessionId, text, label, originSessionId) => {
24262
+ try {
24263
+ await client.session.promptAsync({
24264
+ path: { id: sessionId },
24265
+ body: { parts: [{ type: "text", text }] }
24266
+ });
24267
+ return true;
24268
+ } catch (err) {
24269
+ await notifySupervisor("supervisor", `${label} promptAsync failed: ${err instanceof Error ? err.message : String(err)}`, "warning", true, originSessionId);
24270
+ }
24271
+ try {
24272
+ await client.session.prompt({
24273
+ path: { id: sessionId },
24274
+ body: { parts: [{ type: "text", text }] }
24275
+ });
24276
+ return true;
24277
+ } catch (err) {
24278
+ await notifySupervisor("supervisor", `${label} prompt fallback failed: ${err instanceof Error ? err.message : String(err)}`, "error", true, originSessionId);
24279
+ return false;
24280
+ }
24281
+ };
24241
24282
  const detectProjectDefaults = (root) => exports_Effect.gen(function* () {
24242
24283
  const j = (f) => NodePath.join(root, f);
24243
24284
  const hasBunLock = (yield* fileExists(j("bun.lockb"))) || (yield* fileExists(j("bun.lock")));
@@ -25134,6 +25175,10 @@ No pending questions found.`));
25134
25175
  supervisor.activeReviewerSessionId = undefined;
25135
25176
  supervisor.activeReviewerOutputPath = undefined;
25136
25177
  await persistReviewerState();
25178
+ if (supervisor.ralphHandoffTimer) {
25179
+ clearTimeout(supervisor.ralphHandoffTimer);
25180
+ supervisor.ralphHandoffTimer = undefined;
25181
+ }
25137
25182
  if (args2.clear_binding === true) {
25138
25183
  supervisor.sessionId = undefined;
25139
25184
  }
@@ -25575,7 +25620,8 @@ Set a new goal and run again.
25575
25620
  const st = sessionMap.get(sessionId);
25576
25621
  sessionMap.delete(sessionId);
25577
25622
  let didUpdate = false;
25578
- if (supervisor.currentRalphSessionId === sessionId) {
25623
+ const clearedRalph = supervisor.currentRalphSessionId === sessionId;
25624
+ if (clearedRalph) {
25579
25625
  supervisor.currentRalphSessionId = undefined;
25580
25626
  didUpdate = true;
25581
25627
  }
@@ -25591,6 +25637,10 @@ Set a new goal and run again.
25591
25637
  didUpdate = true;
25592
25638
  await persistReviewerState();
25593
25639
  }
25640
+ if (supervisor.ralphHandoffTimer && clearedRalph) {
25641
+ clearTimeout(supervisor.ralphHandoffTimer);
25642
+ supervisor.ralphHandoffTimer = undefined;
25643
+ }
25594
25644
  if (didUpdate && st) {
25595
25645
  await notifySupervisor(`${st.role}/attempt-${st.attempt}`, `${st.role} session ended (${reason}).`, "info", true, sessionId);
25596
25646
  }
@@ -25651,10 +25701,18 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25651
25701
  attempt: String(attempt),
25652
25702
  nextAttempt: String(attempt + 1)
25653
25703
  });
25654
- await client.session.promptAsync({
25655
- path: { id: workerId },
25656
- body: { parts: [{ type: "text", text: promptText }] }
25657
- }).catch(() => {});
25704
+ const promptOk = await sendPromptWithFallback(workerId, promptText, `Worker prompt (attempt ${attempt})`, workerId);
25705
+ if (!promptOk) {
25706
+ mutateSession(workerId, (s) => {
25707
+ s.reportedStatus = "error";
25708
+ s.reportedStatusNote = "Worker prompt failed";
25709
+ });
25710
+ supervisor.currentWorkerSessionId = undefined;
25711
+ await client.session.abort({ path: { id: workerId } }).catch(() => {});
25712
+ await notifySupervisor(`worker/attempt-${attempt}`, "Worker prompt failed; supervision paused. Retry with ralph_create_supervisor_session(restart_if_done=true).", "error", true);
25713
+ supervisor.paused = true;
25714
+ throw new Error("Worker prompt failed");
25715
+ }
25658
25716
  await notifySupervisor(`supervisor/attempt-${attempt}`, `Spawned worker session ${workerId}.`, "info", true);
25659
25717
  return workerId;
25660
25718
  };
@@ -25667,10 +25725,14 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25667
25725
  if (st.workerSpawned) {
25668
25726
  throw new Error("ralph_spawn_worker() has already been called for this attempt.");
25669
25727
  }
25728
+ if (supervisor.ralphHandoffTimer) {
25729
+ clearTimeout(supervisor.ralphHandoffTimer);
25730
+ supervisor.ralphHandoffTimer = undefined;
25731
+ }
25732
+ const workerId = await spawnRlmWorker(st.attempt);
25670
25733
  mutateSession(sessionID, (s) => {
25671
25734
  s.workerSpawned = true;
25672
25735
  });
25673
- const workerId = await spawnRlmWorker(st.attempt);
25674
25736
  await notifySupervisor(`ralph/attempt-${st.attempt}`, `Delegated coding to worker session ${workerId}.`, "info", true, sessionID);
25675
25737
  return JSON.stringify({ ok: true, workerSessionId: workerId, attempt: st.attempt }, null, 2);
25676
25738
  };
@@ -25688,10 +25750,29 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25688
25750
  attempt: String(attempt),
25689
25751
  nextAttempt: String(attempt + 1)
25690
25752
  });
25691
- await client.session.promptAsync({
25692
- path: { id: ralphId },
25693
- body: { parts: [{ type: "text", text: promptText }] }
25694
- }).catch(() => {});
25753
+ const promptOk = await sendPromptWithFallback(ralphId, promptText, `Strategist prompt (attempt ${attempt})`, ralphId);
25754
+ if (!promptOk) {
25755
+ supervisor.currentRalphSessionId = undefined;
25756
+ await client.session.abort({ path: { id: ralphId } }).catch(() => {});
25757
+ await notifySupervisor(`supervisor/attempt-${attempt}`, "Strategist prompt failed; supervision paused. Retry with ralph_create_supervisor_session(restart_if_done=true).", "error", true);
25758
+ supervisor.paused = true;
25759
+ return;
25760
+ }
25761
+ if (supervisor.ralphHandoffTimer) {
25762
+ clearTimeout(supervisor.ralphHandoffTimer);
25763
+ }
25764
+ const cfg = await run(getConfig());
25765
+ const timeoutMs = Math.max(cfg.heartbeatMinutes, 1) * 60000;
25766
+ supervisor.ralphHandoffTimer = setTimeout(async () => {
25767
+ if (supervisor.done || supervisor.paused)
25768
+ return;
25769
+ if (supervisor.currentRalphSessionId !== ralphId)
25770
+ return;
25771
+ const st = sessionMap.get(ralphId);
25772
+ if (st?.workerSpawned)
25773
+ return;
25774
+ await notifySupervisor(`ralph/attempt-${attempt}`, "Strategist did not hand off to a worker within the heartbeat window. Re-check prompt delivery or restart supervision.", "warning", true, ralphId);
25775
+ }, timeoutMs);
25695
25776
  await notifySupervisor(`supervisor/attempt-${attempt}`, `Spawned Ralph strategist session ${ralphId}.`, "info", true);
25696
25777
  };
25697
25778
  const handleWorkerIdle = async (workerSessionId) => {
@@ -25821,7 +25902,7 @@ ${interpolate(templates.continuePrompt, { attempt: String(attemptN), verdict })}
25821
25902
  },
25822
25903
  "experimental.chat.system.transform": async (input, output) => {
25823
25904
  output.system = output.system ?? [];
25824
- const sessionID = input.sessionID ?? input.session_id;
25905
+ const sessionID = input.sessionID ?? input.session_id ?? input.session?.id;
25825
25906
  const role = sessionMap.get(sessionID ?? "")?.role;
25826
25907
  const base = role === "worker" || role === "subagent" ? templates.workerSystemPrompt : role === "ralph" ? templates.ralphSessionSystemPrompt : templates.systemPrompt;
25827
25908
  const full = templates.systemPromptAppend ? `${base}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ralph-rlm",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
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",