karajan-code 1.9.4 → 1.9.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.9.4",
3
+ "version": "1.9.6",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -70,6 +70,37 @@ function createStreamJsonFilter(onOutput) {
70
70
  };
71
71
  }
72
72
 
73
+ /**
74
+ * Build clean execa options for Claude subprocess.
75
+ *
76
+ * Three critical fixes for running `claude -p` from Node.js:
77
+ *
78
+ * 1. Strip CLAUDECODE env var — Claude Code 2.x sets this to block nested
79
+ * sessions. The spawned `claude -p` is a separate non-interactive
80
+ * invocation, not a true nested session.
81
+ *
82
+ * 2. Detach stdin (stdin: "ignore") — When launched from Node.js (which is
83
+ * how Claude Code / Karajan MCP runs), the child inherits the parent's
84
+ * stdin. `claude -p` then blocks waiting to read from a stdin that the
85
+ * parent is already consuming. Ignoring stdin prevents the hang.
86
+ *
87
+ * 3. Claude Code 2.x writes all structured output (stream-json, json) to
88
+ * stderr, NOT stdout. The agent must read from stderr for the actual
89
+ * response data.
90
+ */
91
+ function cleanExecaOpts(extra = {}) {
92
+ const { CLAUDECODE, ...env } = process.env;
93
+ return { env, stdin: "ignore", ...extra };
94
+ }
95
+
96
+ /**
97
+ * Pick the best raw output from a claude subprocess result.
98
+ * Claude 2.x sends structured output to stderr; stdout is often empty.
99
+ */
100
+ function pickOutput(res) {
101
+ return res.stdout || res.stderr || "";
102
+ }
103
+
73
104
  export class ClaudeAgent extends BaseAgent {
74
105
  async runTask(task) {
75
106
  const role = task.role || "coder";
@@ -81,28 +112,34 @@ export class ClaudeAgent extends BaseAgent {
81
112
  if (task.onOutput) {
82
113
  args.push("--output-format", "stream-json");
83
114
  const streamFilter = createStreamJsonFilter(task.onOutput);
84
- const res = await runCommand(resolveBin("claude"), args, {
115
+ const res = await runCommand(resolveBin("claude"), args, cleanExecaOpts({
85
116
  onOutput: streamFilter,
86
117
  silenceTimeoutMs: task.silenceTimeoutMs,
87
118
  timeout: task.timeoutMs
88
- });
89
- const output = extractTextFromStreamJson(res.stdout);
90
- return { ok: res.exitCode === 0, output, error: res.stderr, exitCode: res.exitCode };
119
+ }));
120
+ const raw = pickOutput(res);
121
+ const output = extractTextFromStreamJson(raw);
122
+ return { ok: res.exitCode === 0, output, error: res.exitCode !== 0 ? raw : "", exitCode: res.exitCode };
91
123
  }
92
124
 
93
- const res = await runCommand(resolveBin("claude"), args);
94
- return { ok: res.exitCode === 0, output: res.stdout, error: res.stderr, exitCode: res.exitCode };
125
+ // Without streaming, use json output to get structured response via stderr
126
+ args.push("--output-format", "json");
127
+ const res = await runCommand(resolveBin("claude"), args, cleanExecaOpts());
128
+ const raw = pickOutput(res);
129
+ const output = extractTextFromStreamJson(raw);
130
+ return { ok: res.exitCode === 0, output, error: res.exitCode !== 0 ? raw : "", exitCode: res.exitCode };
95
131
  }
96
132
 
97
133
  async reviewTask(task) {
98
- const args = ["-p", task.prompt, "--output-format", "json"];
134
+ const args = ["-p", task.prompt, "--output-format", "stream-json"];
99
135
  const model = this.getRoleModel(task.role || "reviewer");
100
136
  if (model) args.push("--model", model);
101
- const res = await runCommand(resolveBin("claude"), args, {
137
+ const res = await runCommand(resolveBin("claude"), args, cleanExecaOpts({
102
138
  onOutput: task.onOutput,
103
139
  silenceTimeoutMs: task.silenceTimeoutMs,
104
140
  timeout: task.timeoutMs
105
- });
106
- return { ok: res.exitCode === 0, output: res.stdout, error: res.stderr, exitCode: res.exitCode };
141
+ }));
142
+ const raw = pickOutput(res);
143
+ return { ok: res.exitCode === 0, output: raw, error: res.exitCode !== 0 ? raw : "", exitCode: res.exitCode };
107
144
  }
108
145
  }