karajan-code 1.21.0 → 1.21.2
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/package.json +1 -1
- package/src/commands/architect.js +39 -42
- package/src/commands/discover.js +45 -41
- package/src/config.js +1 -0
- package/src/mcp/server-handlers.js +69 -0
- package/src/orchestrator/iteration-stages.js +32 -5
- package/src/orchestrator/post-loop-stages.js +14 -2
- package/src/orchestrator/pre-loop-stages.js +15 -0
- package/src/orchestrator/reviewer-fallback.js +7 -1
- package/src/orchestrator/solomon-escalation.js +10 -1
- package/src/session-store.js +18 -0
- package/src/utils/run-log.js +4 -2
package/package.json
CHANGED
|
@@ -3,56 +3,53 @@ import { assertAgentsAvailable } from "../agents/availability.js";
|
|
|
3
3
|
import { resolveRole } from "../config.js";
|
|
4
4
|
import { buildArchitectPrompt, parseArchitectOutput } from "../prompts/architect.js";
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
function formatLayers(layers, lines) {
|
|
7
|
+
lines.push("### Layers");
|
|
8
|
+
for (const l of layers) {
|
|
9
|
+
lines.push(typeof l === "string" ? `- ${l}` : `- **${l.name}**: ${l.responsibility || ""}`);
|
|
10
|
+
}
|
|
11
|
+
lines.push("");
|
|
12
|
+
}
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
function formatTradeoffs(tradeoffs, lines) {
|
|
15
|
+
lines.push("### Tradeoffs");
|
|
16
|
+
for (const t of tradeoffs) {
|
|
17
|
+
lines.push(`- **${t.decision}**: ${t.rationale || ""}`);
|
|
18
|
+
if (t.alternatives?.length) lines.push(` Alternatives: ${t.alternatives.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push("");
|
|
21
|
+
}
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
lines.push("");
|
|
25
|
-
}
|
|
23
|
+
function formatApiContracts(contracts, lines) {
|
|
24
|
+
lines.push("### API Contracts");
|
|
25
|
+
for (const c of contracts) {
|
|
26
|
+
lines.push(`- \`${c.method || "GET"} ${c.endpoint}\``);
|
|
27
|
+
}
|
|
28
|
+
lines.push("");
|
|
29
|
+
}
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
function formatArchitecture(arch, lines) {
|
|
32
|
+
if (arch.type) lines.push(`**Type:** ${arch.type}`, "");
|
|
33
|
+
if (arch.layers?.length) formatLayers(arch.layers, lines);
|
|
34
|
+
if (arch.patterns?.length) {
|
|
35
|
+
lines.push("### Patterns");
|
|
36
|
+
for (const p of arch.patterns) lines.push(`- ${p}`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
}
|
|
39
|
+
if (arch.tradeoffs?.length) formatTradeoffs(arch.tradeoffs, lines);
|
|
40
|
+
if (arch.apiContracts?.length) formatApiContracts(arch.apiContracts, lines);
|
|
41
|
+
}
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (t.alternatives?.length) lines.push(` Alternatives: ${t.alternatives.join(", ")}`);
|
|
38
|
-
}
|
|
39
|
-
lines.push("");
|
|
40
|
-
}
|
|
43
|
+
function formatArchitect(result) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
lines.push(`## Architecture Design`);
|
|
46
|
+
lines.push(`**Verdict:** ${result.verdict || "unknown"}`, "");
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
lines.push("### API Contracts");
|
|
44
|
-
for (const c of arch.apiContracts) {
|
|
45
|
-
lines.push(`- \`${c.method || "GET"} ${c.endpoint}\``);
|
|
46
|
-
}
|
|
47
|
-
lines.push("");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
48
|
+
if (result.architecture) formatArchitecture(result.architecture, lines);
|
|
50
49
|
|
|
51
50
|
if (result.questions?.length) {
|
|
52
51
|
lines.push("### Clarification Questions");
|
|
53
|
-
for (const q of result.questions) {
|
|
54
|
-
lines.push(`- ${q.question || q}`);
|
|
55
|
-
}
|
|
52
|
+
for (const q of result.questions) lines.push(`- ${q.question || q}`);
|
|
56
53
|
lines.push("");
|
|
57
54
|
}
|
|
58
55
|
|
package/src/commands/discover.js
CHANGED
|
@@ -2,57 +2,61 @@ import { createAgent } from "../agents/index.js";
|
|
|
2
2
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
3
3
|
import { resolveRole } from "../config.js";
|
|
4
4
|
import { buildDiscoverPrompt, parseDiscoverOutput } from "../prompts/discover.js";
|
|
5
|
-
import { parseMaybeJsonString } from "../review/parser.js";
|
|
6
5
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
lines.push("## Gaps");
|
|
14
|
-
for (const g of result.gaps) {
|
|
15
|
-
const sev = g.severity ? ` [${g.severity}]` : "";
|
|
16
|
-
lines.push(`- ${g.description || g}${sev}`);
|
|
17
|
-
if (g.suggestedQuestion) lines.push(` → ${g.suggestedQuestion}`);
|
|
18
|
-
}
|
|
19
|
-
lines.push("");
|
|
6
|
+
function formatGaps(gaps, lines) {
|
|
7
|
+
lines.push("## Gaps");
|
|
8
|
+
for (const g of gaps) {
|
|
9
|
+
const sev = g.severity ? ` [${g.severity}]` : "";
|
|
10
|
+
lines.push(`- ${g.description || g}${sev}`);
|
|
11
|
+
if (g.suggestedQuestion) lines.push(` → ${g.suggestedQuestion}`);
|
|
20
12
|
}
|
|
13
|
+
lines.push("");
|
|
14
|
+
}
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
lines.push("");
|
|
16
|
+
function formatMomTest(questions, lines) {
|
|
17
|
+
lines.push("## Mom Test Questions");
|
|
18
|
+
for (const q of questions) {
|
|
19
|
+
lines.push(`- ${q.question || q}`);
|
|
20
|
+
if (q.rationale) lines.push(` _${q.rationale}_`);
|
|
29
21
|
}
|
|
22
|
+
lines.push("");
|
|
23
|
+
}
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
lines.push("");
|
|
25
|
+
function formatWendel(checklist, lines) {
|
|
26
|
+
lines.push("## Wendel Checklist");
|
|
27
|
+
for (const w of checklist) {
|
|
28
|
+
const icon = w.status === "pass" ? "✓" : w.status === "fail" ? "✗" : "?";
|
|
29
|
+
lines.push(`- [${icon}] ${w.condition}: ${w.justification || ""}`);
|
|
38
30
|
}
|
|
31
|
+
lines.push("");
|
|
32
|
+
}
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
function formatClassification(classification, lines) {
|
|
35
|
+
lines.push("## Classification");
|
|
36
|
+
lines.push(`- Type: ${classification.type}`);
|
|
37
|
+
if (classification.adoptionRisk) lines.push(`- Adoption risk: ${classification.adoptionRisk}`);
|
|
38
|
+
if (classification.frictionEstimate) lines.push(`- Friction: ${classification.frictionEstimate}`);
|
|
39
|
+
lines.push("");
|
|
40
|
+
}
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
lines.push("");
|
|
42
|
+
function formatJtbds(jtbds, lines) {
|
|
43
|
+
lines.push("## Jobs-to-be-Done");
|
|
44
|
+
for (const j of jtbds) {
|
|
45
|
+
lines.push(`- **${j.id || ""}**: ${j.functional || j}`);
|
|
54
46
|
}
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatDiscover(result, mode) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
lines.push(`## Discovery (${mode})`);
|
|
53
|
+
lines.push(`**Verdict:** ${result.verdict || "unknown"}`, "");
|
|
55
54
|
|
|
55
|
+
if (result.gaps?.length) formatGaps(result.gaps, lines);
|
|
56
|
+
if (result.momTestQuestions?.length) formatMomTest(result.momTestQuestions, lines);
|
|
57
|
+
if (result.wendelChecklist?.length) formatWendel(result.wendelChecklist, lines);
|
|
58
|
+
if (result.classification) formatClassification(result.classification, lines);
|
|
59
|
+
if (result.jtbds?.length) formatJtbds(result.jtbds, lines);
|
|
56
60
|
if (result.summary) lines.push(`---\n${result.summary}`);
|
|
57
61
|
return lines.join("\n");
|
|
58
62
|
}
|
package/src/config.js
CHANGED
|
@@ -171,6 +171,69 @@ export function buildAskQuestion(server) {
|
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
const MAX_AUTO_RESUMES = 2;
|
|
175
|
+
const NON_RECOVERABLE_CATEGORIES = new Set([
|
|
176
|
+
"config_error", "auth_error", "agent_missing", "branch_error", "git_error"
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
async function attemptAutoResume({ err, config, logger, emitter, askQuestion, runLog }) {
|
|
180
|
+
const { category } = classifyError(err);
|
|
181
|
+
if (NON_RECOVERABLE_CATEGORIES.has(category)) return null;
|
|
182
|
+
|
|
183
|
+
// Find session ID from most recent session file
|
|
184
|
+
const { loadMostRecentSession } = await import("../session-store.js");
|
|
185
|
+
let session;
|
|
186
|
+
try {
|
|
187
|
+
session = await loadMostRecentSession();
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (!session || !["failed", "stopped"].includes(session.status)) return null;
|
|
192
|
+
|
|
193
|
+
const maxRetries = config.session?.max_auto_resumes ?? MAX_AUTO_RESUMES;
|
|
194
|
+
const autoResumeCount = session.auto_resume_count || 0;
|
|
195
|
+
if (autoResumeCount >= maxRetries) {
|
|
196
|
+
runLog.logText(`[resilient] auto-resume limit reached (${maxRetries}), giving up`);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
runLog.logText(`[resilient] run failed (${category}), auto-resuming (${autoResumeCount + 1}/${maxRetries})...`);
|
|
201
|
+
emitter.emit("progress", {
|
|
202
|
+
type: "resilient:auto_resume",
|
|
203
|
+
attempt: autoResumeCount + 1,
|
|
204
|
+
maxRetries,
|
|
205
|
+
errorCategory: category,
|
|
206
|
+
sessionId: session.id
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Increment counter and save before resuming
|
|
210
|
+
const { saveSession } = await import("../session-store.js");
|
|
211
|
+
session.auto_resume_count = autoResumeCount + 1;
|
|
212
|
+
await saveSession(session);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await resumeFlow({
|
|
216
|
+
sessionId: session.id,
|
|
217
|
+
config,
|
|
218
|
+
logger,
|
|
219
|
+
flags: {},
|
|
220
|
+
emitter,
|
|
221
|
+
askQuestion
|
|
222
|
+
});
|
|
223
|
+
const ok = !result.paused && (result.approved !== false);
|
|
224
|
+
runLog.logText(`[resilient] auto-resume ${ok ? "succeeded" : "finished"} — ok=${ok}`);
|
|
225
|
+
return { ok, ...result, autoResumed: true, autoResumeAttempt: autoResumeCount + 1 };
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Recursive: try again if still within limits
|
|
228
|
+
const nestedResult = await attemptAutoResume({
|
|
229
|
+
err: error, config, logger, emitter, askQuestion, runLog
|
|
230
|
+
});
|
|
231
|
+
if (nestedResult) return nestedResult;
|
|
232
|
+
runLog.logText(`[resilient] auto-resume failed: ${error.message}`);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
174
237
|
export async function handleRunDirect(a, server, extra) {
|
|
175
238
|
const config = await buildConfig(a);
|
|
176
239
|
await assertNotOnBaseBranch(config);
|
|
@@ -209,6 +272,12 @@ export async function handleRunDirect(a, server, extra) {
|
|
|
209
272
|
const result = await runFlow({ task: a.task, config, logger, flags: a, emitter, askQuestion, pgTaskId, pgProject });
|
|
210
273
|
runLog.logText(`[kj_run] finished — ok=${!result.paused && (result.approved !== false)}`);
|
|
211
274
|
return { ok: !result.paused && (result.approved !== false), ...result };
|
|
275
|
+
} catch (err) {
|
|
276
|
+
const autoResumeResult = await attemptAutoResume({
|
|
277
|
+
err, config, logger, emitter, askQuestion, runLog, progressNotifier, extra
|
|
278
|
+
});
|
|
279
|
+
if (autoResumeResult) return autoResumeResult;
|
|
280
|
+
throw err;
|
|
212
281
|
} finally {
|
|
213
282
|
runLog.close();
|
|
214
283
|
}
|
|
@@ -256,8 +256,14 @@ async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, s
|
|
|
256
256
|
|
|
257
257
|
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion }) {
|
|
258
258
|
logger.setContext({ iteration, stage: "tdd" });
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
let tddDiff, untrackedFiles;
|
|
260
|
+
try {
|
|
261
|
+
tddDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
262
|
+
untrackedFiles = await getUntrackedFiles();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
logger.warn(`TDD diff generation failed: ${err.message}`);
|
|
265
|
+
return { action: "continue", stageResult: { ok: false, summary: `TDD check failed: ${err.message}` } };
|
|
266
|
+
}
|
|
261
267
|
const tddEval = evaluateTddPolicy(tddDiff, config.development, untrackedFiles);
|
|
262
268
|
await addCheckpoint(session, {
|
|
263
269
|
stage: "tdd-policy",
|
|
@@ -366,7 +372,13 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
366
372
|
const sonarRole = new SonarRole({ config, logger, emitter });
|
|
367
373
|
await sonarRole.init({ iteration });
|
|
368
374
|
const sonarStart = Date.now();
|
|
369
|
-
|
|
375
|
+
let sonarOutput;
|
|
376
|
+
try {
|
|
377
|
+
sonarOutput = await sonarRole.run();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
logger.warn(`Sonar threw: ${err.message}`);
|
|
380
|
+
sonarOutput = { ok: false, result: { error: err.message }, summary: `Sonar error: ${err.message}` };
|
|
381
|
+
}
|
|
370
382
|
trackBudget({ role: "sonar", provider: "sonar", result: sonarOutput, duration_ms: Date.now() - sonarStart });
|
|
371
383
|
const sonarResult = sonarOutput.result;
|
|
372
384
|
|
|
@@ -438,7 +450,13 @@ export async function runSonarCloudStage({ config, logger, emitter, eventBase, s
|
|
|
438
450
|
|
|
439
451
|
const { runSonarCloudScan } = await import("../sonar/cloud-scanner.js");
|
|
440
452
|
const scanStart = Date.now();
|
|
441
|
-
|
|
453
|
+
let result;
|
|
454
|
+
try {
|
|
455
|
+
result = await runSonarCloudScan(config);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
logger.warn(`SonarCloud threw: ${err.message}`);
|
|
458
|
+
result = { ok: false, error: err.message };
|
|
459
|
+
}
|
|
442
460
|
trackBudget({ role: "sonarcloud", provider: "sonarcloud", result: { ok: result.ok }, duration_ms: Date.now() - scanStart });
|
|
443
461
|
|
|
444
462
|
await addCheckpoint(session, {
|
|
@@ -550,7 +568,13 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
550
568
|
})
|
|
551
569
|
);
|
|
552
570
|
|
|
553
|
-
|
|
571
|
+
let diff;
|
|
572
|
+
try {
|
|
573
|
+
diff = await fetchReviewDiff(session, logger);
|
|
574
|
+
} catch (err) {
|
|
575
|
+
logger.warn(`Review diff generation failed: ${err.message}`);
|
|
576
|
+
return { approved: false, blocking_issues: [{ description: `Diff generation failed: ${err.message}` }], non_blocking_suggestions: [], summary: `Reviewer failed: cannot generate diff — ${err.message}`, confidence: 0 };
|
|
577
|
+
}
|
|
554
578
|
const reviewerOnOutput = ({ stream, line }) => {
|
|
555
579
|
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "reviewer" }, {
|
|
556
580
|
message: line,
|
|
@@ -575,6 +599,9 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
575
599
|
trackBudget({ role: "reviewer", provider: reviewer, model: reviewerRole.model, result, duration_ms: Date.now() - reviewerStart });
|
|
576
600
|
}
|
|
577
601
|
});
|
|
602
|
+
} catch (err) {
|
|
603
|
+
logger.warn(`Reviewer threw: ${err.message}`);
|
|
604
|
+
reviewerExec = { execResult: { ok: false, error: err.message }, attempts: [{ reviewer: reviewerRole.provider, result: { ok: false, error: err.message } }] };
|
|
578
605
|
} finally {
|
|
579
606
|
reviewerStall.stop();
|
|
580
607
|
}
|
|
@@ -16,7 +16,13 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
16
16
|
const tester = new TesterRole({ config, logger, emitter });
|
|
17
17
|
await tester.init({ task, iteration });
|
|
18
18
|
const testerStart = Date.now();
|
|
19
|
-
|
|
19
|
+
let testerOutput;
|
|
20
|
+
try {
|
|
21
|
+
testerOutput = await tester.run({ task, diff });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
logger.warn(`Tester threw: ${err.message}`);
|
|
24
|
+
testerOutput = { ok: false, summary: `Tester error: ${err.message}`, result: { error: err.message } };
|
|
25
|
+
}
|
|
20
26
|
trackBudget({
|
|
21
27
|
role: "tester",
|
|
22
28
|
provider: config?.roles?.tester?.provider || coderRole.provider,
|
|
@@ -90,7 +96,13 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
90
96
|
const security = new SecurityRole({ config, logger, emitter });
|
|
91
97
|
await security.init({ task, iteration });
|
|
92
98
|
const securityStart = Date.now();
|
|
93
|
-
|
|
99
|
+
let securityOutput;
|
|
100
|
+
try {
|
|
101
|
+
securityOutput = await security.run({ task, diff });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logger.warn(`Security threw: ${err.message}`);
|
|
104
|
+
securityOutput = { ok: false, summary: `Security error: ${err.message}`, result: { error: err.message } };
|
|
105
|
+
}
|
|
94
106
|
trackBudget({
|
|
95
107
|
role: "security",
|
|
96
108
|
provider: config?.roles?.security?.provider || coderRole.provider,
|
|
@@ -69,6 +69,9 @@ export async function runTriageStage({ config, logger, emitter, eventBase, sessi
|
|
|
69
69
|
let triageOutput;
|
|
70
70
|
try {
|
|
71
71
|
triageOutput = await triage.run({ task: session.task, onOutput: triageStall.onOutput });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.warn(`Triage threw: ${err.message}`);
|
|
74
|
+
triageOutput = { ok: false, summary: `Triage error: ${err.message}`, result: { error: err.message } };
|
|
72
75
|
} finally {
|
|
73
76
|
triageStall.stop();
|
|
74
77
|
}
|
|
@@ -159,6 +162,9 @@ export async function runResearcherStage({ config, logger, emitter, eventBase, s
|
|
|
159
162
|
let researchOutput;
|
|
160
163
|
try {
|
|
161
164
|
researchOutput = await researcher.run({ task: session.task, onOutput: researcherStall.onOutput });
|
|
165
|
+
} catch (err) {
|
|
166
|
+
logger.warn(`Researcher threw: ${err.message}`);
|
|
167
|
+
researchOutput = { ok: false, summary: `Researcher error: ${err.message}`, result: { error: err.message } };
|
|
162
168
|
} finally {
|
|
163
169
|
researcherStall.stop();
|
|
164
170
|
}
|
|
@@ -281,6 +287,9 @@ export async function runArchitectStage({ config, logger, emitter, eventBase, se
|
|
|
281
287
|
discoverResult,
|
|
282
288
|
triageLevel
|
|
283
289
|
});
|
|
290
|
+
} catch (err) {
|
|
291
|
+
logger.warn(`Architect threw: ${err.message}`);
|
|
292
|
+
architectOutput = { ok: false, summary: `Architect error: ${err.message}`, result: { error: err.message } };
|
|
284
293
|
} finally {
|
|
285
294
|
architectStall.stop();
|
|
286
295
|
}
|
|
@@ -380,6 +389,9 @@ export async function runPlannerStage({ config, logger, emitter, eventBase, sess
|
|
|
380
389
|
let planResult;
|
|
381
390
|
try {
|
|
382
391
|
planResult = await planRole.execute({ task, onOutput: plannerStall.onOutput });
|
|
392
|
+
} catch (err) {
|
|
393
|
+
logger.warn(`Planner threw: ${err.message}`);
|
|
394
|
+
planResult = { ok: false, result: { error: err.message }, summary: `Planner error: ${err.message}` };
|
|
383
395
|
} finally {
|
|
384
396
|
plannerStall.stop();
|
|
385
397
|
}
|
|
@@ -453,6 +465,9 @@ export async function runDiscoverStage({ config, logger, emitter, eventBase, ses
|
|
|
453
465
|
let discoverOutput;
|
|
454
466
|
try {
|
|
455
467
|
discoverOutput = await discover.run({ task: session.task, mode, onOutput: discoverStall.onOutput });
|
|
468
|
+
} catch (err) {
|
|
469
|
+
logger.warn(`Discover threw: ${err.message}`);
|
|
470
|
+
discoverOutput = { ok: false, summary: `Discover error: ${err.message}`, result: { error: err.message } };
|
|
456
471
|
} finally {
|
|
457
472
|
discoverStall.stop();
|
|
458
473
|
}
|
|
@@ -16,7 +16,13 @@ export async function runReviewerWithFallback({ reviewerName, config, logger, em
|
|
|
16
16
|
const role = new ReviewerRole({ config: reviewerConfig, logger, emitter, createAgentFn: createAgent });
|
|
17
17
|
await role.init();
|
|
18
18
|
for (let attempt = 1; attempt <= retries + 1; attempt += 1) {
|
|
19
|
-
|
|
19
|
+
let execResult;
|
|
20
|
+
try {
|
|
21
|
+
execResult = await role.execute(reviewInput);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
logger.warn(`Reviewer ${name} attempt ${attempt} threw: ${err.message}`);
|
|
24
|
+
execResult = { ok: false, result: { error: err.message }, summary: `Reviewer error: ${err.message}` };
|
|
25
|
+
}
|
|
20
26
|
if (onAttemptResult) {
|
|
21
27
|
await onAttemptResult({ reviewer: name, result: execResult.result });
|
|
22
28
|
}
|
|
@@ -19,7 +19,16 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
19
19
|
|
|
20
20
|
const solomon = new SolomonRole({ config, logger, emitter });
|
|
21
21
|
await solomon.init({ task: conflict.task || session.task, iteration });
|
|
22
|
-
|
|
22
|
+
let ruling;
|
|
23
|
+
try {
|
|
24
|
+
ruling = await solomon.run({ conflict });
|
|
25
|
+
} catch (err) {
|
|
26
|
+
logger.warn(`Solomon threw: ${err.message}`);
|
|
27
|
+
return escalateToHuman({
|
|
28
|
+
askQuestion, session, emitter, eventBase, stage, iteration,
|
|
29
|
+
conflict: { ...conflict, solomonReason: `Solomon error: ${err.message}` }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
emitProgress(
|
|
25
34
|
emitter,
|
package/src/session-store.js
CHANGED
|
@@ -63,6 +63,24 @@ export async function pauseSession(session, { question, context: pauseContext })
|
|
|
63
63
|
await saveSession(session);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export async function loadMostRecentSession() {
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = await fs.readdir(SESSION_ROOT, { withFileTypes: true });
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
|
|
74
|
+
for (let i = dirs.length - 1; i >= 0; i--) {
|
|
75
|
+
try {
|
|
76
|
+
return await loadSession(dirs[i]);
|
|
77
|
+
} catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
export async function resumeSessionWithAnswer(sessionId, answer) {
|
|
67
85
|
const session = await loadSession(sessionId);
|
|
68
86
|
if (session.status !== "paused") {
|
package/src/utils/run-log.js
CHANGED
|
@@ -183,13 +183,15 @@ export function readRunLog(projectDir, maxLines = 50) {
|
|
|
183
183
|
const total = lines.length;
|
|
184
184
|
const shown = lines.slice(-maxLines);
|
|
185
185
|
const status = parseRunStatus(lines);
|
|
186
|
+
const MAX_LINE_CHARS = 2000;
|
|
187
|
+
const truncated = shown.map(l => l.length > MAX_LINE_CHARS ? l.slice(0, MAX_LINE_CHARS) + "… [truncated]" : l);
|
|
186
188
|
return {
|
|
187
189
|
ok: true,
|
|
188
190
|
path: logPath,
|
|
189
191
|
totalLines: total,
|
|
190
192
|
status,
|
|
191
|
-
lines:
|
|
192
|
-
summary:
|
|
193
|
+
lines: truncated,
|
|
194
|
+
summary: truncated.join("\n")
|
|
193
195
|
};
|
|
194
196
|
} catch (err) {
|
|
195
197
|
return {
|