karajan-code 1.35.0 → 1.36.1
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/README.md +136 -19
- package/bin/kj-tail +294 -41
- package/docs/README.es.md +125 -22
- package/package.json +2 -1
- package/src/agents/aider-agent.js +16 -9
- package/src/agents/base-agent.js +15 -0
- package/src/agents/claude-agent.js +51 -6
- package/src/agents/codex-agent.js +35 -13
- package/src/agents/gemini-agent.js +17 -9
- package/src/agents/model-registry.js +8 -7
- package/src/agents/opencode-agent.js +17 -10
- package/src/commands/doctor.js +98 -0
- package/src/orchestrator/solomon-escalation.js +11 -2
- package/src/roles/solomon-role.js +17 -1
- package/src/utils/budget.js +12 -8
- package/src/utils/model-selector.js +3 -3
- package/src/utils/stall-detector.js +5 -5
- package/templates/kj.config.yml +3 -0
|
@@ -30,10 +30,17 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const solomonError = ruling.result?.error;
|
|
34
|
+
if (!ruling.ok && solomonError) {
|
|
35
|
+
logger.warn(`Solomon execution failed: ${solomonError}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
emitProgress(
|
|
34
39
|
emitter,
|
|
35
40
|
makeEvent("solomon:end", { ...eventBase, stage: "solomon" }, {
|
|
36
|
-
message:
|
|
41
|
+
message: ruling.ok
|
|
42
|
+
? `Solomon ruling: ${ruling.result?.ruling || "unknown"}`
|
|
43
|
+
: `Solomon failed: ${(solomonError || ruling.summary || "unknown error").slice(0, 200)}`,
|
|
37
44
|
detail: ruling.result
|
|
38
45
|
})
|
|
39
46
|
);
|
|
@@ -43,13 +50,15 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
43
50
|
iteration,
|
|
44
51
|
ruling: ruling.result?.ruling,
|
|
45
52
|
escalate: ruling.result?.escalate,
|
|
53
|
+
error: solomonError ? solomonError.slice(0, 500) : undefined,
|
|
46
54
|
subtask: ruling.result?.subtask?.title || null
|
|
47
55
|
});
|
|
48
56
|
|
|
49
57
|
if (!ruling.ok) {
|
|
58
|
+
const reason = ruling.result?.escalate_reason || solomonError || ruling.summary;
|
|
50
59
|
return escalateToHuman({
|
|
51
60
|
askQuestion, session, emitter, eventBase, stage, iteration,
|
|
52
|
-
conflict: { ...conflict, solomonReason:
|
|
61
|
+
conflict: { ...conflict, solomonReason: reason }
|
|
53
62
|
});
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -62,12 +62,28 @@ function buildPrompt({ conflict, task, instructions }) {
|
|
|
62
62
|
const iterationCount = conflict?.iterationCount ?? "?";
|
|
63
63
|
const maxIterations = conflict?.maxIterations ?? "?";
|
|
64
64
|
|
|
65
|
+
const isFirstRejection = conflict?.isFirstRejection ?? false;
|
|
66
|
+
const isRepeat = conflict?.isRepeat ?? false;
|
|
67
|
+
|
|
65
68
|
sections.push(
|
|
66
69
|
`## Conflict context`,
|
|
67
70
|
`Stage: ${stage}`,
|
|
68
|
-
`Iterations exhausted: ${iterationCount}/${maxIterations}
|
|
71
|
+
`Iterations exhausted: ${iterationCount}/${maxIterations}`,
|
|
72
|
+
`isFirstRejection: ${isFirstRejection}`,
|
|
73
|
+
`isRepeat: ${isRepeat}`
|
|
69
74
|
);
|
|
70
75
|
|
|
76
|
+
if (conflict?.issueCategories) {
|
|
77
|
+
sections.push(`## Issue categories\n${JSON.stringify(conflict.issueCategories, null, 2)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (conflict?.blockingIssues?.length) {
|
|
81
|
+
const issueList = conflict.blockingIssues
|
|
82
|
+
.map((issue, i) => `${i + 1}. [${issue.severity || "unknown"}] ${issue.description || issue}`)
|
|
83
|
+
.join("\n");
|
|
84
|
+
sections.push(`## Blocking issues\n${issueList}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
if (task) {
|
|
72
88
|
sections.push(`## Original task\n${task}`);
|
|
73
89
|
}
|
package/src/utils/budget.js
CHANGED
|
@@ -40,18 +40,22 @@ export function extractUsageMetrics(result, defaultModel = null) {
|
|
|
40
40
|
null;
|
|
41
41
|
|
|
42
42
|
// If no real token data AND no explicit cost, estimate from prompt/output sizes.
|
|
43
|
-
//
|
|
43
|
+
// Primary: uses result.promptSize when explicitly provided.
|
|
44
|
+
// Fallback: estimates from result.output or result.error text length.
|
|
44
45
|
let estimated = false;
|
|
45
46
|
let finalTokensIn = tokens_in;
|
|
46
47
|
let finalTokensOut = tokens_out;
|
|
47
48
|
const hasExplicitCost = cost_usd !== undefined && cost_usd !== null && cost_usd !== "";
|
|
48
|
-
if (!tokens_in && !tokens_out && !hasExplicitCost
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
if (!tokens_in && !tokens_out && !hasExplicitCost) {
|
|
50
|
+
const outputText = result?.output || result?.error || result?.summary || "";
|
|
51
|
+
const promptSize = result?.promptSize || 0;
|
|
52
|
+
const MIN_TEXT_FOR_ESTIMATION = 40;
|
|
53
|
+
if (promptSize > 0 || outputText.length >= MIN_TEXT_FOR_ESTIMATION) {
|
|
54
|
+
const est = estimateTokens(promptSize, outputText.length);
|
|
55
|
+
finalTokensIn = est.tokens_in;
|
|
56
|
+
finalTokensOut = est.tokens_out;
|
|
57
|
+
estimated = true;
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
return { tokens_in: finalTokensIn, tokens_out: finalTokensOut, cost_usd, model, estimated };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const DEFAULT_MODEL_TIERS = {
|
|
2
|
-
claude: { trivial: "
|
|
3
|
-
codex: { trivial: "
|
|
4
|
-
gemini: { trivial: "gemini
|
|
2
|
+
claude: { trivial: "haiku", simple: "haiku", medium: "sonnet", complex: "opus" },
|
|
3
|
+
codex: { trivial: "o4-mini", simple: "o4-mini", medium: "o4-mini", complex: "o3" },
|
|
4
|
+
gemini: { trivial: "gemini-2.0-flash", simple: "gemini-2.0-flash", medium: "gemini-2.5-pro", complex: "gemini-2.5-pro" },
|
|
5
5
|
aider: { trivial: null, simple: null, medium: null, complex: null }
|
|
6
6
|
};
|
|
7
7
|
|
|
@@ -51,7 +51,7 @@ export function createStallDetector({
|
|
|
51
51
|
lastCriticalWarnAt = now;
|
|
52
52
|
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
53
53
|
status: "critical",
|
|
54
|
-
message: `Agent ${provider} unresponsive for ${Math.round(silenceMs / 1000)}s — may be hung`,
|
|
54
|
+
message: `[${stage}] Agent ${provider} unresponsive for ${Math.round(silenceMs / 1000)}s — may be hung`,
|
|
55
55
|
detail: {
|
|
56
56
|
provider,
|
|
57
57
|
silenceMs,
|
|
@@ -65,7 +65,7 @@ export function createStallDetector({
|
|
|
65
65
|
lastStallWarnAt = now;
|
|
66
66
|
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
67
67
|
status: "warning",
|
|
68
|
-
message: `Agent ${provider} silent for ${Math.round(silenceMs / 1000)}s — still waiting`,
|
|
68
|
+
message: `[${stage}] Agent ${provider} silent for ${Math.round(silenceMs / 1000)}s — still waiting`,
|
|
69
69
|
detail: {
|
|
70
70
|
provider,
|
|
71
71
|
silenceMs,
|
|
@@ -79,8 +79,8 @@ export function createStallDetector({
|
|
|
79
79
|
|
|
80
80
|
emitProgress(emitter, makeEvent("agent:heartbeat", { ...eventBase, stage }, {
|
|
81
81
|
message: silenceMs < stallTimeoutMs
|
|
82
|
-
? `Agent ${provider} active — ${lineCount} lines, ${Math.round(elapsedMs / 1000)}s elapsed`
|
|
83
|
-
: `Agent ${provider} waiting — silent ${Math.round(silenceMs / 1000)}s, ${Math.round(elapsedMs / 1000)}s elapsed`,
|
|
82
|
+
? `[${stage}] Agent ${provider} active — ${lineCount} lines, ${Math.round(elapsedMs / 1000)}s elapsed`
|
|
83
|
+
: `[${stage}] Agent ${provider} waiting — silent ${Math.round(silenceMs / 1000)}s, ${Math.round(elapsedMs / 1000)}s elapsed`,
|
|
84
84
|
detail: {
|
|
85
85
|
provider,
|
|
86
86
|
elapsedMs,
|
|
@@ -96,7 +96,7 @@ export function createStallDetector({
|
|
|
96
96
|
maxSilenceTriggered = true;
|
|
97
97
|
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
98
98
|
status: "fail",
|
|
99
|
-
message: `Agent ${provider} exceeded max silence (${Math.round(hardLimit / 1000)}s)`,
|
|
99
|
+
message: `[${stage}] Agent ${provider} exceeded max silence (${Math.round(hardLimit / 1000)}s)`,
|
|
100
100
|
detail: {
|
|
101
101
|
provider,
|
|
102
102
|
silenceMs,
|
package/templates/kj.config.yml
CHANGED
|
@@ -34,6 +34,9 @@ development:
|
|
|
34
34
|
- .spec.
|
|
35
35
|
|
|
36
36
|
# SonarQube settings
|
|
37
|
+
# Authentication: set token below, or KJ_SONAR_TOKEN env var,
|
|
38
|
+
# or save admin credentials in ~/.karajan/sonar-credentials.json:
|
|
39
|
+
# {"user": "admin", "password": "your-password"}
|
|
37
40
|
sonarqube:
|
|
38
41
|
enabled: true
|
|
39
42
|
host: http://localhost:9000
|