@kernlang/agon 0.1.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/chunk-4HY7J7LY.js +11875 -0
- package/dist/chunk-4HY7J7LY.js.map +1 -0
- package/dist/chunk-5J4XXR3J.js +2727 -0
- package/dist/chunk-5J4XXR3J.js.map +1 -0
- package/dist/chunk-ESSX7EFA.js +473 -0
- package/dist/chunk-ESSX7EFA.js.map +1 -0
- package/dist/chunk-GCXVT7RP.js +29109 -0
- package/dist/chunk-GCXVT7RP.js.map +1 -0
- package/dist/chunk-YNZ4UHZI.js +1593 -0
- package/dist/chunk-YNZ4UHZI.js.map +1 -0
- package/dist/dispatch-CJVPYWJJ.js +22 -0
- package/dist/dispatch-CJVPYWJJ.js.map +1 -0
- package/dist/engines/agy.json +43 -0
- package/dist/engines/aider.json +40 -0
- package/dist/engines/claude.json +79 -0
- package/dist/engines/codex.json +74 -0
- package/dist/engines/mistral.json +44 -0
- package/dist/engines/ollama.json +35 -0
- package/dist/engines/opencode.json +55 -0
- package/dist/engines/openrouter.json +54 -0
- package/dist/engines/qwen.json +40 -0
- package/dist/forge-OWSOM4TQ.js +33 -0
- package/dist/forge-OWSOM4TQ.js.map +1 -0
- package/dist/index.js +25246 -0
- package/dist/index.js.map +1 -0
- package/dist/plan-mode-MWL3DAHY.js +15 -0
- package/dist/plan-mode-MWL3DAHY.js.map +1 -0
- package/dist/src-HKGB6OYR.js +808 -0
- package/dist/src-HKGB6OYR.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,1593 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ENGINE_COLORS,
|
|
4
|
+
confidenceBadge,
|
|
5
|
+
ensureCesarSession,
|
|
6
|
+
filterDefaultOrchestrationEngines,
|
|
7
|
+
parseConfidence,
|
|
8
|
+
runForge
|
|
9
|
+
} from "./chunk-4HY7J7LY.js";
|
|
10
|
+
import {
|
|
11
|
+
RUNS_DIR,
|
|
12
|
+
acquireApplyLock,
|
|
13
|
+
applyPatchWithUndo,
|
|
14
|
+
approvePlan,
|
|
15
|
+
branchChanged,
|
|
16
|
+
cancelPlan,
|
|
17
|
+
classifyTask,
|
|
18
|
+
createPlan,
|
|
19
|
+
currentBranch,
|
|
20
|
+
ensureAgonHome,
|
|
21
|
+
extractPatchFilePatterns,
|
|
22
|
+
failPlan,
|
|
23
|
+
getActiveWorkspace,
|
|
24
|
+
gitChangedFiles,
|
|
25
|
+
headChanged,
|
|
26
|
+
headSha,
|
|
27
|
+
loadOrCreateActiveThread,
|
|
28
|
+
mergeStepResult,
|
|
29
|
+
recordForgeJudgment,
|
|
30
|
+
releaseApplyLock,
|
|
31
|
+
resolveWorkingDir,
|
|
32
|
+
savePlan,
|
|
33
|
+
scanProjectContext,
|
|
34
|
+
snapshotWorkspace,
|
|
35
|
+
startPlan,
|
|
36
|
+
tracker
|
|
37
|
+
} from "./chunk-GCXVT7RP.js";
|
|
38
|
+
|
|
39
|
+
// src/generated/handlers/forge.ts
|
|
40
|
+
import { join as join4 } from "path";
|
|
41
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
42
|
+
import { execFileSync } from "child_process";
|
|
43
|
+
|
|
44
|
+
// src/generated/cesar/judge.ts
|
|
45
|
+
import { join } from "path";
|
|
46
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
47
|
+
function parseForgeJudgment(response, manifest) {
|
|
48
|
+
const stripped = parseConfidence(response).rest;
|
|
49
|
+
const lines = stripped.split("\n");
|
|
50
|
+
let winner = manifest.winner ?? "";
|
|
51
|
+
const strengths = [];
|
|
52
|
+
const convergencePlan = [];
|
|
53
|
+
let summary = "";
|
|
54
|
+
let shouldConverge = false;
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
const winnerMatch = trimmed.match(/^WINNER:\s*(.+)/i);
|
|
58
|
+
if (winnerMatch) {
|
|
59
|
+
winner = winnerMatch[1].trim();
|
|
60
|
+
}
|
|
61
|
+
const convergeMatch = trimmed.match(/^CONVERGE:\s*(yes|no)/i);
|
|
62
|
+
if (convergeMatch) {
|
|
63
|
+
shouldConverge = convergeMatch[1].toLowerCase() === "yes";
|
|
64
|
+
}
|
|
65
|
+
const summaryMatch = trimmed.match(/^SUMMARY:\s*(.+)/i);
|
|
66
|
+
if (summaryMatch) {
|
|
67
|
+
summary = summaryMatch[1].trim();
|
|
68
|
+
}
|
|
69
|
+
const strengthMatch = trimmed.match(/^-\s+(\w+):\s+(.+?)\s+[—–-]\s+(.+)/);
|
|
70
|
+
if (strengthMatch && !trimmed.startsWith("- file:")) {
|
|
71
|
+
strengths.push({ engineId: strengthMatch[1], category: strengthMatch[2], reason: strengthMatch[3] });
|
|
72
|
+
}
|
|
73
|
+
const convMatch = trimmed.match(/^-\s+file:(\S+)\s+fn:(\S+)\s+from:(\S+)\s+reason:(.+)/);
|
|
74
|
+
if (convMatch) {
|
|
75
|
+
convergencePlan.push({ file: convMatch[1], fn: convMatch[2], from: convMatch[3], reason: convMatch[4].trim() });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!winner) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return { winner, strengths, convergencePlan, summary, shouldConverge };
|
|
82
|
+
}
|
|
83
|
+
async function cesarReviewForgeOutcome(manifest, dispatch, ctx) {
|
|
84
|
+
let session;
|
|
85
|
+
try {
|
|
86
|
+
session = await ensureCesarSession(ctx);
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!session.alive) return;
|
|
91
|
+
const cesarEngineId = ctx.config.cesarEngine ?? ctx.config.forgeFixedStarter ?? "claude";
|
|
92
|
+
const color = ENGINE_COLORS[cesarEngineId] ?? 124;
|
|
93
|
+
if (manifest.error) {
|
|
94
|
+
const skippedDetail = manifest.engines.length > 0 ? `Engines configured: ${manifest.engines.join(", ")}.` : "No engines were dispatched.";
|
|
95
|
+
const errorPrompt = `The forge run failed before producing a winner.
|
|
96
|
+
|
|
97
|
+
TASK: ${manifest.task}
|
|
98
|
+
FITNESS COMMAND: ${manifest.fitnessCmd}
|
|
99
|
+
ERROR: ${manifest.error}
|
|
100
|
+
${skippedDetail}
|
|
101
|
+
|
|
102
|
+
In 2-3 sentences, explain to the user what went wrong and the most likely fix (refresh API keys, install a CLI, check git state, etc.). Be specific and actionable. Do not pad. End with a confidence percentage on whatever fix you suggest.`;
|
|
103
|
+
dispatch({ type: "spinner-start", message: "Cesar reading forge failure\u2026", color });
|
|
104
|
+
let response = "";
|
|
105
|
+
try {
|
|
106
|
+
const gen = session.send({ message: errorPrompt, signal: void 0 });
|
|
107
|
+
for await (const chunk of gen) {
|
|
108
|
+
if (chunk.type === "text") response += chunk.content;
|
|
109
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
dispatch({ type: "spinner-stop" });
|
|
114
|
+
if (response.trim()) {
|
|
115
|
+
dispatch({ type: "header", title: "Cesar \u2014 Forge Failure" });
|
|
116
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response.trim() });
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (manifest.alreadySatisfied) {
|
|
121
|
+
const satisfiedPrompt = `The forge baseline check passed before any engine ran. The task says: ${manifest.task}. The fitness command "${manifest.fitnessCmd}" already passes on a clean checkout. In 1-2 sentences, confirm to the user that the task appears already done and suggest next steps (verify, move on, or re-check if the fitness command is too lenient). End with a confidence percentage.`;
|
|
122
|
+
dispatch({ type: "spinner-start", message: "Cesar confirming task state\u2026", color });
|
|
123
|
+
let response = "";
|
|
124
|
+
try {
|
|
125
|
+
const gen = session.send({ message: satisfiedPrompt, signal: void 0 });
|
|
126
|
+
for await (const chunk of gen) {
|
|
127
|
+
if (chunk.type === "text") response += chunk.content;
|
|
128
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
dispatch({ type: "spinner-stop" });
|
|
133
|
+
if (response.trim()) {
|
|
134
|
+
dispatch({ type: "header", title: "Cesar \u2014 Already Satisfied" });
|
|
135
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response.trim() });
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (manifest.singleSurvivor && manifest.winner) {
|
|
140
|
+
const winnerId = manifest.winner;
|
|
141
|
+
const winnerResult = manifest.results[winnerId];
|
|
142
|
+
let winnerPatch = "";
|
|
143
|
+
const winnerPatchPath = manifest.patches[winnerId];
|
|
144
|
+
if (winnerPatchPath) {
|
|
145
|
+
try {
|
|
146
|
+
const fullPatch = readFileSync(winnerPatchPath, "utf-8");
|
|
147
|
+
const lines = fullPatch.split("\n");
|
|
148
|
+
winnerPatch = lines.slice(0, 200).join("\n");
|
|
149
|
+
if (lines.length > 200) winnerPatch += `
|
|
150
|
+
... (${lines.length - 200} more lines)`;
|
|
151
|
+
} catch {
|
|
152
|
+
winnerPatch = "(patch file unreadable)";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const failedList = Object.entries(manifest.results).filter(([id, r]) => id !== winnerId && !r.pass).map(([id, r]) => `- ${id}: ${r.durationSec}s, ${r.diffLines ?? 0} lines, ${r.pass ? "passed" : "failed"}`).join("\n");
|
|
156
|
+
const survivorPrompt = `Forge produced exactly one passing result. The other engines failed.
|
|
157
|
+
|
|
158
|
+
TASK: ${manifest.task}
|
|
159
|
+
FITNESS COMMAND: ${manifest.fitnessCmd}
|
|
160
|
+
SOLE SURVIVOR: ${winnerId} (score: ${winnerResult?.score ?? 0})
|
|
161
|
+
OTHER ENGINES:
|
|
162
|
+
${failedList || "(none reported)"}
|
|
163
|
+
|
|
164
|
+
## SURVIVING PATCH (first 200 lines)
|
|
165
|
+
|
|
166
|
+
\`\`\`diff
|
|
167
|
+
${winnerPatch}
|
|
168
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
Read the patch carefully. Tell the user:
|
|
171
|
+
1. **Quality verdict** \u2014 does this look like a competent solution to the task? Note specific concerns or strengths.
|
|
172
|
+
2. **Recommendation** \u2014 accept this patch, or run /forge again hoping for more engines? Be specific about why.
|
|
173
|
+
3. **Apply hint** \u2014 if accept-worthy, give the exact path: ${winnerPatchPath ?? "(no patch path)"}
|
|
174
|
+
|
|
175
|
+
End with a confidence percentage. Don't pad.`;
|
|
176
|
+
dispatch({ type: "spinner-start", message: `Cesar reviewing ${winnerId}'s solo solution\u2026`, color });
|
|
177
|
+
let response = "";
|
|
178
|
+
try {
|
|
179
|
+
const gen = session.send({ message: survivorPrompt, signal: void 0 });
|
|
180
|
+
for await (const chunk of gen) {
|
|
181
|
+
if (chunk.type === "text") response += chunk.content;
|
|
182
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
dispatch({ type: "spinner-stop" });
|
|
187
|
+
if (response.trim()) {
|
|
188
|
+
dispatch({ type: "header", title: `Cesar \u2014 Single Survivor (${winnerId})` });
|
|
189
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response.trim() });
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!manifest.winner) {
|
|
194
|
+
const noWinnerPrompt = `Forge dispatched ${manifest.enginesDispatched} engine(s) and none produced a passing result for: ${manifest.task}
|
|
195
|
+
|
|
196
|
+
Fitness command: ${manifest.fitnessCmd}
|
|
197
|
+
|
|
198
|
+
Engine outcomes:
|
|
199
|
+
${Object.entries(manifest.results).map(([id, r]) => `- ${id}: pass=${r.pass}, score=${r.score}, ${r.diffLines ?? 0} lines`).join("\n")}
|
|
200
|
+
|
|
201
|
+
In 2-3 sentences, tell the user the most likely root cause (task too vague, fitness too strict, engines lacking context, etc.) and one concrete next move. End with a confidence percentage.`;
|
|
202
|
+
dispatch({ type: "spinner-start", message: "Cesar diagnosing forge failure\u2026", color });
|
|
203
|
+
let response = "";
|
|
204
|
+
try {
|
|
205
|
+
const gen = session.send({ message: noWinnerPrompt, signal: void 0 });
|
|
206
|
+
for await (const chunk of gen) {
|
|
207
|
+
if (chunk.type === "text") response += chunk.content;
|
|
208
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
dispatch({ type: "spinner-stop" });
|
|
213
|
+
if (response.trim()) {
|
|
214
|
+
dispatch({ type: "header", title: "Cesar \u2014 No Winner" });
|
|
215
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response.trim() });
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function cesarJudgeForge(manifest, dispatch, ctx) {
|
|
221
|
+
let session;
|
|
222
|
+
try {
|
|
223
|
+
session = await ensureCesarSession(ctx);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
if (!session.alive) return null;
|
|
228
|
+
const cesarEngineId = ctx.config.cesarEngine ?? ctx.config.forgeFixedStarter ?? "claude";
|
|
229
|
+
const color = ENGINE_COLORS[cesarEngineId] ?? 124;
|
|
230
|
+
const engineSummaries = [];
|
|
231
|
+
const patchTexts = [];
|
|
232
|
+
for (const [id, result] of Object.entries(manifest.results)) {
|
|
233
|
+
let patchSummary = "(no patch)";
|
|
234
|
+
const patchPath = manifest.patches[id];
|
|
235
|
+
if (patchPath) {
|
|
236
|
+
try {
|
|
237
|
+
const patch = readFileSync(patchPath, "utf-8");
|
|
238
|
+
patchTexts.push(patch);
|
|
239
|
+
const lines = patch.split("\n");
|
|
240
|
+
patchSummary = lines.slice(0, 150).join("\n");
|
|
241
|
+
if (lines.length > 150) patchSummary += `
|
|
242
|
+
... (${lines.length - 150} more lines)`;
|
|
243
|
+
} catch {
|
|
244
|
+
patchSummary = "(patch unreadable)";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
engineSummaries.push(
|
|
248
|
+
`### ${id}
|
|
249
|
+
- Score: ${result.score} | Pass: ${result.pass} | Diff: ${result.diffLines} lines | Files: ${result.filesChanged} | Time: ${result.durationSec}s
|
|
250
|
+
\`\`\`diff
|
|
251
|
+
${patchSummary}
|
|
252
|
+
\`\`\``
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const judgePrompt = `You are judging forge results. Multiple engines competed on this task.
|
|
256
|
+
|
|
257
|
+
TASK: ${manifest.task}
|
|
258
|
+
FITNESS COMMAND: ${manifest.fitnessCmd}
|
|
259
|
+
AUTOMATIC WINNER: ${manifest.winner ?? "none"}
|
|
260
|
+
|
|
261
|
+
## ENGINE RESULTS
|
|
262
|
+
|
|
263
|
+
${engineSummaries.join("\n\n")}
|
|
264
|
+
|
|
265
|
+
## YOUR JOB
|
|
266
|
+
|
|
267
|
+
Evaluate each engine's code. Identify:
|
|
268
|
+
1. **Overall winner** \u2014 who produced the best result and why
|
|
269
|
+
2. **Per-engine strengths** \u2014 where each engine did something BETTER than others (architecture, error handling, tests, edge cases, code style, performance)
|
|
270
|
+
3. **Convergence plan** \u2014 if multiple engines have complementary strengths, list which parts from which engine should be merged. Only suggest convergence if it would genuinely improve the result.
|
|
271
|
+
|
|
272
|
+
Respond in this EXACT format:
|
|
273
|
+
|
|
274
|
+
WINNER: {engineId}
|
|
275
|
+
STRENGTHS:
|
|
276
|
+
- {engineId}: {category} \u2014 {reason}
|
|
277
|
+
- {engineId}: {category} \u2014 {reason}
|
|
278
|
+
CONVERGE: {yes|no}
|
|
279
|
+
CONVERGENCE:
|
|
280
|
+
- file:{path} fn:{name} from:{engineId} reason:{why}
|
|
281
|
+
SUMMARY: {your read \u2014 as long or as short as it actually deserves}`;
|
|
282
|
+
dispatch({ type: "spinner-start", message: "Cesar judging forge results\u2026", color });
|
|
283
|
+
let judgeResponse = "";
|
|
284
|
+
try {
|
|
285
|
+
const gen = session.send({ message: judgePrompt, signal: void 0 });
|
|
286
|
+
for await (const chunk of gen) {
|
|
287
|
+
if (chunk.type === "text") judgeResponse += chunk.content;
|
|
288
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
dispatch({ type: "spinner-stop" });
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
dispatch({ type: "spinner-stop" });
|
|
295
|
+
const judgment = parseForgeJudgment(judgeResponse, manifest);
|
|
296
|
+
const conf = parseConfidence(judgeResponse);
|
|
297
|
+
if (conf.value !== null) {
|
|
298
|
+
dispatch({ type: "info", message: confidenceBadge(conf.value) + " Cesar (judge)" });
|
|
299
|
+
}
|
|
300
|
+
if (judgment) {
|
|
301
|
+
try {
|
|
302
|
+
recordForgeJudgment(
|
|
303
|
+
judgment.winner,
|
|
304
|
+
manifest.winner ?? null,
|
|
305
|
+
Object.keys(manifest.results),
|
|
306
|
+
classifyTask(manifest.task),
|
|
307
|
+
manifest.forgeId,
|
|
308
|
+
judgment.summary,
|
|
309
|
+
judgment.strengths,
|
|
310
|
+
extractPatchFilePatterns(patchTexts.join("\n"))
|
|
311
|
+
);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.warn(`[agon] failed to record Cesar forge judgment: ${err instanceof Error ? err.message : String(err)}`);
|
|
314
|
+
}
|
|
315
|
+
dispatch({ type: "header", title: "Cesar's Judgment" });
|
|
316
|
+
dispatch({ type: "success", message: `Winner: ${judgment.winner}` });
|
|
317
|
+
if (judgment.strengths.length > 0) {
|
|
318
|
+
dispatch({ type: "info", message: "Per-engine strengths:" });
|
|
319
|
+
for (const s of judgment.strengths) {
|
|
320
|
+
const sColor = ENGINE_COLORS[s.engineId] ?? 124;
|
|
321
|
+
dispatch({ type: "engine-block", engineId: s.engineId, color: sColor, content: `${s.category} \u2014 ${s.reason}` });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
dispatch({ type: "info", message: judgment.summary });
|
|
325
|
+
if (judgment.shouldConverge && judgment.convergencePlan.length > 0) {
|
|
326
|
+
dispatch({ type: "header", title: "Convergence Plan" });
|
|
327
|
+
for (const entry of judgment.convergencePlan) {
|
|
328
|
+
const eColor = ENGINE_COLORS[entry.from] ?? 124;
|
|
329
|
+
dispatch({ type: "engine-block", engineId: entry.from, color: eColor, content: `${entry.file}:${entry.fn} \u2014 ${entry.reason}` });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return judgment;
|
|
334
|
+
}
|
|
335
|
+
async function cesarConvergeForge(manifest, judgment, dispatch, ctx) {
|
|
336
|
+
let session;
|
|
337
|
+
try {
|
|
338
|
+
session = await ensureCesarSession(ctx);
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
if (!session.alive) return null;
|
|
343
|
+
const cesarEngineId = ctx.config.cesarEngine ?? ctx.config.forgeFixedStarter ?? "claude";
|
|
344
|
+
const color = ENGINE_COLORS[cesarEngineId] ?? 124;
|
|
345
|
+
const patchContents = {};
|
|
346
|
+
if (judgment.winner && manifest.patches[judgment.winner]) {
|
|
347
|
+
try {
|
|
348
|
+
patchContents[judgment.winner] = readFileSync(manifest.patches[judgment.winner], "utf-8");
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const entry of judgment.convergencePlan) {
|
|
353
|
+
if (!patchContents[entry.from] && manifest.patches[entry.from]) {
|
|
354
|
+
try {
|
|
355
|
+
patchContents[entry.from] = readFileSync(manifest.patches[entry.from], "utf-8");
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const patchSections = Object.entries(patchContents).map(([id, content]) => {
|
|
361
|
+
const lines = content.split("\n");
|
|
362
|
+
const summary = lines.slice(0, 200).join("\n");
|
|
363
|
+
return `### ${id}'s patch
|
|
364
|
+
\`\`\`diff
|
|
365
|
+
${summary}
|
|
366
|
+
\`\`\``;
|
|
367
|
+
}).join("\n\n");
|
|
368
|
+
const planLines = judgment.convergencePlan.map(
|
|
369
|
+
(e) => `- Take ${e.fn} from ${e.from} (${e.file}) \u2014 ${e.reason}`
|
|
370
|
+
).join("\n");
|
|
371
|
+
const convergePrompt = `You are synthesizing a converged patch from multiple forge competitors.
|
|
372
|
+
|
|
373
|
+
TASK: ${manifest.task}
|
|
374
|
+
WINNER: ${judgment.winner}
|
|
375
|
+
|
|
376
|
+
## CONVERGENCE PLAN
|
|
377
|
+
${planLines}
|
|
378
|
+
|
|
379
|
+
## PATCHES TO MERGE
|
|
380
|
+
${patchSections}
|
|
381
|
+
|
|
382
|
+
## INSTRUCTIONS
|
|
383
|
+
Create a single unified diff that takes the best parts from each engine according to the convergence plan above.
|
|
384
|
+
- Start from the winner's patch as the base
|
|
385
|
+
- Replace specific functions/sections with the better versions from other engines
|
|
386
|
+
- Ensure the merged result is coherent \u2014 no duplicate functions, no conflicts
|
|
387
|
+
- Output ONLY the unified diff, nothing else. Start with --- and +++ lines.`;
|
|
388
|
+
dispatch({ type: "spinner-start", message: "Cesar synthesizing converged patch\u2026", color });
|
|
389
|
+
let convergedPatch = "";
|
|
390
|
+
try {
|
|
391
|
+
const gen = session.send({ message: convergePrompt, signal: void 0 });
|
|
392
|
+
for await (const chunk of gen) {
|
|
393
|
+
if (chunk.type === "text") convergedPatch += chunk.content;
|
|
394
|
+
if (chunk.type === "done" || chunk.type === "error") break;
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
dispatch({ type: "spinner-stop" });
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
dispatch({ type: "spinner-stop" });
|
|
401
|
+
let patch = convergedPatch.trim();
|
|
402
|
+
const fenceMatch = patch.match(/```(?:diff)?\n([\s\S]*?)```/);
|
|
403
|
+
if (fenceMatch) patch = fenceMatch[1].trim();
|
|
404
|
+
if (!patch || !patch.includes("---")) {
|
|
405
|
+
dispatch({ type: "warning", message: "Cesar could not produce a valid converged patch" });
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const convergePath = join(manifest.forgeDir, "convergence-patch.diff");
|
|
409
|
+
writeFileSync(convergePath, patch, "utf-8");
|
|
410
|
+
return convergePath;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/generated/models/session-results.ts
|
|
414
|
+
var SessionResultStore = class {
|
|
415
|
+
results = [];
|
|
416
|
+
add(result) {
|
|
417
|
+
this.results.push(result);
|
|
418
|
+
}
|
|
419
|
+
getResults() {
|
|
420
|
+
return [...this.results];
|
|
421
|
+
}
|
|
422
|
+
hasResults() {
|
|
423
|
+
return this.results.length > 0;
|
|
424
|
+
}
|
|
425
|
+
getLatest() {
|
|
426
|
+
return this.results.length > 0 ? this.results[this.results.length - 1] : null;
|
|
427
|
+
}
|
|
428
|
+
clear() {
|
|
429
|
+
this.results = [];
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var sessionResultStore = new SessionResultStore();
|
|
433
|
+
|
|
434
|
+
// src/generated/cesar/scoreboard.ts
|
|
435
|
+
function createScoreboard(runId, mode, engineIds) {
|
|
436
|
+
const now = Date.now();
|
|
437
|
+
return { runId, mode, startedAt: now, entries: engineIds.map((id) => Object.assign({}, { engineId: id, state: "waiting", progress: 0 })), overallState: "running" };
|
|
438
|
+
}
|
|
439
|
+
function scoreboardFind(board, engineId) {
|
|
440
|
+
return board.entries.find((e) => e.engineId === engineId);
|
|
441
|
+
}
|
|
442
|
+
function scoreboardUpdateProgress(board, engineId, progress) {
|
|
443
|
+
const entry = scoreboardFind(board, engineId);
|
|
444
|
+
if (!entry) {
|
|
445
|
+
return board;
|
|
446
|
+
}
|
|
447
|
+
entry.progress = Math.max(0, Math.min(100, Math.floor(progress)));
|
|
448
|
+
return board;
|
|
449
|
+
}
|
|
450
|
+
function scoreboardFinishEngine(board, engineId, opts) {
|
|
451
|
+
const entry = scoreboardFind(board, engineId);
|
|
452
|
+
if (!entry) {
|
|
453
|
+
return board;
|
|
454
|
+
}
|
|
455
|
+
entry.state = "done";
|
|
456
|
+
entry.finishedAt = Date.now();
|
|
457
|
+
entry.progress = 100;
|
|
458
|
+
if (opts?.score !== void 0) {
|
|
459
|
+
entry.score = opts.score;
|
|
460
|
+
}
|
|
461
|
+
if (opts?.result !== void 0) {
|
|
462
|
+
entry.result = opts.result;
|
|
463
|
+
}
|
|
464
|
+
scoreboardRecomputeOverall(board);
|
|
465
|
+
return board;
|
|
466
|
+
}
|
|
467
|
+
function scoreboardFailEngine(board, engineId, error) {
|
|
468
|
+
const entry = scoreboardFind(board, engineId);
|
|
469
|
+
if (!entry) {
|
|
470
|
+
return board;
|
|
471
|
+
}
|
|
472
|
+
entry.state = "failed";
|
|
473
|
+
entry.finishedAt = Date.now();
|
|
474
|
+
entry.error = error;
|
|
475
|
+
scoreboardRecomputeOverall(board);
|
|
476
|
+
return board;
|
|
477
|
+
}
|
|
478
|
+
function scoreboardRecomputeOverall(board) {
|
|
479
|
+
if (board.entries.every((e) => e.state === "done")) {
|
|
480
|
+
board.overallState = "done";
|
|
481
|
+
} else if (board.entries.every((e) => e.state === "failed")) {
|
|
482
|
+
board.overallState = "failed";
|
|
483
|
+
} else if (board.entries.every((e) => e.state === "done" || e.state === "failed" || e.state === "cancelled")) {
|
|
484
|
+
board.overallState = "done";
|
|
485
|
+
} else if (board.entries.some((e) => e.state === "running")) {
|
|
486
|
+
board.overallState = "running";
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function formatScoreboardLine(entry) {
|
|
490
|
+
const iconMap = {
|
|
491
|
+
waiting: "\u25C7",
|
|
492
|
+
running: "\u25D0",
|
|
493
|
+
done: "\u2714",
|
|
494
|
+
failed: "\u2718",
|
|
495
|
+
cancelled: "\u25C7"
|
|
496
|
+
};
|
|
497
|
+
const color = ENGINE_COLORS[entry.engineId] ?? 245;
|
|
498
|
+
const icon = iconMap[entry.state];
|
|
499
|
+
const bar = entry.state === "running" ? `${entry.progress}%` : entry.state === "done" && entry.score != null ? `score ${entry.score}` : "";
|
|
500
|
+
const result = entry.result ? ` \u2014 ${entry.result}` : "";
|
|
501
|
+
const error = entry.error ? ` [${entry.error}]` : "";
|
|
502
|
+
return `\x1B[38;5;${color}m${icon} ${entry.engineId}${bar ? ` ${bar}` : ""}${result}${error}\x1B[0m`;
|
|
503
|
+
}
|
|
504
|
+
function renderScoreboard(board) {
|
|
505
|
+
const lines = board.entries.map((e) => formatScoreboardLine(e));
|
|
506
|
+
return lines.join("\n");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/generated/cesar/checkpoint.ts
|
|
510
|
+
import { join as join2 } from "path";
|
|
511
|
+
import { mkdirSync, appendFileSync, existsSync, readFileSync as readFileSync2 } from "fs";
|
|
512
|
+
function createCheckpointId() {
|
|
513
|
+
return `chk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
514
|
+
}
|
|
515
|
+
function buildCheckpoint(runId, phase, mode, engineIds, metadata) {
|
|
516
|
+
const cwd = resolveWorkingDir();
|
|
517
|
+
let branch = "";
|
|
518
|
+
let changedFiles = [];
|
|
519
|
+
try {
|
|
520
|
+
branch = currentBranch(cwd);
|
|
521
|
+
} catch {
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
changedFiles = gitChangedFiles(cwd);
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
id: createCheckpointId(),
|
|
529
|
+
runId,
|
|
530
|
+
phase,
|
|
531
|
+
ts: Date.now(),
|
|
532
|
+
cwd,
|
|
533
|
+
branch,
|
|
534
|
+
changedFiles,
|
|
535
|
+
mode,
|
|
536
|
+
engineIds,
|
|
537
|
+
metadata
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function recordCheckpoint(cp, runsDir) {
|
|
541
|
+
try {
|
|
542
|
+
const dir = runsDir ?? RUNS_DIR;
|
|
543
|
+
mkdirSync(dir, { recursive: true });
|
|
544
|
+
appendFileSync(join2(dir, "checkpoints.jsonl"), JSON.stringify(cp) + "\n");
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/telemetry/index.ts
|
|
550
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
551
|
+
import { join as join3, resolve } from "path";
|
|
552
|
+
import { homedir } from "os";
|
|
553
|
+
var DEFAULT_WINDOW = 50;
|
|
554
|
+
function getAgonHome() {
|
|
555
|
+
const override = process.env.AGON_HOME?.trim();
|
|
556
|
+
return override ? resolve(override) : join3(homedir(), ".agon");
|
|
557
|
+
}
|
|
558
|
+
function telemetryPath() {
|
|
559
|
+
return join3(getAgonHome(), "telemetry.json");
|
|
560
|
+
}
|
|
561
|
+
function classifyTaskType(mode, intent) {
|
|
562
|
+
const m = mode.toLowerCase();
|
|
563
|
+
const text = (intent ?? "").toLowerCase();
|
|
564
|
+
if (m === "review") return "review";
|
|
565
|
+
if (text.length > 0) {
|
|
566
|
+
if (/\b(fix|bug|patch|hotfix|debug|regression|crash|error|issue)\b/.test(text)) return "bugfix";
|
|
567
|
+
if (/\b(add|implement|create|new|feature|support|enable)\b/.test(text)) return "feature";
|
|
568
|
+
if (/\b(refactor|cleanup|clean.?up|restructure|rename|move|extract|simplify|consolidate)\b/.test(text)) return "refactor";
|
|
569
|
+
if (/\b(spec|design|plan|proposal|architect)\b/.test(text)) return "spec";
|
|
570
|
+
if (/\b(question|explain|how|what|why|help|understand|clarify)\b/.test(text)) return "question";
|
|
571
|
+
}
|
|
572
|
+
if (m === "build" || m === "agent" || m === "team-agent" || m === "agent-solo") return "build";
|
|
573
|
+
if (m === "pipeline") return "pipeline";
|
|
574
|
+
if (m === "brainstorm" || m === "team-brainstorm") return "question";
|
|
575
|
+
if (m === "tribunal" || m === "team-tribunal") return "spec";
|
|
576
|
+
if (m === "campfire") return "question";
|
|
577
|
+
if (m === "forge" || m === "team-forge" || m === "speculate") return "feature";
|
|
578
|
+
return "unknown";
|
|
579
|
+
}
|
|
580
|
+
var TelemetryLedger = class {
|
|
581
|
+
filePath;
|
|
582
|
+
constructor(filePath) {
|
|
583
|
+
this.filePath = filePath ?? telemetryPath();
|
|
584
|
+
}
|
|
585
|
+
/** Read the entire ledger file, returning an empty structure if missing/malformed. */
|
|
586
|
+
read() {
|
|
587
|
+
try {
|
|
588
|
+
const raw = readFileSync3(this.filePath, "utf-8");
|
|
589
|
+
const parsed = JSON.parse(raw);
|
|
590
|
+
if (parsed && Array.isArray(parsed.runs)) return parsed;
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
return { version: 1, runs: [] };
|
|
594
|
+
}
|
|
595
|
+
/** Persist the ledger to disk atomically. */
|
|
596
|
+
write(data) {
|
|
597
|
+
const dir = join3(this.filePath, "..");
|
|
598
|
+
mkdirSync2(dir, { recursive: true });
|
|
599
|
+
writeFileSync2(this.filePath, JSON.stringify(data, null, 2));
|
|
600
|
+
}
|
|
601
|
+
/** Append a single run record. */
|
|
602
|
+
append(record) {
|
|
603
|
+
const data = this.read();
|
|
604
|
+
this.write({ ...data, runs: [...data.runs, record] });
|
|
605
|
+
}
|
|
606
|
+
/** Return the last N runs, optionally filtered by mode. */
|
|
607
|
+
recentRuns(limit = DEFAULT_WINDOW, mode) {
|
|
608
|
+
const data = this.read();
|
|
609
|
+
let runs = data.runs;
|
|
610
|
+
if (mode) runs = runs.filter((r) => r.mode === mode);
|
|
611
|
+
return runs.slice(-limit);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Compute rolling win rate over the last `window` runs.
|
|
615
|
+
* Returns entries per mode found in the window, plus an overall entry.
|
|
616
|
+
*/
|
|
617
|
+
winRates(window = DEFAULT_WINDOW) {
|
|
618
|
+
const runs = this.recentRuns(window);
|
|
619
|
+
const byMode = /* @__PURE__ */ new Map();
|
|
620
|
+
for (const r of runs) {
|
|
621
|
+
const entry = byMode.get(r.mode) ?? { total: 0, wins: 0 };
|
|
622
|
+
entry.total += 1;
|
|
623
|
+
if (r.success) entry.wins += 1;
|
|
624
|
+
byMode.set(r.mode, entry);
|
|
625
|
+
}
|
|
626
|
+
const entries = [];
|
|
627
|
+
for (const [mode, { total, wins }] of byMode) {
|
|
628
|
+
entries.push({ mode, total, wins, rate: total > 0 ? wins / total : 0 });
|
|
629
|
+
}
|
|
630
|
+
const overallTotal = runs.length;
|
|
631
|
+
const overallWins = runs.filter((r) => r.success).length;
|
|
632
|
+
entries.push({
|
|
633
|
+
mode: "__overall__",
|
|
634
|
+
total: overallTotal,
|
|
635
|
+
wins: overallWins,
|
|
636
|
+
rate: overallTotal > 0 ? overallWins / overallTotal : 0
|
|
637
|
+
});
|
|
638
|
+
return entries;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
var defaultLedger = new TelemetryLedger();
|
|
642
|
+
function recordRun(result) {
|
|
643
|
+
const stats = typeof tracker?.getStats === "function" ? tracker.getStats() : null;
|
|
644
|
+
const record = {
|
|
645
|
+
id: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
646
|
+
timestamp: Date.now(),
|
|
647
|
+
mode: result.mode,
|
|
648
|
+
taskType: classifyTaskType(result.mode, result.intent),
|
|
649
|
+
winner: result.winner,
|
|
650
|
+
success: result.success,
|
|
651
|
+
durationMs: result.durationMs,
|
|
652
|
+
engineIds: result.engineIds ?? [],
|
|
653
|
+
completionState: result.completionState,
|
|
654
|
+
costEstimateUsd: stats?.totalCostUsd ?? void 0
|
|
655
|
+
};
|
|
656
|
+
defaultLedger.append(record);
|
|
657
|
+
return record;
|
|
658
|
+
}
|
|
659
|
+
function formatRunSummary(record) {
|
|
660
|
+
const mode = record.mode;
|
|
661
|
+
const winner = record.winner ?? "\u2014";
|
|
662
|
+
const cost = record.costEstimateUsd && record.costEstimateUsd > 0 ? `~${record.costEstimateUsd.toFixed(2)}` : "";
|
|
663
|
+
const mins = Math.floor(record.durationMs / 6e4);
|
|
664
|
+
const secs = Math.floor(record.durationMs % 6e4 / 1e3);
|
|
665
|
+
const time = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
666
|
+
const costText = cost ? ` \xB7 ${cost}` : "";
|
|
667
|
+
return `${mode} complete \u2192 ${winner} won${costText} \xB7 ${time}`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/generated/handlers/forge.ts
|
|
671
|
+
function handleForgeEvent(event, plan, engineStatus, dispatch, ctx) {
|
|
672
|
+
if (ctx.currentPlan?.state === "cancelled") return plan;
|
|
673
|
+
const id = event.engineId ?? "";
|
|
674
|
+
switch (event.type) {
|
|
675
|
+
case "baseline:start":
|
|
676
|
+
plan = mergeStepResult(plan, "baseline", { state: "running", attempts: [{ startedAt: (/* @__PURE__ */ new Date()).toISOString() }] });
|
|
677
|
+
break;
|
|
678
|
+
case "baseline:done":
|
|
679
|
+
plan = mergeStepResult(plan, "baseline", { state: "completed" });
|
|
680
|
+
if (event.data?.passes) {
|
|
681
|
+
dispatch({ type: "warning", message: "Baseline passes \u2014 fitness may be non-discriminating. Add a failing regression test, use forge validate mode, or allow no-diff validation." });
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
case "stage1:dispatch":
|
|
685
|
+
plan = mergeStepResult(plan, "dispatch", { state: "running", attempts: [{ startedAt: (/* @__PURE__ */ new Date()).toISOString() }] });
|
|
686
|
+
engineStatus[id] = "building";
|
|
687
|
+
break;
|
|
688
|
+
case "stage2:dispatch":
|
|
689
|
+
engineStatus[id] = "building";
|
|
690
|
+
break;
|
|
691
|
+
case "engine:worktree": {
|
|
692
|
+
if (id) {
|
|
693
|
+
engineStatus[id] = "building";
|
|
694
|
+
const worktreePath = String(event.data?.worktreePath ?? "");
|
|
695
|
+
if (worktreePath && engineStatus[`${id}:worktree`] !== worktreePath) {
|
|
696
|
+
engineStatus[`${id}:worktree`] = worktreePath;
|
|
697
|
+
dispatch({ type: "info", message: `Forge worktree ${id}: ${worktreePath}` });
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case "engine:pid":
|
|
703
|
+
dispatch({ type: "engine-pid", engineId: id, pid: Number(event.data?.pid ?? 0), phase: event.data?.phase });
|
|
704
|
+
break;
|
|
705
|
+
case "engine:pid-clear":
|
|
706
|
+
dispatch({ type: "engine-pid-clear", engineId: id });
|
|
707
|
+
break;
|
|
708
|
+
case "engine:fallback":
|
|
709
|
+
engineStatus[String(event.data?.to ?? "")] = "fallback";
|
|
710
|
+
dispatch({ type: "warning", message: `${String(event.data?.from ?? id)} failed during ${String(event.data?.phase ?? "forge")} \u2014 retrying on ${String(event.data?.to ?? "fallback")}` });
|
|
711
|
+
break;
|
|
712
|
+
case "engine:failed": {
|
|
713
|
+
engineStatus[id] = "failed";
|
|
714
|
+
engineStatus[`${id}:score`] = "0";
|
|
715
|
+
const errMsg = String(event.data?.error ?? "failed");
|
|
716
|
+
engineStatus[`${id}:error`] = errMsg;
|
|
717
|
+
const snippet = errMsg.length > 120 ? errMsg.slice(0, 120) + "\u2026" : errMsg;
|
|
718
|
+
dispatch({ type: "warning", message: `${id} failed: ${snippet}` });
|
|
719
|
+
dispatch({ type: "engine-pid-clear", engineId: id });
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case "stage1:accepted":
|
|
723
|
+
engineStatus[id] = "done";
|
|
724
|
+
engineStatus[`${id}:score`] = String(event.data?.score ?? "?");
|
|
725
|
+
break;
|
|
726
|
+
case "stage1:score":
|
|
727
|
+
case "stage2:score": {
|
|
728
|
+
if (id) {
|
|
729
|
+
const passed = event.data?.pass !== false;
|
|
730
|
+
engineStatus[id] = passed ? "done" : "failed";
|
|
731
|
+
engineStatus[`${id}:score`] = String(event.data?.score ?? "?");
|
|
732
|
+
if (!passed) engineStatus[`${id}:error`] = Number(event.data?.score ?? 0) === 0 ? "no candidate changes or fitness failed" : "fitness failed";
|
|
733
|
+
}
|
|
734
|
+
const scoreStep = plan.steps.find((s) => s.id === "score");
|
|
735
|
+
if (scoreStep && scoreStep.result.state === "pending") {
|
|
736
|
+
plan = mergeStepResult(plan, "score", { state: "running", attempts: [{ startedAt: (/* @__PURE__ */ new Date()).toISOString() }] });
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
case "winner:determined": {
|
|
741
|
+
plan = mergeStepResult(plan, "dispatch", { state: "completed" });
|
|
742
|
+
plan = mergeStepResult(plan, "score", { state: "completed" });
|
|
743
|
+
plan = mergeStepResult(plan, "winner", { state: "completed" });
|
|
744
|
+
if (event.data?.winner) {
|
|
745
|
+
const winnerId = String(event.data.winner);
|
|
746
|
+
engineStatus[winnerId] = "done";
|
|
747
|
+
engineStatus[`${winnerId}:score`] = String(event.data.bestScore ?? "?");
|
|
748
|
+
engineStatus["__winner"] = winnerId;
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
case "forge:no-candidate-diff": {
|
|
753
|
+
const count = Number(event.data?.count ?? 0);
|
|
754
|
+
const toolLoopLimit = Number(event.data?.toolLoopLimit ?? 0);
|
|
755
|
+
if (toolLoopLimit > 0) {
|
|
756
|
+
dispatch({ type: "warning", message: `Forge produced no candidate diff: ${toolLoopLimit}/${count} engine(s) hit the tool-loop limit before producing a patch.` });
|
|
757
|
+
} else {
|
|
758
|
+
dispatch({ type: "warning", message: `Forge produced no candidate diff from ${count} engine(s).` });
|
|
759
|
+
}
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
case "synthesis:start": {
|
|
763
|
+
plan = mergeStepResult(plan, "synthesis", { state: "running", attempts: [{ startedAt: (/* @__PURE__ */ new Date()).toISOString() }] });
|
|
764
|
+
const winnerId = id || engineStatus["__winner"] || "";
|
|
765
|
+
if (winnerId) engineStatus[winnerId] = "synthesizing";
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
case "synthesis:done": {
|
|
769
|
+
plan = mergeStepResult(plan, "synthesis", { state: "completed" });
|
|
770
|
+
const winnerId = id || engineStatus["__winner"] || "";
|
|
771
|
+
if (winnerId && engineStatus[winnerId] === "synthesizing") engineStatus[winnerId] = "done";
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
ctx.setCurrentPlan(plan);
|
|
776
|
+
return plan;
|
|
777
|
+
}
|
|
778
|
+
function applyForgePatchToWorkspace(winnerId, patchContent, dispatch, baseSha, baseBranch) {
|
|
779
|
+
if (!patchContent.trim()) {
|
|
780
|
+
dispatch({ type: "info", message: `Winner ${winnerId} produced an empty patch.` });
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
const cwd = resolveWorkingDir();
|
|
784
|
+
const movedHead = headChanged(cwd, baseSha ?? null);
|
|
785
|
+
if (movedHead.changed) {
|
|
786
|
+
dispatch({ type: "warning", message: `HEAD moved (${(baseSha ?? "?").slice(0, 8)} \u2192 ${(movedHead.current ?? "?").slice(0, 8)}) since this run started \u2014 not auto-applying. Review with /apply.` });
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
const movedBranch = branchChanged(cwd, baseBranch ?? null);
|
|
790
|
+
if (movedBranch.changed) {
|
|
791
|
+
dispatch({ type: "warning", message: `Branch changed (${baseBranch} \u2192 ${movedBranch.current ?? "?"}) since this run started \u2014 not auto-applying. Review with /apply.` });
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
const lock = acquireApplyLock(cwd, `forge-apply ${winnerId}`);
|
|
795
|
+
if (!lock.acquired) {
|
|
796
|
+
const who = lock.heldBy ? ` (pid ${lock.heldBy.pid}: ${lock.heldBy.action})` : "";
|
|
797
|
+
dispatch({ type: "warning", message: `Another apply is in progress in this checkout${who} \u2014 not auto-applying. Review with /apply once it finishes.` });
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const result = applyPatchWithUndo(cwd, patchContent);
|
|
802
|
+
if (result.ok) {
|
|
803
|
+
dispatch({ type: "success", message: `Applied winner patch from ${winnerId}` });
|
|
804
|
+
if (result.undoToken) dispatch({ type: "info", message: "Undo available: /undo" });
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
dispatch({ type: "warning", message: `Winner patch not applied automatically: ${result.error ?? "unknown error"}` });
|
|
808
|
+
return false;
|
|
809
|
+
} finally {
|
|
810
|
+
if (lock.sessionUUID) releaseApplyLock(cwd, lock.sessionUUID);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function extractFitnessCommandFromCesarOutput(output) {
|
|
814
|
+
let text = String(output ?? "").trim();
|
|
815
|
+
if (!text) return null;
|
|
816
|
+
text = text.replace(/^```(?:json|bash|sh)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
817
|
+
let candidate = null;
|
|
818
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
819
|
+
if (jsonMatch) {
|
|
820
|
+
try {
|
|
821
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
822
|
+
if (parsed && typeof parsed.fitnessCmd === "string") candidate = parsed.fitnessCmd;
|
|
823
|
+
else if (parsed && typeof parsed.command === "string") candidate = parsed.command;
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (!candidate) {
|
|
828
|
+
candidate = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0 && !/^(```|fitnessCmd\s*:|command\s*:)/i.test(line)) ?? null;
|
|
829
|
+
}
|
|
830
|
+
if (!candidate) return null;
|
|
831
|
+
candidate = candidate.replace(/^`|`$/g, "").trim();
|
|
832
|
+
candidate = candidate.replace(/^(fitnessCmd|command)\s*:\s*/i, "").trim();
|
|
833
|
+
if (!candidate || candidate.length > 500 || /[\r\n\0]/.test(candidate)) return null;
|
|
834
|
+
return candidate;
|
|
835
|
+
}
|
|
836
|
+
function extractGithubLiterals(text) {
|
|
837
|
+
return [...new Set((String(text ?? "").match(/(?:https?:\/\/)?github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+/g) ?? []).map((m) => m.replace(/^https?:\/\//, "").replace(/[).,;:'"`\]]+$/g, "")))];
|
|
838
|
+
}
|
|
839
|
+
function repairFitnessCommandTaskLiterals(task, command) {
|
|
840
|
+
const taskUrls = extractGithubLiterals(task);
|
|
841
|
+
if (taskUrls.length === 0) return command;
|
|
842
|
+
const taskUrlSet = new Set(taskUrls);
|
|
843
|
+
let repaired = command;
|
|
844
|
+
for (const cmdUrl of extractGithubLiterals(command)) {
|
|
845
|
+
if (taskUrlSet.has(cmdUrl)) continue;
|
|
846
|
+
const cmdParts = cmdUrl.split("/");
|
|
847
|
+
const replacement = taskUrls.length === 1 ? taskUrls[0] : taskUrls.find((url) => {
|
|
848
|
+
const parts = url.split("/");
|
|
849
|
+
return parts[2]?.toLowerCase() === cmdParts[2]?.toLowerCase();
|
|
850
|
+
});
|
|
851
|
+
if (replacement) {
|
|
852
|
+
repaired = repaired.split(cmdUrl).join(replacement);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return repaired;
|
|
856
|
+
}
|
|
857
|
+
function normalizeGithubRemoteLiteral(remote) {
|
|
858
|
+
const raw = String(remote ?? "").trim();
|
|
859
|
+
if (!raw) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const cleaned = raw.replace(/\.git$/, "");
|
|
863
|
+
const ssh = cleaned.match(/^git@github\.com:([^/]+)\/(.+)$/);
|
|
864
|
+
if (ssh) {
|
|
865
|
+
return `github.com/${ssh[1]}/${ssh[2]}`;
|
|
866
|
+
}
|
|
867
|
+
const https = cleaned.match(/^https?:\/\/github\.com\/([^/]+)\/(.+)$/);
|
|
868
|
+
if (https) {
|
|
869
|
+
return `github.com/${https[1]}/${https[2]}`;
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
function detectGithubRemoteLiteral(cwd) {
|
|
874
|
+
try {
|
|
875
|
+
const remote = execFileSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
876
|
+
return normalizeGithubRemoteLiteral(remote);
|
|
877
|
+
} catch (e) {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function repairFitnessCommandRepositoryLiteral(command, repoLiteral) {
|
|
882
|
+
if (!repoLiteral) {
|
|
883
|
+
return command;
|
|
884
|
+
}
|
|
885
|
+
let repaired = command;
|
|
886
|
+
for (const cmdUrl of extractGithubLiterals(command)) {
|
|
887
|
+
if (cmdUrl === repoLiteral) {
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
repaired = repaired.split(cmdUrl).join(repoLiteral);
|
|
891
|
+
}
|
|
892
|
+
return repaired;
|
|
893
|
+
}
|
|
894
|
+
function taskExplicitlyMentionsLiteral(task, literal) {
|
|
895
|
+
const taskLower = String(task ?? "").toLowerCase();
|
|
896
|
+
const literalLower = String(literal ?? "").toLowerCase();
|
|
897
|
+
if (!literalLower) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
if (taskLower.includes(literalLower)) {
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
const escaped = literalLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
904
|
+
return new RegExp(`\\b(?:no|avoid|without|forbid|exclude|do not mention)\\s+${escaped}\\b`, "i").test(taskLower);
|
|
905
|
+
}
|
|
906
|
+
function taskWantsRepositoryLinkCheck(task) {
|
|
907
|
+
const t = String(task ?? "").toLowerCase();
|
|
908
|
+
if (/github\.com\/[a-z0-9_.-]+\/[a-z0-9_.-]+/i.test(t)) {
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
return /\b(?:github|repo(?:sitory)?\s+(?:url|link)|project\s+(?:url|link)|source\s+(?:url|link)|footer\s+(?:with|containing|link)|public\s+(?:repo|repository|link|url))\b/i.test(t);
|
|
912
|
+
}
|
|
913
|
+
function repairOverbroadForbiddenLiterals(task, command, repoLiteral) {
|
|
914
|
+
const forbidden = extractFitnessStringArray(command, "forbidden");
|
|
915
|
+
if (forbidden.length === 0 || !repoLiteral) return command;
|
|
916
|
+
const parts = repoLiteral.split("/");
|
|
917
|
+
const protectedLiterals = /* @__PURE__ */ new Set();
|
|
918
|
+
if (parts[1]) protectedLiterals.add(parts[1]);
|
|
919
|
+
if (parts[2]) protectedLiterals.add(parts[2]);
|
|
920
|
+
protectedLiterals.add(repoLiteral);
|
|
921
|
+
protectedLiterals.add(`https://${repoLiteral}`);
|
|
922
|
+
const kept = forbidden.filter((f) => {
|
|
923
|
+
const isProtected = [...protectedLiterals].some((p) => p.toLowerCase() === f.toLowerCase());
|
|
924
|
+
if (!isProtected) return true;
|
|
925
|
+
return taskExplicitlyMentionsLiteral(task, f);
|
|
926
|
+
});
|
|
927
|
+
if (kept.length === forbidden.length) return command;
|
|
928
|
+
const serialized = kept.map((v) => `'${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`).join(",");
|
|
929
|
+
return command.replace(/forbidden\s*=\s*\[[^\]]*\]/, `forbidden=[${serialized}]`);
|
|
930
|
+
}
|
|
931
|
+
function extractFitnessStringArray(command, name) {
|
|
932
|
+
const re = new RegExp(`${name}\\s*=\\s*\\[([^\\]]*)\\]`);
|
|
933
|
+
const match = String(command ?? "").match(re);
|
|
934
|
+
if (!match) return [];
|
|
935
|
+
const values = [];
|
|
936
|
+
const body = match[1];
|
|
937
|
+
const stringRe = /(['"])((?:\\.|(?!\1).)*)\1/g;
|
|
938
|
+
let m;
|
|
939
|
+
while ((m = stringRe.exec(body)) !== null) {
|
|
940
|
+
values.push(m[2].replace(/\\(['"\\])/g, "$1"));
|
|
941
|
+
}
|
|
942
|
+
return values;
|
|
943
|
+
}
|
|
944
|
+
function repairContradictoryFitnessLiterals(command) {
|
|
945
|
+
const required = extractFitnessStringArray(command, "required");
|
|
946
|
+
const forbidden = extractFitnessStringArray(command, "forbidden");
|
|
947
|
+
if (required.length === 0 || forbidden.length === 0) return command;
|
|
948
|
+
const kept = forbidden.filter((f) => {
|
|
949
|
+
const lower = f.toLowerCase();
|
|
950
|
+
return !required.some((r) => {
|
|
951
|
+
const req = r.toLowerCase();
|
|
952
|
+
return req.includes(lower) || lower.includes(req);
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
if (kept.length === forbidden.length) return command;
|
|
956
|
+
const serialized = kept.map((v) => `'${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`).join(",");
|
|
957
|
+
return command.replace(/forbidden\s*=\s*\[[^\]]*\]/, `forbidden=[${serialized}]`);
|
|
958
|
+
}
|
|
959
|
+
function validateFitnessCommandIntent(task, command, repoLiteral) {
|
|
960
|
+
const cmd = String(command ?? "");
|
|
961
|
+
if (!cmd.trim()) {
|
|
962
|
+
return { ok: false, reason: "empty fitness command" };
|
|
963
|
+
}
|
|
964
|
+
const githubLiterals = extractGithubLiterals(cmd);
|
|
965
|
+
if (githubLiterals.length > 0 && !taskWantsRepositoryLinkCheck(task)) {
|
|
966
|
+
return { ok: false, reason: `fitness added repository URL check without repo-link intent: ${githubLiterals.join(", ")}` };
|
|
967
|
+
}
|
|
968
|
+
const required = extractFitnessStringArray(cmd, "required");
|
|
969
|
+
const forbidden = extractFitnessStringArray(cmd, "forbidden");
|
|
970
|
+
for (const r of required) {
|
|
971
|
+
const req = r.toLowerCase();
|
|
972
|
+
for (const f of forbidden) {
|
|
973
|
+
const forb = f.toLowerCase();
|
|
974
|
+
if (req.includes(forb) || forb.includes(req)) {
|
|
975
|
+
return { ok: false, reason: `fitness requires and forbids overlapping literal "${f}"` };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (repoLiteral) {
|
|
980
|
+
const owner = repoLiteral.split("/")[1] ?? "";
|
|
981
|
+
if (owner) {
|
|
982
|
+
const overbroad = forbidden.find((f) => f.toLowerCase() === owner.toLowerCase() && !taskExplicitlyMentionsLiteral(task, f));
|
|
983
|
+
if (overbroad) {
|
|
984
|
+
return { ok: false, reason: `fitness forbids local repo identity "${overbroad}" without explicit task intent` };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return { ok: true };
|
|
989
|
+
}
|
|
990
|
+
function repairForgeTaskRepositoryLiteral(task, cwd) {
|
|
991
|
+
const repoLiteral = detectGithubRemoteLiteral(cwd);
|
|
992
|
+
return repairFitnessCommandRepositoryLiteral(task, repoLiteral);
|
|
993
|
+
}
|
|
994
|
+
function describeProjectFitnessOptions(cwd) {
|
|
995
|
+
const lines = [];
|
|
996
|
+
const packageJsonPath = join4(cwd, "package.json");
|
|
997
|
+
if (existsSync2(packageJsonPath)) {
|
|
998
|
+
try {
|
|
999
|
+
const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
1000
|
+
const scripts = pkg && typeof pkg === "object" && pkg.scripts && typeof pkg.scripts === "object" ? Object.keys(pkg.scripts).sort() : [];
|
|
1001
|
+
if (scripts.length > 0) {
|
|
1002
|
+
lines.push(`package.json scripts: ${scripts.join(", ")}`);
|
|
1003
|
+
}
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
lines.push("package.json exists but could not be parsed");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (existsSync2(join4(cwd, "Cargo.toml"))) {
|
|
1009
|
+
lines.push("Cargo.toml present");
|
|
1010
|
+
}
|
|
1011
|
+
if (existsSync2(join4(cwd, "go.mod"))) {
|
|
1012
|
+
lines.push("go.mod present");
|
|
1013
|
+
}
|
|
1014
|
+
if (existsSync2(join4(cwd, "pyproject.toml"))) {
|
|
1015
|
+
lines.push("pyproject.toml present");
|
|
1016
|
+
}
|
|
1017
|
+
if (existsSync2(join4(cwd, "README.md"))) {
|
|
1018
|
+
lines.push("README.md present");
|
|
1019
|
+
}
|
|
1020
|
+
const repoLiteral = detectGithubRemoteLiteral(cwd);
|
|
1021
|
+
if (repoLiteral) {
|
|
1022
|
+
lines.push(`git origin: ${repoLiteral}`);
|
|
1023
|
+
}
|
|
1024
|
+
return lines.length > 0 ? lines.join("\n") : "No common project test metadata found.";
|
|
1025
|
+
}
|
|
1026
|
+
async function prepareForgeFitnessCommand(task, dispatch, ctx) {
|
|
1027
|
+
const config = ctx.config;
|
|
1028
|
+
const cwd = resolveWorkingDir();
|
|
1029
|
+
const cesarEngineId = String(config.cesarEngine ?? config.forgeFixedStarter ?? ctx.activeEngines()[0] ?? "").trim();
|
|
1030
|
+
if (!cesarEngineId) return null;
|
|
1031
|
+
let engine = null;
|
|
1032
|
+
try {
|
|
1033
|
+
engine = ctx.registry.get(cesarEngineId);
|
|
1034
|
+
} catch {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
if (!engine) return null;
|
|
1038
|
+
const outputDir = join4(RUNS_DIR, `fitness-${Date.now()}`);
|
|
1039
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
1040
|
+
dispatch({ type: "info", message: `Cesar preparing fitness check with ${cesarEngineId}\u2026` });
|
|
1041
|
+
const prompt = [
|
|
1042
|
+
"Prepare one shell fitness command for a forge run.",
|
|
1043
|
+
"",
|
|
1044
|
+
'Return strict JSON only: {"fitnessCmd":"<command>","reason":"<short reason>"}',
|
|
1045
|
+
"",
|
|
1046
|
+
"Rules:",
|
|
1047
|
+
"- The command must be non-interactive and suitable to run from the repository root.",
|
|
1048
|
+
"- Interpret the task semantically. Do not convert every phrase into a literal grep.",
|
|
1049
|
+
"- The command should verify the task requirements as specifically as possible using local files and local project facts.",
|
|
1050
|
+
"- Do not add GitHub/repository URL checks unless the task explicitly asks for a public repo link, GitHub URL, or footer/source link.",
|
|
1051
|
+
"- If a repo link is needed, use the git origin shown in Project signals as the source of truth.",
|
|
1052
|
+
"- Preserve exact literals only when the task actually requires those literals. Do not correct or invent spellings.",
|
|
1053
|
+
'- Do not turn broad wording like "avoid internal build details" into forbidding local identity names such as the GitHub owner or repo name.',
|
|
1054
|
+
"- Prefer existing project scripts when they fit; otherwise write a small shell/node/python check.",
|
|
1055
|
+
"- Do not include markdown, prose, or multiple commands outside the JSON string.",
|
|
1056
|
+
"",
|
|
1057
|
+
`Task:
|
|
1058
|
+
${task}`,
|
|
1059
|
+
"",
|
|
1060
|
+
`Project signals:
|
|
1061
|
+
${describeProjectFitnessOptions(cwd)}`
|
|
1062
|
+
].join("\n");
|
|
1063
|
+
try {
|
|
1064
|
+
const result = await ctx.adapter.dispatch({
|
|
1065
|
+
engine,
|
|
1066
|
+
prompt,
|
|
1067
|
+
cwd,
|
|
1068
|
+
mode: "exec",
|
|
1069
|
+
timeout: Math.min(config.timeout ?? 90, 90),
|
|
1070
|
+
outputDir
|
|
1071
|
+
});
|
|
1072
|
+
const cmd = extractFitnessCommandFromCesarOutput(String(result.stdout ?? ""));
|
|
1073
|
+
if (cmd) {
|
|
1074
|
+
const repoLiteral = detectGithubRemoteLiteral(cwd);
|
|
1075
|
+
const taskRepaired = repairFitnessCommandTaskLiterals(task, cmd);
|
|
1076
|
+
const repoRepaired = repairFitnessCommandRepositoryLiteral(taskRepaired, repoLiteral);
|
|
1077
|
+
const forbiddenRepaired = repairOverbroadForbiddenLiterals(task, repoRepaired, repoLiteral);
|
|
1078
|
+
const repaired = repairContradictoryFitnessLiterals(forbiddenRepaired);
|
|
1079
|
+
const validation = validateFitnessCommandIntent(task, repaired, repoLiteral);
|
|
1080
|
+
if (!validation.ok) {
|
|
1081
|
+
dispatch({ type: "warning", message: `Rejected Cesar fitness check: ${validation.reason}` });
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
if (repaired !== cmd) {
|
|
1085
|
+
dispatch({ type: "warning", message: `Repaired Cesar fitness literal: ${cmd} -> ${repaired}` });
|
|
1086
|
+
}
|
|
1087
|
+
dispatch({ type: "info", message: `Cesar fitness check: ${repaired}` });
|
|
1088
|
+
return repaired;
|
|
1089
|
+
}
|
|
1090
|
+
} catch (err) {
|
|
1091
|
+
dispatch({ type: "warning", message: `Cesar could not prepare a fitness check: ${err instanceof Error ? err.message : String(err)}` });
|
|
1092
|
+
}
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
function inferProjectFitnessCommand(cwd) {
|
|
1096
|
+
const packageJsonPath = join4(cwd, "package.json");
|
|
1097
|
+
if (existsSync2(packageJsonPath)) {
|
|
1098
|
+
try {
|
|
1099
|
+
const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
1100
|
+
const scripts = pkg && typeof pkg === "object" && pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
|
|
1101
|
+
const hasScript = (name) => typeof scripts[name] === "string" && String(scripts[name]).trim().length > 0;
|
|
1102
|
+
if (hasScript("test:ts")) return "npm run test:ts";
|
|
1103
|
+
if (hasScript("test")) return "npm test";
|
|
1104
|
+
if (hasScript("typecheck")) return "npm run typecheck";
|
|
1105
|
+
if (hasScript("build")) return "npm run build";
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (existsSync2(join4(cwd, "Cargo.toml"))) return "cargo test";
|
|
1110
|
+
if (existsSync2(join4(cwd, "go.mod"))) return "go test ./...";
|
|
1111
|
+
if (existsSync2(join4(cwd, "pyproject.toml"))) return "python -m pytest";
|
|
1112
|
+
return "git diff --check";
|
|
1113
|
+
}
|
|
1114
|
+
async function handleForge(task, fitnessCmd, dispatch, ctx, existingPlan, hardened, skipPlanApproval) {
|
|
1115
|
+
const forgeAbort = new AbortController();
|
|
1116
|
+
try {
|
|
1117
|
+
ensureAgonHome();
|
|
1118
|
+
if (!task) {
|
|
1119
|
+
dispatch({ type: "warning", message: 'No task provided. Usage: "fix the auth bug, test with npm test"' });
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
const forgeCwd = resolveWorkingDir();
|
|
1123
|
+
const preflightHead = (() => {
|
|
1124
|
+
try {
|
|
1125
|
+
return headSha(forgeCwd);
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
})();
|
|
1130
|
+
const preflightBranch = (() => {
|
|
1131
|
+
try {
|
|
1132
|
+
return currentBranch(forgeCwd);
|
|
1133
|
+
} catch {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
})();
|
|
1137
|
+
const originalTask = task;
|
|
1138
|
+
task = repairForgeTaskRepositoryLiteral(task, forgeCwd);
|
|
1139
|
+
if (task !== originalTask) {
|
|
1140
|
+
dispatch({ type: "warning", message: "Repaired forge task repository link to match git origin." });
|
|
1141
|
+
}
|
|
1142
|
+
let fitness = fitnessCmd;
|
|
1143
|
+
if (!fitness) {
|
|
1144
|
+
fitness = await prepareForgeFitnessCommand(task, dispatch, ctx);
|
|
1145
|
+
}
|
|
1146
|
+
if (!fitness) {
|
|
1147
|
+
fitness = inferProjectFitnessCommand(resolveWorkingDir());
|
|
1148
|
+
dispatch({ type: "warning", message: `Cesar did not provide a fitness check; falling back to: ${fitness}` });
|
|
1149
|
+
}
|
|
1150
|
+
const allEngines = ctx.activeEngines();
|
|
1151
|
+
const engines = filterDefaultOrchestrationEngines(allEngines);
|
|
1152
|
+
const excluded = allEngines.filter((id) => !engines.includes(id));
|
|
1153
|
+
if (excluded.length > 0) dispatch({ type: "info", message: `Skipping disabled orchestration engines: ${excluded.join(", ")}` });
|
|
1154
|
+
if (engines.length === 0) {
|
|
1155
|
+
dispatch({ type: "error", message: "No engines available. Install at least one AI CLI tool." });
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
const config = ctx.config;
|
|
1159
|
+
let plan;
|
|
1160
|
+
if (existingPlan) {
|
|
1161
|
+
plan = startPlan(existingPlan);
|
|
1162
|
+
ctx.setCurrentPlan(plan);
|
|
1163
|
+
savePlan(plan);
|
|
1164
|
+
} else {
|
|
1165
|
+
const ws = getActiveWorkspace();
|
|
1166
|
+
const snapshot = ws ? snapshotWorkspace(ws) : { id: "cwd", path: forgeCwd, headSha: "unknown", branch: "unknown", dirty: false };
|
|
1167
|
+
const forgeSteps = [
|
|
1168
|
+
{ id: "baseline", kind: "fitness", label: "Baseline fitness check", effects: ["exec"] },
|
|
1169
|
+
{ id: "dispatch", kind: "dispatch", label: `Dispatch engines: ${engines.join(", ")}`, effects: ["exec", "write", "network"] },
|
|
1170
|
+
{ id: "score", kind: "fitness", label: "Score engine results", effects: ["exec", "read"] },
|
|
1171
|
+
{ id: "winner", kind: "dispatch", label: "Determine winner", effects: ["read"] }
|
|
1172
|
+
];
|
|
1173
|
+
if (config.forgeEnableSynthesis) {
|
|
1174
|
+
forgeSteps.push({ id: "synthesis", kind: "synthesis", label: "Critique & synthesize", effects: ["exec", "write", "network"] });
|
|
1175
|
+
}
|
|
1176
|
+
plan = createPlan(
|
|
1177
|
+
{ type: "forge", task, fitnessCmd: fitness, engines, hardened: hardened ?? false },
|
|
1178
|
+
snapshot,
|
|
1179
|
+
forgeSteps
|
|
1180
|
+
);
|
|
1181
|
+
ctx.setCurrentPlan(plan);
|
|
1182
|
+
dispatch({ type: "plan", plan });
|
|
1183
|
+
const approvalLevel = config.approvalLevel ?? "plan";
|
|
1184
|
+
if (!skipPlanApproval && approvalLevel !== "auto") {
|
|
1185
|
+
const answer = await ctx.askQuestion("Approve plan? [Y/n]");
|
|
1186
|
+
if (answer.trim().toLowerCase() === "n") {
|
|
1187
|
+
plan = cancelPlan(plan);
|
|
1188
|
+
ctx.setCurrentPlan(plan);
|
|
1189
|
+
savePlan(plan);
|
|
1190
|
+
dispatch({ type: "info", message: "Plan cancelled." });
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
plan = approvePlan(plan);
|
|
1195
|
+
plan = startPlan(plan);
|
|
1196
|
+
ctx.setCurrentPlan(plan);
|
|
1197
|
+
savePlan(plan);
|
|
1198
|
+
}
|
|
1199
|
+
const forgeDir = join4(RUNS_DIR, `forge-${Date.now()}`);
|
|
1200
|
+
mkdirSync3(forgeDir, { recursive: true });
|
|
1201
|
+
dispatch({ type: "info", message: `Forge run dir: ${forgeDir}` });
|
|
1202
|
+
const projectCtx = scanProjectContext(forgeCwd, config.projectContext || void 0, config.contextFormat);
|
|
1203
|
+
const engineStatus = {};
|
|
1204
|
+
const startTime = Date.now();
|
|
1205
|
+
const runId = `forge-${Date.now()}`;
|
|
1206
|
+
const scoreboard = createScoreboard(runId, "forge", engines);
|
|
1207
|
+
const preCp = buildCheckpoint(runId, "pre-dispatch", "forge", engines, { task, fitnessCmd: fitness, hardened: hardened ?? false });
|
|
1208
|
+
recordCheckpoint(preCp);
|
|
1209
|
+
const agentEngineIds = /* @__PURE__ */ new Set();
|
|
1210
|
+
for (const id of engines) {
|
|
1211
|
+
try {
|
|
1212
|
+
const eng = ctx.registry.get(id);
|
|
1213
|
+
if (eng.agent || eng.api) agentEngineIds.add(id);
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
let _lastProgressSig = "";
|
|
1218
|
+
const progressInterval = setInterval(() => {
|
|
1219
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
1220
|
+
const progress = engines.map((id) => {
|
|
1221
|
+
const status = engineStatus[id] ?? "preparing";
|
|
1222
|
+
return {
|
|
1223
|
+
id,
|
|
1224
|
+
status: status === "done" ? `done (${engineStatus[`${id}:score`] ?? "?"})` : status,
|
|
1225
|
+
elapsed,
|
|
1226
|
+
done: status === "done",
|
|
1227
|
+
failed: status === "failed",
|
|
1228
|
+
score: engineStatus[`${id}:score`],
|
|
1229
|
+
isAgent: agentEngineIds.has(id)
|
|
1230
|
+
};
|
|
1231
|
+
});
|
|
1232
|
+
const sig = progress.map((p) => `${p.id}:${p.status}:${p.score ?? ""}`).join("|") + `@${elapsed}`;
|
|
1233
|
+
if (sig !== _lastProgressSig) {
|
|
1234
|
+
_lastProgressSig = sig;
|
|
1235
|
+
dispatch({ type: "progress-update", engines: progress });
|
|
1236
|
+
}
|
|
1237
|
+
for (const id of engines) {
|
|
1238
|
+
const s = engineStatus[id] ?? "waiting";
|
|
1239
|
+
if (s === "building") scoreboardUpdateProgress(scoreboard, id, Math.min(95, elapsed * 2));
|
|
1240
|
+
else if (s === "done") scoreboardFinishEngine(scoreboard, id, { score: Number(engineStatus[`${id}:score`]) || void 0 });
|
|
1241
|
+
else if (s === "failed") scoreboardFailEngine(scoreboard, id, engineStatus[`${id}:error`] ?? "failed");
|
|
1242
|
+
}
|
|
1243
|
+
}, 250);
|
|
1244
|
+
ctx.setActiveAbort(forgeAbort);
|
|
1245
|
+
let manifest;
|
|
1246
|
+
try {
|
|
1247
|
+
manifest = await runForge(
|
|
1248
|
+
{
|
|
1249
|
+
task,
|
|
1250
|
+
fitnessCmd: fitness,
|
|
1251
|
+
cwd: forgeCwd,
|
|
1252
|
+
forgeDir,
|
|
1253
|
+
engines,
|
|
1254
|
+
context: projectCtx,
|
|
1255
|
+
hardened: hardened ?? false,
|
|
1256
|
+
// Interactive forge: Cesar synthesizes the best-of-all result (only
|
|
1257
|
+
// active when config.forgeEnableSynthesis is on). Falls back to the
|
|
1258
|
+
// winner engine if cesarEngine is unset/unknown.
|
|
1259
|
+
synthEngine: String(ctx.config.cesarEngine ?? "").trim() || void 0,
|
|
1260
|
+
synthesize: "always",
|
|
1261
|
+
signal: forgeAbort.signal
|
|
1262
|
+
},
|
|
1263
|
+
ctx.registry,
|
|
1264
|
+
ctx.adapter,
|
|
1265
|
+
(event) => {
|
|
1266
|
+
plan = handleForgeEvent(event, plan, engineStatus, dispatch, ctx);
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
clearInterval(progressInterval);
|
|
1271
|
+
dispatch({ type: "progress-clear" });
|
|
1272
|
+
ctx.setActiveAbort(null);
|
|
1273
|
+
if (ctx.currentPlan?.state !== "cancelled") {
|
|
1274
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1275
|
+
plan = failPlan(plan, errorMsg);
|
|
1276
|
+
ctx.setCurrentPlan(plan);
|
|
1277
|
+
savePlan(plan);
|
|
1278
|
+
}
|
|
1279
|
+
throw err;
|
|
1280
|
+
}
|
|
1281
|
+
clearInterval(progressInterval);
|
|
1282
|
+
dispatch({ type: "progress-clear" });
|
|
1283
|
+
if (manifest.error) {
|
|
1284
|
+
dispatch({ type: "warning", message: `Forge failed: ${manifest.error}` });
|
|
1285
|
+
try {
|
|
1286
|
+
await cesarReviewForgeOutcome(manifest, dispatch, ctx);
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
console.warn(`[agon] Cesar review (error path) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1289
|
+
}
|
|
1290
|
+
if (ctx.currentPlan?.state !== "cancelled") {
|
|
1291
|
+
plan = failPlan(plan, manifest.error);
|
|
1292
|
+
ctx.setCurrentPlan(plan);
|
|
1293
|
+
savePlan(plan);
|
|
1294
|
+
}
|
|
1295
|
+
recordRun({
|
|
1296
|
+
mode: "forge",
|
|
1297
|
+
intent: task,
|
|
1298
|
+
winner: void 0,
|
|
1299
|
+
success: false,
|
|
1300
|
+
durationMs: Date.now() - startTime,
|
|
1301
|
+
engineIds: engines,
|
|
1302
|
+
fitnessCmd: fitness ?? void 0,
|
|
1303
|
+
completionState: "crashed",
|
|
1304
|
+
error: manifest.error
|
|
1305
|
+
});
|
|
1306
|
+
ctx.setActiveAbort(null);
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
if (manifest.alreadySatisfied) {
|
|
1310
|
+
dispatch({ type: "info", message: "Forge: task already satisfied \u2014 fitness command passes on baseline, no engines dispatched." });
|
|
1311
|
+
try {
|
|
1312
|
+
await cesarReviewForgeOutcome(manifest, dispatch, ctx);
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
console.warn(`[agon] Cesar review (already-satisfied path) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1315
|
+
}
|
|
1316
|
+
recordRun({
|
|
1317
|
+
mode: "forge",
|
|
1318
|
+
intent: task,
|
|
1319
|
+
winner: void 0,
|
|
1320
|
+
success: true,
|
|
1321
|
+
durationMs: Date.now() - startTime,
|
|
1322
|
+
engineIds: engines,
|
|
1323
|
+
fitnessCmd: fitness ?? void 0,
|
|
1324
|
+
completionState: "completed"
|
|
1325
|
+
});
|
|
1326
|
+
ctx.setActiveAbort(null);
|
|
1327
|
+
return { winner: null, patchPath: null, manifestPath: `${forgeDir}/manifest.json`, task, fitnessCmd: fitness };
|
|
1328
|
+
}
|
|
1329
|
+
if (manifest.singleSurvivor && manifest.winner) {
|
|
1330
|
+
const winner = manifest.winner;
|
|
1331
|
+
const score = manifest.results[winner]?.score ?? 0;
|
|
1332
|
+
dispatch({ type: "warning", message: `Only ${winner} produced a passing result (score ${score}). Other engines failed.` });
|
|
1333
|
+
try {
|
|
1334
|
+
await cesarReviewForgeOutcome(manifest, dispatch, ctx);
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
console.warn(`[agon] Cesar review (single-survivor path) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
for (const id of engines) {
|
|
1340
|
+
const s = engineStatus[id] ?? "waiting";
|
|
1341
|
+
if (s === "done") scoreboardFinishEngine(scoreboard, id, { score: Number(engineStatus[`${id}:score`]) || void 0 });
|
|
1342
|
+
else if (s === "failed") scoreboardFailEngine(scoreboard, id, engineStatus[`${id}:error`] ?? "failed");
|
|
1343
|
+
}
|
|
1344
|
+
dispatch({ type: "info", message: renderScoreboard(scoreboard) });
|
|
1345
|
+
const postCp = buildCheckpoint(runId, "post-dispatch", "forge", engines, { winner: manifest.winner ?? null, task });
|
|
1346
|
+
recordCheckpoint(postCp);
|
|
1347
|
+
const engineIds = Object.keys(manifest.results);
|
|
1348
|
+
const results = Object.values(manifest.results);
|
|
1349
|
+
dispatch({
|
|
1350
|
+
type: "scoreboard",
|
|
1351
|
+
title: "Forge Scoreboard",
|
|
1352
|
+
engineIds,
|
|
1353
|
+
winner: manifest.winner ?? void 0,
|
|
1354
|
+
metrics: [
|
|
1355
|
+
{ label: "Fitness", values: results.map((r) => r.pass ? `PASS (${r.score})` : "FAIL") },
|
|
1356
|
+
{ label: "Score", values: results.map((r) => String(r.score)) },
|
|
1357
|
+
{ label: "Diff size", values: results.map((r) => `${r.diffLines} lines`) },
|
|
1358
|
+
{ label: "Files changed", values: results.map((r) => String(r.filesChanged)) },
|
|
1359
|
+
{ label: "Time", values: results.map((r) => `${r.durationSec}s`) }
|
|
1360
|
+
]
|
|
1361
|
+
});
|
|
1362
|
+
let judgment = null;
|
|
1363
|
+
const passingEngines = Object.values(manifest.results).filter((r) => r.pass).length;
|
|
1364
|
+
if (passingEngines >= 2 && ctx.cesarSession) {
|
|
1365
|
+
try {
|
|
1366
|
+
judgment = await cesarJudgeForge(manifest, dispatch, ctx);
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
console.warn(`[agon] Cesar judge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1369
|
+
}
|
|
1370
|
+
} else if (passingEngines === 0 && !manifest.alreadySatisfied && ctx.cesarSession) {
|
|
1371
|
+
try {
|
|
1372
|
+
await cesarReviewForgeOutcome(manifest, dispatch, ctx);
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
console.warn(`[agon] Cesar review (no-winner path) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const finalWinner = judgment?.winner ?? manifest.winner;
|
|
1378
|
+
if (judgment?.shouldConverge && judgment.convergencePlan.length > 0 && finalWinner) {
|
|
1379
|
+
dispatch({ type: "info", message: "Cesar recommends convergence \u2014 synthesizing best-of-breed\u2026" });
|
|
1380
|
+
const answer = await ctx.askQuestion("Converge best parts from all engines? [Y/n]");
|
|
1381
|
+
if (answer.trim().toLowerCase() !== "n") {
|
|
1382
|
+
try {
|
|
1383
|
+
const convergedPath = await cesarConvergeForge(manifest, judgment, dispatch, ctx);
|
|
1384
|
+
if (convergedPath) {
|
|
1385
|
+
dispatch({ type: "success", message: "Convergence complete \u2014 merged patch ready" });
|
|
1386
|
+
const convergedContent = readFileSync4(convergedPath, "utf-8");
|
|
1387
|
+
const applied = applyForgePatchToWorkspace("convergence", convergedContent, dispatch, preflightHead, preflightBranch);
|
|
1388
|
+
if (!applied) dispatch({ type: "patch-review", winnerId: "convergence", patchPath: convergedPath, patchContent: convergedContent });
|
|
1389
|
+
if (ctx.config.sessionContinuity === true) {
|
|
1390
|
+
try {
|
|
1391
|
+
const convergeThread = loadOrCreateActiveThread(resolveWorkingDir());
|
|
1392
|
+
const capped = convergedContent.length > 9e4 ? convergedContent.slice(0, 9e4) + `
|
|
1393
|
+
|
|
1394
|
+
[... ${convergedContent.length - 9e4} more chars truncated \u2014 full patch at ${convergedPath}]` : convergedContent;
|
|
1395
|
+
convergeThread.append({
|
|
1396
|
+
role: "assistant",
|
|
1397
|
+
content: `[forge-convergence-diff] winner: convergence (best-of-breed)
|
|
1398
|
+
patch path: ${convergedPath}
|
|
1399
|
+
|
|
1400
|
+
${capped}`
|
|
1401
|
+
});
|
|
1402
|
+
await convergeThread.save();
|
|
1403
|
+
} catch (threadErr) {
|
|
1404
|
+
dispatch({ type: "warning", message: `Convergence diff NOT persisted to thread: ${threadErr instanceof Error ? threadErr.message : String(threadErr)}` });
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
dispatch({ type: "info", message: `Manifest: ${forgeDir}/manifest.json` });
|
|
1408
|
+
dispatch({ type: "info", message: `Result bundle: ${manifest.resultBundlePath ?? `${forgeDir}/result.json`}` });
|
|
1409
|
+
for (const step of plan.steps) {
|
|
1410
|
+
if (step.result.state === "pending" || step.result.state === "running") {
|
|
1411
|
+
plan = mergeStepResult(plan, step.id, { state: "completed" });
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
const winnerArtifacts2 = [
|
|
1415
|
+
{ type: "manifest", path: `${forgeDir}/manifest.json` },
|
|
1416
|
+
{ type: "patch", path: convergedPath, engineId: "convergence" }
|
|
1417
|
+
];
|
|
1418
|
+
plan = mergeStepResult(plan, "winner", { state: "completed", artifacts: winnerArtifacts2 });
|
|
1419
|
+
ctx.setCurrentPlan(plan);
|
|
1420
|
+
savePlan(plan);
|
|
1421
|
+
dispatch({ type: "info", message: `Plan: ${plan.id}` });
|
|
1422
|
+
sessionResultStore.add({
|
|
1423
|
+
type: "forge",
|
|
1424
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1425
|
+
question: task,
|
|
1426
|
+
engines: engineIds,
|
|
1427
|
+
winner: "convergence",
|
|
1428
|
+
data: {
|
|
1429
|
+
scoreboard: engineIds.map((id) => {
|
|
1430
|
+
const r = manifest.results[id];
|
|
1431
|
+
return { engineId: id, pass: r.pass, score: r.score, diffLines: r.diffLines, filesChanged: r.filesChanged, durationSec: r.durationSec };
|
|
1432
|
+
}),
|
|
1433
|
+
winner: "convergence",
|
|
1434
|
+
synthesis: manifest.synthesis ?? void 0
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
for (const [id, r] of Object.entries(manifest.results)) {
|
|
1438
|
+
tracker.record(id, { prompt: task, response: `score:${r.score} diff:${r.diffLines}` });
|
|
1439
|
+
}
|
|
1440
|
+
recordRun({
|
|
1441
|
+
mode: "forge",
|
|
1442
|
+
intent: task,
|
|
1443
|
+
winner: "convergence",
|
|
1444
|
+
success: true,
|
|
1445
|
+
durationMs: Date.now() - startTime,
|
|
1446
|
+
engineIds,
|
|
1447
|
+
fitnessCmd: fitness ?? void 0,
|
|
1448
|
+
completionState: "completed"
|
|
1449
|
+
});
|
|
1450
|
+
return {
|
|
1451
|
+
winner: "convergence",
|
|
1452
|
+
patchPath: convergedPath,
|
|
1453
|
+
manifestPath: `${forgeDir}/manifest.json`,
|
|
1454
|
+
task,
|
|
1455
|
+
fitnessCmd: fitness
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
dispatch({ type: "warning", message: `Convergence failed: ${err instanceof Error ? err.message : String(err)}. Falling back to winner.` });
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (finalWinner) {
|
|
1464
|
+
if (!judgment) dispatch({ type: "success", message: `Winner: ${finalWinner}` });
|
|
1465
|
+
const patchPath = manifest.patches[finalWinner];
|
|
1466
|
+
dispatch({ type: "info", message: `Patch: ${patchPath}` });
|
|
1467
|
+
if (patchPath) {
|
|
1468
|
+
try {
|
|
1469
|
+
const patchContent = readFileSync4(patchPath, "utf-8");
|
|
1470
|
+
const applied = applyForgePatchToWorkspace(finalWinner, patchContent, dispatch, preflightHead, preflightBranch);
|
|
1471
|
+
if (!applied) dispatch({ type: "patch-review", winnerId: finalWinner, patchPath, patchContent });
|
|
1472
|
+
if (ctx.config.sessionContinuity === true) {
|
|
1473
|
+
try {
|
|
1474
|
+
const cwd = resolveWorkingDir();
|
|
1475
|
+
const forgeThread = loadOrCreateActiveThread(cwd);
|
|
1476
|
+
const capped = patchContent.length > 9e4 ? patchContent.slice(0, 9e4) + `
|
|
1477
|
+
|
|
1478
|
+
[... ${patchContent.length - 9e4} more chars truncated \u2014 full patch at ${patchPath}]` : patchContent;
|
|
1479
|
+
forgeThread.append({
|
|
1480
|
+
role: "assistant",
|
|
1481
|
+
content: `[forge-diff] winner: ${finalWinner}
|
|
1482
|
+
patch path: ${patchPath}
|
|
1483
|
+
|
|
1484
|
+
${capped}`
|
|
1485
|
+
});
|
|
1486
|
+
await forgeThread.save();
|
|
1487
|
+
} catch (threadErr) {
|
|
1488
|
+
dispatch({ type: "warning", message: `Winner diff NOT persisted to thread: ${threadErr instanceof Error ? threadErr.message : String(threadErr)}` });
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
console.warn(`[agon] failed to read winning patch ${patchPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
} else {
|
|
1496
|
+
if (manifest.noDiffSummary) {
|
|
1497
|
+
const summary = manifest.noDiffSummary;
|
|
1498
|
+
if (Number(summary.toolLoopLimit ?? 0) > 0) {
|
|
1499
|
+
dispatch({ type: "error", message: `No candidate diff \u2014 ${summary.toolLoopLimit}/${summary.count} engine(s) hit the tool-loop limit before producing a patch.` });
|
|
1500
|
+
} else {
|
|
1501
|
+
dispatch({ type: "warning", message: "No candidate diff \u2014 engines produced no patch. Use validate/improve mode or a more discriminating fitness command for already-passing work." });
|
|
1502
|
+
}
|
|
1503
|
+
} else {
|
|
1504
|
+
dispatch({ type: "error", message: "No winner \u2014 all engines failed" });
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
dispatch({ type: "info", message: `Manifest: ${forgeDir}/manifest.json` });
|
|
1508
|
+
dispatch({ type: "info", message: `Result bundle: ${manifest.resultBundlePath ?? `${forgeDir}/result.json`}` });
|
|
1509
|
+
for (const step of plan.steps) {
|
|
1510
|
+
if (step.result.state === "pending" || step.result.state === "running") {
|
|
1511
|
+
plan = mergeStepResult(plan, step.id, { state: "completed" });
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const anyPassed = Object.values(manifest.results).some((r) => r.pass);
|
|
1515
|
+
const winnerForPlan = finalWinner ?? manifest.winner;
|
|
1516
|
+
const winnerArtifacts = [
|
|
1517
|
+
{ type: "manifest", path: `${forgeDir}/manifest.json` },
|
|
1518
|
+
...winnerForPlan && manifest.patches[winnerForPlan] ? [{ type: "patch", path: manifest.patches[winnerForPlan], engineId: winnerForPlan }] : []
|
|
1519
|
+
];
|
|
1520
|
+
plan = mergeStepResult(plan, "winner", { state: "completed", artifacts: winnerArtifacts });
|
|
1521
|
+
if (!anyPassed) {
|
|
1522
|
+
plan = { ...plan, state: "failed", currentStepId: null, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1523
|
+
}
|
|
1524
|
+
ctx.setCurrentPlan(plan);
|
|
1525
|
+
savePlan(plan);
|
|
1526
|
+
dispatch({ type: "info", message: `Plan: ${plan.id}` });
|
|
1527
|
+
sessionResultStore.add({
|
|
1528
|
+
type: "forge",
|
|
1529
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1530
|
+
question: task,
|
|
1531
|
+
engines: engineIds,
|
|
1532
|
+
winner: finalWinner ?? null,
|
|
1533
|
+
data: {
|
|
1534
|
+
scoreboard: engineIds.map((id, i) => {
|
|
1535
|
+
const r = results[i];
|
|
1536
|
+
return { engineId: id, pass: r.pass, score: r.score, diffLines: r.diffLines, filesChanged: r.filesChanged, durationSec: r.durationSec };
|
|
1537
|
+
}),
|
|
1538
|
+
winner: finalWinner ?? null,
|
|
1539
|
+
synthesis: manifest.synthesis ?? void 0
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
for (const [id, r] of Object.entries(manifest.results)) {
|
|
1543
|
+
tracker.record(id, { prompt: task, response: `score:${r.score} diff:${r.diffLines}` });
|
|
1544
|
+
}
|
|
1545
|
+
const runRecord = recordRun({
|
|
1546
|
+
mode: "forge",
|
|
1547
|
+
intent: task,
|
|
1548
|
+
winner: finalWinner ?? void 0,
|
|
1549
|
+
success: anyPassed,
|
|
1550
|
+
durationMs: Date.now() - startTime,
|
|
1551
|
+
engineIds,
|
|
1552
|
+
fitnessCmd: fitness ?? void 0,
|
|
1553
|
+
completionState: "completed"
|
|
1554
|
+
});
|
|
1555
|
+
if (!process.env.AGON_NO_SUMMARY) {
|
|
1556
|
+
dispatch({ type: "info", message: formatRunSummary(runRecord) });
|
|
1557
|
+
}
|
|
1558
|
+
return {
|
|
1559
|
+
winner: finalWinner ?? null,
|
|
1560
|
+
patchPath: finalWinner && manifest.patches[finalWinner] ? manifest.patches[finalWinner] : null,
|
|
1561
|
+
manifestPath: `${forgeDir}/manifest.json`,
|
|
1562
|
+
task,
|
|
1563
|
+
fitnessCmd: fitness
|
|
1564
|
+
};
|
|
1565
|
+
} finally {
|
|
1566
|
+
ctx.setActiveAbort(null);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
export {
|
|
1571
|
+
sessionResultStore,
|
|
1572
|
+
createScoreboard,
|
|
1573
|
+
scoreboardFinishEngine,
|
|
1574
|
+
scoreboardFailEngine,
|
|
1575
|
+
renderScoreboard,
|
|
1576
|
+
buildCheckpoint,
|
|
1577
|
+
recordCheckpoint,
|
|
1578
|
+
recordRun,
|
|
1579
|
+
formatRunSummary,
|
|
1580
|
+
extractFitnessCommandFromCesarOutput,
|
|
1581
|
+
repairFitnessCommandTaskLiterals,
|
|
1582
|
+
normalizeGithubRemoteLiteral,
|
|
1583
|
+
repairFitnessCommandRepositoryLiteral,
|
|
1584
|
+
taskWantsRepositoryLinkCheck,
|
|
1585
|
+
repairOverbroadForbiddenLiterals,
|
|
1586
|
+
repairContradictoryFitnessLiterals,
|
|
1587
|
+
validateFitnessCommandIntent,
|
|
1588
|
+
repairForgeTaskRepositoryLiteral,
|
|
1589
|
+
prepareForgeFitnessCommand,
|
|
1590
|
+
inferProjectFitnessCommand,
|
|
1591
|
+
handleForge
|
|
1592
|
+
};
|
|
1593
|
+
//# sourceMappingURL=chunk-YNZ4UHZI.js.map
|