@neriros/ralphy 2.9.3 → 2.10.0
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/dist/cli/index.js +133 -56
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -56407,7 +56407,7 @@ function log(msg) {
|
|
|
56407
56407
|
// package.json
|
|
56408
56408
|
var package_default = {
|
|
56409
56409
|
name: "@neriros/ralphy",
|
|
56410
|
-
version: "2.
|
|
56410
|
+
version: "2.10.0",
|
|
56411
56411
|
description: "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
|
|
56412
56412
|
keywords: [
|
|
56413
56413
|
"agent",
|
|
@@ -70178,25 +70178,82 @@ async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
|
70178
70178
|
|
|
70179
70179
|
// apps/cli/src/agent/state.ts
|
|
70180
70180
|
import { join as join10 } from "path";
|
|
70181
|
+
var TaskStateSchema = exports_external.enum(["started", "processed", "failed"]);
|
|
70182
|
+
var TaskEntrySchema = exports_external.object({
|
|
70183
|
+
issueId: exports_external.string(),
|
|
70184
|
+
identifier: exports_external.string(),
|
|
70185
|
+
state: TaskStateSchema,
|
|
70186
|
+
changeName: exports_external.string().optional(),
|
|
70187
|
+
startedAt: exports_external.string().optional(),
|
|
70188
|
+
finishedAt: exports_external.string().optional(),
|
|
70189
|
+
exitCode: exports_external.number().optional(),
|
|
70190
|
+
commentPosted: exports_external.boolean().optional()
|
|
70191
|
+
});
|
|
70181
70192
|
var AgentStateSchema = exports_external.object({
|
|
70193
|
+
tasks: exports_external.record(exports_external.string(), TaskEntrySchema).default({}),
|
|
70194
|
+
lastPollAt: exports_external.string().nullable().default(null)
|
|
70195
|
+
});
|
|
70196
|
+
var LegacyAgentStateSchema = exports_external.object({
|
|
70182
70197
|
processedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70183
70198
|
startedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70184
70199
|
failedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70185
70200
|
lastPollAt: exports_external.string().nullable().default(null),
|
|
70186
|
-
changeMeta: exports_external.record(exports_external.string(), exports_external.object({
|
|
70187
|
-
|
|
70188
|
-
|
|
70189
|
-
|
|
70190
|
-
|
|
70201
|
+
changeMeta: exports_external.record(exports_external.string(), exports_external.object({ issueId: exports_external.string(), identifier: exports_external.string() })).default({})
|
|
70202
|
+
}).partial();
|
|
70203
|
+
function migrateLegacy(raw) {
|
|
70204
|
+
const parsed = LegacyAgentStateSchema.safeParse(raw);
|
|
70205
|
+
if (!parsed.success)
|
|
70206
|
+
return AgentStateSchema.parse({});
|
|
70207
|
+
const legacy = parsed.data;
|
|
70208
|
+
const tasks = {};
|
|
70209
|
+
const byIssueId = new Map;
|
|
70210
|
+
for (const [changeName, meta] of Object.entries(legacy.changeMeta ?? {})) {
|
|
70211
|
+
byIssueId.set(meta.issueId, { identifier: meta.identifier, changeName });
|
|
70212
|
+
}
|
|
70213
|
+
const fold = (ids, state) => {
|
|
70214
|
+
for (const issueId of ids ?? []) {
|
|
70215
|
+
const found = byIssueId.get(issueId);
|
|
70216
|
+
if (!found)
|
|
70217
|
+
continue;
|
|
70218
|
+
tasks[found.identifier] = {
|
|
70219
|
+
issueId,
|
|
70220
|
+
identifier: found.identifier,
|
|
70221
|
+
state,
|
|
70222
|
+
changeName: found.changeName
|
|
70223
|
+
};
|
|
70224
|
+
}
|
|
70225
|
+
};
|
|
70226
|
+
fold(legacy.startedIssueIds, "started");
|
|
70227
|
+
fold(legacy.processedIssueIds, "processed");
|
|
70228
|
+
fold(legacy.failedIssueIds, "failed");
|
|
70229
|
+
for (const issueId of legacy.startedIssueIds ?? []) {
|
|
70230
|
+
const found = byIssueId.get(issueId);
|
|
70231
|
+
if (!found)
|
|
70232
|
+
continue;
|
|
70233
|
+
const entry = tasks[found.identifier];
|
|
70234
|
+
if (entry)
|
|
70235
|
+
entry.commentPosted = true;
|
|
70236
|
+
}
|
|
70237
|
+
return { tasks, lastPollAt: legacy.lastPollAt ?? null };
|
|
70238
|
+
}
|
|
70191
70239
|
function statePath(projectRoot) {
|
|
70192
70240
|
return join10(projectRoot, ".ralph", "agent-state.json");
|
|
70193
70241
|
}
|
|
70242
|
+
function looksLegacy(raw) {
|
|
70243
|
+
if (typeof raw !== "object" || raw === null)
|
|
70244
|
+
return false;
|
|
70245
|
+
const r = raw;
|
|
70246
|
+
return "processedIssueIds" in r || "startedIssueIds" in r || "failedIssueIds" in r || "changeMeta" in r;
|
|
70247
|
+
}
|
|
70194
70248
|
async function readAgentState(projectRoot) {
|
|
70195
70249
|
const file = Bun.file(statePath(projectRoot));
|
|
70196
70250
|
if (!await file.exists()) {
|
|
70197
70251
|
return AgentStateSchema.parse({});
|
|
70198
70252
|
}
|
|
70199
|
-
|
|
70253
|
+
const raw = await file.json();
|
|
70254
|
+
if (looksLegacy(raw))
|
|
70255
|
+
return migrateLegacy(raw);
|
|
70256
|
+
return AgentStateSchema.parse(raw);
|
|
70200
70257
|
}
|
|
70201
70258
|
async function writeAgentState(projectRoot, state) {
|
|
70202
70259
|
await Bun.write(statePath(projectRoot), JSON.stringify(state, null, 2) + `
|
|
@@ -70371,15 +70428,19 @@ class AgentCoordinator {
|
|
|
70371
70428
|
return { found: 0, added: 0 };
|
|
70372
70429
|
}
|
|
70373
70430
|
const state = this.state;
|
|
70374
|
-
const
|
|
70375
|
-
const
|
|
70431
|
+
const tasksByIssueId = new Map;
|
|
70432
|
+
for (const entry of Object.values(state.tasks)) {
|
|
70433
|
+
tasksByIssueId.set(entry.issueId, entry);
|
|
70434
|
+
}
|
|
70435
|
+
const isProcessed = (id) => tasksByIssueId.get(id)?.state === "processed";
|
|
70436
|
+
const isFailed = (id) => tasksByIssueId.get(id)?.state === "failed";
|
|
70376
70437
|
const queued = new Set(this.queue.map((i) => i.id));
|
|
70377
70438
|
const active = new Set(this.workers.map((w) => w.issueId));
|
|
70378
70439
|
let added = 0;
|
|
70379
70440
|
for (const issue of issues) {
|
|
70380
|
-
if (
|
|
70441
|
+
if (isProcessed(issue.id))
|
|
70381
70442
|
continue;
|
|
70382
|
-
if (
|
|
70443
|
+
if (isFailed(issue.id))
|
|
70383
70444
|
continue;
|
|
70384
70445
|
if (queued.has(issue.id))
|
|
70385
70446
|
continue;
|
|
@@ -70387,7 +70448,7 @@ class AgentCoordinator {
|
|
|
70387
70448
|
continue;
|
|
70388
70449
|
if (this.pendingIds.has(issue.id))
|
|
70389
70450
|
continue;
|
|
70390
|
-
const blocker = issue.blockedByIds.find((bid) => !
|
|
70451
|
+
const blocker = issue.blockedByIds.find((bid) => !isProcessed(bid));
|
|
70391
70452
|
if (blocker !== undefined) {
|
|
70392
70453
|
this.deps.onLog(` \u23F8 ${issue.identifier} skipped \u2014 blocked by unresolved dependency`, "yellow");
|
|
70393
70454
|
continue;
|
|
@@ -70445,6 +70506,18 @@ class AgentCoordinator {
|
|
|
70445
70506
|
this.launchWorker(issue);
|
|
70446
70507
|
}
|
|
70447
70508
|
}
|
|
70509
|
+
upsertTask(issue, patch) {
|
|
70510
|
+
if (!this.state)
|
|
70511
|
+
return;
|
|
70512
|
+
const existing = this.state.tasks[issue.identifier];
|
|
70513
|
+
this.state.tasks[issue.identifier] = {
|
|
70514
|
+
issueId: issue.id,
|
|
70515
|
+
identifier: issue.identifier,
|
|
70516
|
+
state: existing?.state ?? "started",
|
|
70517
|
+
...existing,
|
|
70518
|
+
...patch
|
|
70519
|
+
};
|
|
70520
|
+
}
|
|
70448
70521
|
async launchWorker(issue) {
|
|
70449
70522
|
let changeName;
|
|
70450
70523
|
try {
|
|
@@ -70459,6 +70532,14 @@ class AgentCoordinator {
|
|
|
70459
70532
|
this.pendingIds.delete(issue.id);
|
|
70460
70533
|
return;
|
|
70461
70534
|
}
|
|
70535
|
+
if (this.state) {
|
|
70536
|
+
this.upsertTask(issue, {
|
|
70537
|
+
state: "started",
|
|
70538
|
+
changeName,
|
|
70539
|
+
startedAt: this.state.tasks[issue.identifier]?.startedAt ?? new Date().toISOString()
|
|
70540
|
+
});
|
|
70541
|
+
this.deps.saveState(this.state);
|
|
70542
|
+
}
|
|
70462
70543
|
this.deps.onLog(`\u25B6 ${issue.identifier} \u2192 ${changeName} (worker started)`, "cyan");
|
|
70463
70544
|
const handle = this.deps.spawnWorker(changeName, issue);
|
|
70464
70545
|
const worker = {
|
|
@@ -70479,12 +70560,12 @@ class AgentCoordinator {
|
|
|
70479
70560
|
this.workers.splice(idx, 1);
|
|
70480
70561
|
const ok = code === 0;
|
|
70481
70562
|
this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue.identifier} \u2192 ${changeName} exited (code ${code})`, ok ? "green" : "red");
|
|
70482
|
-
if (
|
|
70483
|
-
this.
|
|
70484
|
-
|
|
70485
|
-
|
|
70486
|
-
|
|
70487
|
-
|
|
70563
|
+
if (this.state) {
|
|
70564
|
+
this.upsertTask(issue, {
|
|
70565
|
+
state: ok ? "processed" : "failed",
|
|
70566
|
+
finishedAt: new Date().toISOString(),
|
|
70567
|
+
exitCode: code
|
|
70568
|
+
});
|
|
70488
70569
|
this.deps.saveState(this.state);
|
|
70489
70570
|
}
|
|
70490
70571
|
this.notifyExited(issue, changeName, code);
|
|
@@ -70496,12 +70577,12 @@ class AgentCoordinator {
|
|
|
70496
70577
|
const updater = this.deps.updater;
|
|
70497
70578
|
if (!updater)
|
|
70498
70579
|
return;
|
|
70499
|
-
const
|
|
70500
|
-
if (this.opts.postComments !== false && !
|
|
70580
|
+
const alreadyCommented = this.state?.tasks[issue.identifier]?.commentPosted === true;
|
|
70581
|
+
if (this.opts.postComments !== false && !alreadyCommented) {
|
|
70501
70582
|
try {
|
|
70502
70583
|
await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
|
|
70503
|
-
if (this.state
|
|
70504
|
-
this.
|
|
70584
|
+
if (this.state) {
|
|
70585
|
+
this.upsertTask(issue, { commentPosted: true });
|
|
70505
70586
|
await this.deps.saveState(this.state);
|
|
70506
70587
|
}
|
|
70507
70588
|
} catch (err) {
|
|
@@ -70806,22 +70887,28 @@ function nextId() {
|
|
|
70806
70887
|
return `${Date.now()}-${lineCounter}`;
|
|
70807
70888
|
}
|
|
70808
70889
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
70809
|
-
async function
|
|
70810
|
-
const
|
|
70811
|
-
const
|
|
70812
|
-
const stamped =
|
|
70813
|
-
|
|
70814
|
-
### Steering feedback (${new Date().toISOString()})
|
|
70890
|
+
async function injectFixSteering(changeDir, heading, steering) {
|
|
70891
|
+
const steeringFile = Bun.file(join14(changeDir, "steering.md"));
|
|
70892
|
+
const existing = await steeringFile.exists() ? await steeringFile.text() : "";
|
|
70893
|
+
const stamped = `## ${heading} (${new Date().toISOString()})
|
|
70815
70894
|
|
|
70816
70895
|
${steering}
|
|
70817
70896
|
`;
|
|
70818
|
-
const
|
|
70819
|
-
${stamped}
|
|
70820
|
-
|
|
70821
|
-
|
|
70822
|
-
|
|
70897
|
+
const nextSteering = existing ? `${stamped}
|
|
70898
|
+
${existing.trimStart()}` : `${stamped}
|
|
70899
|
+
`;
|
|
70900
|
+
await Bun.write(join14(changeDir, "steering.md"), nextSteering);
|
|
70901
|
+
const tasksFile = Bun.file(join14(changeDir, "tasks.md"));
|
|
70902
|
+
const tasks = await tasksFile.exists() ? await tasksFile.text() : "";
|
|
70903
|
+
const taskSection = `
|
|
70904
|
+
## ${heading} (${new Date().toISOString()})
|
|
70905
|
+
|
|
70906
|
+
` + `- [ ] ${heading}. The error output is recorded in steering.md \u2014 read it first, ` + `then fix the underlying problem (do not just retry the failing command).
|
|
70823
70907
|
`;
|
|
70824
|
-
|
|
70908
|
+
const nextTasks = tasks.endsWith(`
|
|
70909
|
+
`) ? tasks + taskSection : tasks + `
|
|
70910
|
+
` + taskSection;
|
|
70911
|
+
await Bun.write(join14(changeDir, "tasks.md"), nextTasks);
|
|
70825
70912
|
}
|
|
70826
70913
|
function fmtElapsed(ms) {
|
|
70827
70914
|
const s = Math.floor(ms / 1000);
|
|
@@ -70930,13 +71017,6 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70930
71017
|
issueByChange.set(changeName, issue);
|
|
70931
71018
|
if (workerBranch)
|
|
70932
71019
|
branchByChange.set(changeName, workerBranch);
|
|
70933
|
-
try {
|
|
70934
|
-
const s = await readAgentState(projectRoot);
|
|
70935
|
-
s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
|
|
70936
|
-
await writeAgentState(projectRoot, s);
|
|
70937
|
-
} catch (err) {
|
|
70938
|
-
appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
|
|
70939
|
-
}
|
|
70940
71020
|
if (cfg.setupScript) {
|
|
70941
71021
|
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70942
71022
|
}
|
|
@@ -71005,13 +71085,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
71005
71085
|
appendLog(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
71006
71086
|
effectiveCode = PR_FAILED_EXIT;
|
|
71007
71087
|
} else {
|
|
71008
|
-
const
|
|
71088
|
+
const changeDir = join14(statesDirByChange.get(changeName) ?? statesDir, "..", "..", "openspec", "changes", changeName);
|
|
71009
71089
|
const maxHookFixAttempts = cfg.maxCiFixAttempts;
|
|
71010
|
-
const
|
|
71090
|
+
const runWorkerWithFixSteering = async (heading, steering) => {
|
|
71011
71091
|
try {
|
|
71012
|
-
await
|
|
71092
|
+
await injectFixSteering(changeDir, heading, steering);
|
|
71013
71093
|
} catch (steerErr) {
|
|
71014
|
-
appendLog(`! could not
|
|
71094
|
+
appendLog(`! could not inject steering: ${steerErr.message}`, "red");
|
|
71015
71095
|
return 1;
|
|
71016
71096
|
}
|
|
71017
71097
|
const rp = Bun.spawn({
|
|
@@ -71058,7 +71138,7 @@ ${e.stderr ?? ""}`;
|
|
|
71058
71138
|
commitFixAttempt += 1;
|
|
71059
71139
|
appendLog(`! commit rejected for ${changeName} \u2014 feeding error back to worker (attempt ${commitFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71060
71140
|
appendLog(` detail: ${detail}`, "yellow");
|
|
71061
|
-
const retryCode = await
|
|
71141
|
+
const retryCode = await runWorkerWithFixSteering("Fix host pre-commit hook rejection", `Committing residual changes was rejected by the host repo's pre-commit hook. ` + `Fix the underlying problem reported below, then the commit will be retried.
|
|
71062
71142
|
|
|
71063
71143
|
` + "```\n" + combined.trim() + "\n```");
|
|
71064
71144
|
if (retryCode !== 0) {
|
|
@@ -71096,7 +71176,7 @@ ${e.stderr ?? ""}`;
|
|
|
71096
71176
|
pushFixAttempt += 1;
|
|
71097
71177
|
appendLog(`! push rejected for ${changeName} \u2014 feeding error back to worker (attempt ${pushFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71098
71178
|
appendLog(` detail: ${detail}`, "yellow");
|
|
71099
|
-
const retryCode = await
|
|
71179
|
+
const retryCode = await runWorkerWithFixSteering("Fix host pre-push hook rejection", `Push to origin/${branch} was rejected by the host repo's pre-push hook. ` + `Fix the underlying problem reported below, then the push will be retried.
|
|
71100
71180
|
|
|
71101
71181
|
` + "```\n" + combined.trim() + "\n```");
|
|
71102
71182
|
if (retryCode !== 0) {
|
|
@@ -71119,11 +71199,11 @@ ${e.stderr ?? ""}`;
|
|
|
71119
71199
|
getFailedLogs: (ids) => fetchFailedRunLogs(ids, bunCmdRunner, cwd2),
|
|
71120
71200
|
runTaskWithSteering: async (steering) => {
|
|
71121
71201
|
try {
|
|
71122
|
-
await
|
|
71202
|
+
await injectFixSteering(changeDir, "Fix failing CI checks", `CI feedback:
|
|
71123
71203
|
|
|
71124
71204
|
${steering}`);
|
|
71125
71205
|
} catch (err) {
|
|
71126
|
-
appendLog(`! could not
|
|
71206
|
+
appendLog(`! could not inject steering: ${err.message}`, "red");
|
|
71127
71207
|
}
|
|
71128
71208
|
const p = Bun.spawn({
|
|
71129
71209
|
cmd: buildTaskCmd(),
|
|
@@ -71684,14 +71764,11 @@ try {
|
|
|
71684
71764
|
}
|
|
71685
71765
|
try {
|
|
71686
71766
|
const agentState = await readAgentState(projectRoot);
|
|
71687
|
-
const
|
|
71688
|
-
if (
|
|
71689
|
-
|
|
71690
|
-
agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
|
|
71691
|
-
agentState.failedIssueIds = agentState.failedIssueIds.filter((id) => id !== meta.issueId);
|
|
71692
|
-
delete agentState.changeMeta[args.name];
|
|
71767
|
+
const entry = Object.values(agentState.tasks).find((t) => t.changeName === args.name);
|
|
71768
|
+
if (entry) {
|
|
71769
|
+
delete agentState.tasks[entry.identifier];
|
|
71693
71770
|
await writeAgentState(projectRoot, agentState);
|
|
71694
|
-
removed.push(`agent-state entry for ${
|
|
71771
|
+
removed.push(`agent-state entry for ${entry.identifier} (${entry.issueId})`);
|
|
71695
71772
|
}
|
|
71696
71773
|
} catch {}
|
|
71697
71774
|
if (removed.length === 0) {
|