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.
@@ -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: `Solomon ruling: ${ruling.result?.ruling || "unknown"}`,
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: ruling.result?.escalate_reason }
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
  }
@@ -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
- // Estimation is opt-in: only triggered when result.promptSize is explicitly provided.
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 && result?.promptSize > 0) {
49
- const promptSize = result.promptSize;
50
- const outputSize = (result?.output || result?.summary || "").length;
51
- const est = estimateTokens(promptSize, outputSize);
52
- finalTokensIn = est.tokens_in;
53
- finalTokensOut = est.tokens_out;
54
- estimated = true;
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: "claude/haiku", simple: "claude/haiku", medium: "claude/sonnet", complex: "claude/opus" },
3
- codex: { trivial: "codex/o4-mini", simple: "codex/o4-mini", medium: "codex/o4-mini", complex: "codex/o3" },
4
- gemini: { trivial: "gemini/flash", simple: "gemini/flash", medium: "gemini/pro", complex: "gemini/pro" },
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,
@@ -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