@neriros/ralphy 2.9.4 → 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 +107 -36
- 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) {
|
|
@@ -70936,13 +71017,6 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70936
71017
|
issueByChange.set(changeName, issue);
|
|
70937
71018
|
if (workerBranch)
|
|
70938
71019
|
branchByChange.set(changeName, workerBranch);
|
|
70939
|
-
try {
|
|
70940
|
-
const s = await readAgentState(projectRoot);
|
|
70941
|
-
s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
|
|
70942
|
-
await writeAgentState(projectRoot, s);
|
|
70943
|
-
} catch (err) {
|
|
70944
|
-
appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
|
|
70945
|
-
}
|
|
70946
71020
|
if (cfg.setupScript) {
|
|
70947
71021
|
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70948
71022
|
}
|
|
@@ -71690,14 +71764,11 @@ try {
|
|
|
71690
71764
|
}
|
|
71691
71765
|
try {
|
|
71692
71766
|
const agentState = await readAgentState(projectRoot);
|
|
71693
|
-
const
|
|
71694
|
-
if (
|
|
71695
|
-
|
|
71696
|
-
agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
|
|
71697
|
-
agentState.failedIssueIds = agentState.failedIssueIds.filter((id) => id !== meta.issueId);
|
|
71698
|
-
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];
|
|
71699
71770
|
await writeAgentState(projectRoot, agentState);
|
|
71700
|
-
removed.push(`agent-state entry for ${
|
|
71771
|
+
removed.push(`agent-state entry for ${entry.identifier} (${entry.issueId})`);
|
|
71701
71772
|
}
|
|
71702
71773
|
} catch {}
|
|
71703
71774
|
if (removed.length === 0) {
|