kernelbot 1.0.16 → 1.0.17
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/coder.js +109 -39
- package/src/tools/os.js +13 -2
package/package.json
CHANGED
package/src/coder.js
CHANGED
|
@@ -9,13 +9,11 @@ function ensureClaudeCodeSetup() {
|
|
|
9
9
|
const claudeJson = join(homedir(), '.claude.json');
|
|
10
10
|
const claudeDir = join(homedir(), '.claude');
|
|
11
11
|
|
|
12
|
-
// Create ~/.claude/ directory if missing
|
|
13
12
|
if (!existsSync(claudeDir)) {
|
|
14
13
|
mkdirSync(claudeDir, { recursive: true });
|
|
15
14
|
logger.info('Created ~/.claude/ directory');
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
// Create ~/.claude.json with onboarding completed if missing
|
|
19
17
|
if (!existsSync(claudeJson)) {
|
|
20
18
|
const defaults = {
|
|
21
19
|
hasCompletedOnboarding: true,
|
|
@@ -27,42 +25,112 @@ function ensureClaudeCodeSetup() {
|
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
function
|
|
28
|
+
function extractText(event) {
|
|
29
|
+
// Try to find text in various possible event structures
|
|
30
|
+
// Format 1: { type: "message", role: "assistant", content: [{ type: "text", text: "..." }] }
|
|
31
|
+
// Format 2: { type: "assistant", message: { content: [{ type: "text", text: "..." }] } }
|
|
32
|
+
// Format 3: { type: "assistant", content: [{ type: "text", text: "..." }] }
|
|
33
|
+
|
|
34
|
+
const contentSources = [
|
|
35
|
+
event.content,
|
|
36
|
+
event.message?.content,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const content of contentSources) {
|
|
40
|
+
if (Array.isArray(content)) {
|
|
41
|
+
const texts = content
|
|
42
|
+
.filter((b) => b.type === 'text' && b.text?.trim())
|
|
43
|
+
.map((b) => b.text.trim());
|
|
44
|
+
if (texts.length > 0) return texts.join('\n');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Direct text field
|
|
49
|
+
if (event.text?.trim()) return event.text.trim();
|
|
50
|
+
if (event.result?.trim()) return event.result.trim();
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractToolUse(event) {
|
|
56
|
+
// Format 1: { type: "tool_use", name: "Bash", input: { command: "..." } }
|
|
57
|
+
// Format 2: content block with type tool_use
|
|
58
|
+
if (event.name && event.input) {
|
|
59
|
+
const input = event.input;
|
|
60
|
+
const summary = input.command || input.file_path || input.pattern || input.query || input.content?.slice(0, 80) || JSON.stringify(input).slice(0, 100);
|
|
61
|
+
return { name: event.name, summary: String(summary).slice(0, 150) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check inside content array
|
|
65
|
+
const content = event.content || event.message?.content;
|
|
66
|
+
if (Array.isArray(content)) {
|
|
67
|
+
for (const block of content) {
|
|
68
|
+
if (block.type === 'tool_use' && block.name) {
|
|
69
|
+
const input = block.input || {};
|
|
70
|
+
const summary = input.command || input.file_path || input.pattern || input.query || JSON.stringify(input).slice(0, 100);
|
|
71
|
+
return { name: block.name, summary: String(summary).slice(0, 150) };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function processEvent(line, onOutput, logger) {
|
|
31
80
|
let event;
|
|
32
81
|
try {
|
|
33
82
|
event = JSON.parse(line);
|
|
34
83
|
} catch {
|
|
35
|
-
|
|
84
|
+
// Not JSON — send raw text if it looks meaningful
|
|
85
|
+
if (line.trim() && line.length > 3 && onOutput) {
|
|
86
|
+
logger.info(`Claude Code (raw): ${line.slice(0, 200)}`);
|
|
87
|
+
onOutput(`📟 ${line.trim()}`).catch(() => {});
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
36
90
|
}
|
|
37
91
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
const type = event.type || '';
|
|
93
|
+
|
|
94
|
+
// Assistant thinking/text
|
|
95
|
+
if (type === 'message' || type === 'assistant') {
|
|
96
|
+
const text = extractText(event);
|
|
97
|
+
if (text) {
|
|
98
|
+
logger.info(`Claude Code: ${text.slice(0, 200)}`);
|
|
99
|
+
if (onOutput) onOutput(`💬 *Claude Code:*\n${text}`).catch(() => {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Also check for tool_use inside the same message
|
|
103
|
+
const tool = extractToolUse(event);
|
|
104
|
+
if (tool) {
|
|
105
|
+
logger.info(`Claude Code tool: ${tool.name}: ${tool.summary}`);
|
|
106
|
+
if (onOutput) onOutput(`🔨 \`${tool.name}: ${tool.summary}\``).catch(() => {});
|
|
47
107
|
}
|
|
108
|
+
return event;
|
|
48
109
|
}
|
|
49
110
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
// Standalone tool use event
|
|
112
|
+
if (type === 'tool_use') {
|
|
113
|
+
const tool = extractToolUse(event);
|
|
114
|
+
if (tool) {
|
|
115
|
+
logger.info(`Claude Code tool: ${tool.name}: ${tool.summary}`);
|
|
116
|
+
if (onOutput) onOutput(`🔨 \`${tool.name}: ${tool.summary}\``).catch(() => {});
|
|
117
|
+
}
|
|
118
|
+
return event;
|
|
57
119
|
}
|
|
58
120
|
|
|
59
|
-
// Result
|
|
60
|
-
if (
|
|
61
|
-
const status = event.status || 'done';
|
|
62
|
-
const duration = event.duration_ms ?
|
|
63
|
-
|
|
64
|
-
|
|
121
|
+
// Result / completion
|
|
122
|
+
if (type === 'result') {
|
|
123
|
+
const status = event.status || event.subtype || 'done';
|
|
124
|
+
const duration = event.duration_ms ? ` in ${(event.duration_ms / 1000).toFixed(1)}s` : '';
|
|
125
|
+
const cost = event.cost_usd ? ` ($${event.cost_usd.toFixed(3)})` : '';
|
|
126
|
+
logger.info(`Claude Code finished: ${status}${duration}${cost}`);
|
|
127
|
+
if (onOutput) onOutput(`✅ Claude Code finished (${status}${duration}${cost})`).catch(() => {});
|
|
128
|
+
return event;
|
|
65
129
|
}
|
|
130
|
+
|
|
131
|
+
// Log any other event type for debugging
|
|
132
|
+
logger.debug(`Claude Code event [${type}]: ${JSON.stringify(event).slice(0, 300)}`);
|
|
133
|
+
return event;
|
|
66
134
|
}
|
|
67
135
|
|
|
68
136
|
export class ClaudeCodeSpawner {
|
|
@@ -76,10 +144,10 @@ export class ClaudeCodeSpawner {
|
|
|
76
144
|
const logger = getLogger();
|
|
77
145
|
const turns = maxTurns || this.maxTurns;
|
|
78
146
|
|
|
79
|
-
// Auto-setup Claude Code if not configured
|
|
80
147
|
ensureClaudeCodeSetup();
|
|
81
148
|
|
|
82
|
-
logger.info(`Spawning Claude Code in ${workingDirectory}`);
|
|
149
|
+
logger.info(`Spawning Claude Code in ${workingDirectory}${this.model ? ` (model: ${this.model})` : ''}`);
|
|
150
|
+
if (onOutput) onOutput(`⏳ Starting Claude Code...`).catch(() => {});
|
|
83
151
|
|
|
84
152
|
return new Promise((resolve, reject) => {
|
|
85
153
|
const args = [
|
|
@@ -106,9 +174,8 @@ export class ClaudeCodeSpawner {
|
|
|
106
174
|
child.stdout.on('data', (data) => {
|
|
107
175
|
buffer += data.toString();
|
|
108
176
|
|
|
109
|
-
// Process complete JSON lines
|
|
110
177
|
const lines = buffer.split('\n');
|
|
111
|
-
buffer = lines.pop();
|
|
178
|
+
buffer = lines.pop();
|
|
112
179
|
|
|
113
180
|
for (const line of lines) {
|
|
114
181
|
const trimmed = line.trim();
|
|
@@ -116,40 +183,43 @@ export class ClaudeCodeSpawner {
|
|
|
116
183
|
|
|
117
184
|
fullOutput += trimmed + '\n';
|
|
118
185
|
|
|
119
|
-
//
|
|
186
|
+
// Try to extract result text
|
|
120
187
|
try {
|
|
121
188
|
const event = JSON.parse(trimmed);
|
|
122
|
-
if (event.type === 'result'
|
|
123
|
-
resultText = event.result;
|
|
189
|
+
if (event.type === 'result') {
|
|
190
|
+
resultText = event.result || resultText;
|
|
124
191
|
}
|
|
125
192
|
} catch {}
|
|
126
193
|
|
|
127
|
-
|
|
194
|
+
processEvent(trimmed, onOutput, logger);
|
|
128
195
|
}
|
|
129
196
|
});
|
|
130
197
|
|
|
131
198
|
child.stderr.on('data', (data) => {
|
|
132
|
-
|
|
199
|
+
const chunk = data.toString();
|
|
200
|
+
stderr += chunk;
|
|
201
|
+
// Forward stderr too — might contain useful info
|
|
202
|
+
logger.warn(`Claude Code stderr: ${chunk.trim().slice(0, 200)}`);
|
|
133
203
|
});
|
|
134
204
|
|
|
135
205
|
const timer = setTimeout(() => {
|
|
136
206
|
child.kill('SIGTERM');
|
|
207
|
+
if (onOutput) onOutput(`⏰ Claude Code timed out after ${this.timeout / 1000}s`).catch(() => {});
|
|
137
208
|
reject(new Error(`Claude Code timed out after ${this.timeout / 1000}s`));
|
|
138
209
|
}, this.timeout);
|
|
139
210
|
|
|
140
211
|
child.on('close', (code) => {
|
|
141
212
|
clearTimeout(timer);
|
|
142
213
|
|
|
143
|
-
// Process remaining buffer
|
|
144
214
|
if (buffer.trim()) {
|
|
145
215
|
fullOutput += buffer.trim();
|
|
146
216
|
try {
|
|
147
217
|
const event = JSON.parse(buffer.trim());
|
|
148
|
-
if (event.type === 'result'
|
|
149
|
-
resultText = event.result;
|
|
218
|
+
if (event.type === 'result') {
|
|
219
|
+
resultText = event.result || resultText;
|
|
150
220
|
}
|
|
151
221
|
} catch {}
|
|
152
|
-
|
|
222
|
+
processEvent(buffer.trim(), onOutput, logger);
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
if (code !== 0 && !fullOutput) {
|
package/src/tools/os.js
CHANGED
|
@@ -129,11 +129,22 @@ export const handlers = {
|
|
|
129
129
|
if (error && error.killed) {
|
|
130
130
|
return res({ error: `Command timed out after ${timeout_seconds}s` });
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
const result = {
|
|
133
133
|
stdout: stdout || '',
|
|
134
134
|
stderr: stderr || '',
|
|
135
135
|
exit_code: error ? error.code ?? 1 : 0,
|
|
136
|
-
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Send output summary to Telegram
|
|
139
|
+
if (context.onUpdate) {
|
|
140
|
+
const output = (result.stdout || result.stderr || '').trim();
|
|
141
|
+
if (output) {
|
|
142
|
+
const preview = output.length > 300 ? output.slice(0, 300) + '...' : output;
|
|
143
|
+
context.onUpdate(`📋 \`${command.slice(0, 60)}\`\n\`\`\`\n${preview}\n\`\`\``).catch(() => {});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
res(result);
|
|
137
148
|
},
|
|
138
149
|
);
|
|
139
150
|
});
|