glitool 2.0.4 → 2.1.0
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/dist/agent.js +104 -103
- package/dist/agents/coder.js +26 -9
- package/dist/agents/debugger.js +17 -5
- package/dist/agents/executor.js +144 -0
- package/dist/agents/graph.js +29 -8
- package/dist/agents/planner.js +5 -8
- package/dist/clarificationHandler.js +7 -0
- package/dist/clarifier.js +101 -0
- package/dist/llm/factory.js +30 -0
- package/dist/llm/router.js +16 -18
- package/dist/tools/askUserTool.js +22 -0
- package/dist/tools/bashTool.js +20 -7
- package/dist/tools/editFileTool.js +13 -7
- package/dist/tools/index.js +1 -0
- package/dist/tools/readFileTool.js +9 -6
- package/dist/tools/searchCodeTool.js +8 -3
- package/dist/ui/App.js +21 -3
- package/dist/ui/ClarificationCard.js +60 -0
- package/dist/ui/ProcessTrace.js +12 -16
- package/package.json +1 -1
- package/dist/agents/reviewer.js +0 -22
- package/dist/readProject.js +0 -51
- package/dist/tools/analyzeProject.js +0 -61
package/dist/agent.js
CHANGED
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import { writeFileTool, listFilesTool, readFileTool, searchCodeTool, editFileTool, bashTool, readBackgroundOutputTool, webFetchTool, } from "./tools/index.js";
|
|
2
2
|
import { AIMessage, BaseMessage, HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
3
|
-
import { StructuredTool } from "@langchain/core/tools";
|
|
4
3
|
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
5
4
|
import { ChatOpenAI } from '@langchain/openai';
|
|
6
5
|
import { loadSession, loadSummary, saveSession, generateAndSaveSummary } from "./memory.js";
|
|
7
|
-
import { loadConfig } from "./config.js";
|
|
8
6
|
import { loadProjectMemory } from "./projectMemory.js";
|
|
9
7
|
import { config as loadEnv } from 'dotenv';
|
|
10
8
|
import { fileURLToPath } from 'url';
|
|
11
9
|
import { dirname, join } from 'path';
|
|
12
10
|
import { route, stripExplicitPrefix } from './llm/router.js';
|
|
13
11
|
import { logRouting } from './llm/telemetry.js';
|
|
14
|
-
import { runAgentGraph } from "./agents/graph.js";
|
|
15
|
-
import { runReviewer } from "./agents/reviewer-agent.js";
|
|
16
12
|
import os from 'os';
|
|
17
13
|
import { cleanupAll } from "./tools/processRegistry.js";
|
|
18
|
-
import {
|
|
19
|
-
import { runDebugger } from "./agents/debugger.js";
|
|
20
|
-
import { runRefactorer } from "./agents/refactorer.js";
|
|
21
|
-
import { runGitAgent } from "./agents/git-agent.js";
|
|
22
|
-
import { makeLlm, startNewRequest } from './llm/factory.js';
|
|
14
|
+
import { makeLlm, startNewRequest, getResolvedModelForCurrentRequest } from './llm/factory.js';
|
|
23
15
|
import { emit } from './monitor.js';
|
|
16
|
+
import { runClarifier } from './clarifier.js';
|
|
17
|
+
import { setClarificationHandler } from './clarificationHandler.js';
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
import { runExecutor } from "./agents/executor.js";
|
|
24
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
21
|
const __dirname = dirname(__filename);
|
|
26
22
|
loadEnv({ path: join(os.homedir(), '.glitool', '.env') });
|
|
27
23
|
const MAX_HISTORY_CHARS = 60_000;
|
|
28
|
-
// const simpleLlm = makeLlm('meta-llama/Llama-3.3-70B-Instruct-Turbo');
|
|
29
|
-
// const simpleLlm = makeLlm('meta-llama/Llama-3.3-70B-Instruct-Turbo');
|
|
30
24
|
function createLlm(model) {
|
|
31
25
|
return makeLlm(model);
|
|
32
26
|
}
|
|
@@ -37,8 +31,7 @@ function createLlm(model) {
|
|
|
37
31
|
export function getDefaultLlm() {
|
|
38
32
|
return createLlm('meta-llama/Llama-3.3-70B-Instruct-Turbo');
|
|
39
33
|
}
|
|
40
|
-
|
|
41
|
-
const tools = [listFilesTool, readFileTool, searchCodeTool, writeFileTool, editFileTool, bashTool, readBackgroundOutputTool, webFetchTool];
|
|
34
|
+
const chatFallbackTools = [listFilesTool, readFileTool, searchCodeTool, writeFileTool, editFileTool, bashTool, readBackgroundOutputTool, webFetchTool];
|
|
42
35
|
process.on('exit', cleanupAll);
|
|
43
36
|
process.on('SIGINT', () => { cleanupAll(); process.exit(0); });
|
|
44
37
|
process.on('SIGTERM', () => { cleanupAll(); process.exit(0); });
|
|
@@ -49,13 +42,28 @@ export function clearSession() {
|
|
|
49
42
|
}
|
|
50
43
|
const MAX_SUMMARY_CHARS = 2_000;
|
|
51
44
|
const MAX_PROJECT_FACTS_CHARS = 3_000;
|
|
45
|
+
function getGitContext() {
|
|
46
|
+
try {
|
|
47
|
+
const status = execSync('git --no-optional-locks status --short --branch', {
|
|
48
|
+
cwd: process.cwd(),
|
|
49
|
+
timeout: 3000,
|
|
50
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
+
}).toString().trim();
|
|
52
|
+
if (!status)
|
|
53
|
+
return '';
|
|
54
|
+
return `\n\n## Git State\n${status}`;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
52
60
|
function buildSystemPrompt() {
|
|
53
61
|
let summary = loadSummary();
|
|
54
62
|
const project = loadProjectMemory();
|
|
55
63
|
if (!summary) {
|
|
56
64
|
const rawSession = loadSession();
|
|
57
65
|
if (rawSession.length > 4) {
|
|
58
|
-
generateAndSaveSummary(rawSession, getDefaultLlm());
|
|
66
|
+
generateAndSaveSummary(rawSession, getDefaultLlm()).catch(() => { });
|
|
59
67
|
summary = loadSummary();
|
|
60
68
|
}
|
|
61
69
|
}
|
|
@@ -70,6 +78,8 @@ You can:
|
|
|
70
78
|
|
|
71
79
|
Be concise. Default to plain conversation. Only call tools when the request clearly needs them.
|
|
72
80
|
|
|
81
|
+
For greetings or small talk (e.g. "hi", "hey", "thanks"), reply warmly in one short line and invite the user to say what they're working on. Don't ask them to clarify a "request" — there isn't one.
|
|
82
|
+
|
|
73
83
|
When the user asks to read, show, or display a specific file → call readFile.
|
|
74
84
|
For "read <name>" shorthand, pass the bare name; the tool searches the project automatically.
|
|
75
85
|
Don't claim a file is missing without verifying via listFiles or readFile first.
|
|
@@ -95,6 +105,9 @@ Style:
|
|
|
95
105
|
: json;
|
|
96
106
|
prompt += `\n\nProject facts:\n${capped}`;
|
|
97
107
|
}
|
|
108
|
+
const gitContext = getGitContext();
|
|
109
|
+
if (gitContext)
|
|
110
|
+
prompt += gitContext;
|
|
98
111
|
return prompt;
|
|
99
112
|
}
|
|
100
113
|
const systemPrompt = await buildSystemPrompt();
|
|
@@ -165,14 +178,16 @@ function trimHistory(messages) {
|
|
|
165
178
|
}
|
|
166
179
|
return kept;
|
|
167
180
|
}
|
|
181
|
+
// Keyed by the model the SERVER actually runs, not the CLI's role hint.
|
|
182
|
+
// Together.ai pricing per million tokens (May 2026).
|
|
168
183
|
const COST_PER_TOKEN = {
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
184
|
+
'MiniMaxAI/MiniMax-M2.7': { input: 0.30 / 1_000_000, output: 1.20 / 1_000_000 },
|
|
185
|
+
'deepseek-ai/DeepSeek-V4-Pro': { input: 2.10 / 1_000_000, output: 4.40 / 1_000_000 },
|
|
186
|
+
'moonshotai/Kimi-K2.6': { input: 1.20 / 1_000_000, output: 4.50 / 1_000_000 },
|
|
187
|
+
'Qwen/Qwen2.5-7B-Instruct-Turbo': { input: 0.30 / 1_000_000, output: 0.30 / 1_000_000 },
|
|
173
188
|
};
|
|
174
189
|
function estimateCost(model, inputTokens, outputTokens) {
|
|
175
|
-
const rates = COST_PER_TOKEN[model] ?? COST_PER_TOKEN['
|
|
190
|
+
const rates = COST_PER_TOKEN[model] ?? COST_PER_TOKEN['MiniMaxAI/MiniMax-M2.7'];
|
|
176
191
|
return inputTokens * rates.input + outputTokens * rates.output;
|
|
177
192
|
}
|
|
178
193
|
function extractTarget(args) {
|
|
@@ -193,13 +208,33 @@ function extractTarget(args) {
|
|
|
193
208
|
}
|
|
194
209
|
return String(first ?? '');
|
|
195
210
|
}
|
|
196
|
-
export async function chat(userInput, onToolCall, onStatus, onToken,
|
|
211
|
+
export async function chat(userInput, onToolCall, onStatus, onToken, onUsage, onStageEvent, onClarificationNeeded) {
|
|
197
212
|
startNewRequest();
|
|
198
213
|
emit('user_prompt', { text: userInput });
|
|
199
214
|
const decision = await route(userInput, sessionMessages.slice(-6));
|
|
200
215
|
emit('router', { domain: decision.domain, tier: decision.tier, model: decision.recommendedModel, reason: decision.reason });
|
|
201
216
|
logRouting(userInput, decision);
|
|
202
217
|
const cleanedInput = decision.source === 'explicit' ? stripExplicitPrefix(userInput) : userInput;
|
|
218
|
+
// Register the askUser tool callback so the executor can pause mid-execution and ask the user
|
|
219
|
+
if (onClarificationNeeded) {
|
|
220
|
+
setClarificationHandler(onClarificationNeeded);
|
|
221
|
+
}
|
|
222
|
+
// Fast codebase grep — no LLM, just gives the executor a head start
|
|
223
|
+
const { codeContext } = await runClarifier(cleanedInput, decision.domain, sessionMessages.length);
|
|
224
|
+
let finalInput = cleanedInput;
|
|
225
|
+
if (codeContext) {
|
|
226
|
+
finalInput = `${cleanedInput}\n\n[Codebase search context]:\n${codeContext}`;
|
|
227
|
+
}
|
|
228
|
+
emit('clarifier', { questions: [], skipped: true });
|
|
229
|
+
emit('memory', {
|
|
230
|
+
session_messages: sessionMessages.length,
|
|
231
|
+
has_summary: !!loadSummary(),
|
|
232
|
+
has_project: !!loadProjectMemory(),
|
|
233
|
+
recent: sessionMessages.slice(-4).map(m => ({
|
|
234
|
+
role: m._getType(),
|
|
235
|
+
text: (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)).slice(0, 150)
|
|
236
|
+
}))
|
|
237
|
+
});
|
|
203
238
|
sessionMessages.push(new HumanMessage(cleanedInput));
|
|
204
239
|
const shortcut = await tryDirectReadShortcut(cleanedInput, onToolCall);
|
|
205
240
|
if (shortcut !== null) {
|
|
@@ -207,93 +242,37 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
207
242
|
saveSession(sessionMessages);
|
|
208
243
|
return shortcut;
|
|
209
244
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
if (decision.domain === 'review') {
|
|
221
|
-
emit('agent', { name: 'reviewer' });
|
|
222
|
-
onStageEvent?.({ type: 'stage_start', stage: 'reviewer' });
|
|
223
|
-
const result = await runReviewer(cleanedInput, (name, args) => {
|
|
224
|
-
onStageEvent?.({ type: 'tool', stage: 'reviewer', tool: name, target: extractTarget(args) });
|
|
225
|
-
onToolCall(name, args);
|
|
226
|
-
}, decision.recommendedModel);
|
|
227
|
-
onStageEvent?.({ type: 'stage_done', stage: 'reviewer' });
|
|
228
|
-
sessionMessages.push(new AIMessage(result));
|
|
229
|
-
saveSession(sessionMessages);
|
|
230
|
-
return result;
|
|
231
|
-
}
|
|
232
|
-
if (decision.domain === 'debugging') {
|
|
233
|
-
emit('agent', { name: 'debugger' });
|
|
234
|
-
onStageEvent?.({ type: 'stage_start', stage: 'debugger' });
|
|
235
|
-
const result = await runDebugger(cleanedInput, (name, args) => {
|
|
236
|
-
onStageEvent?.({ type: 'tool', stage: 'debugger', tool: name, target: extractTarget(args) });
|
|
245
|
+
const EXECUTOR_DOMAINS = new Set([
|
|
246
|
+
'coding', 'debugging', 'refactoring', 'git', 'planning', 'review'
|
|
247
|
+
]);
|
|
248
|
+
if (EXECUTOR_DOMAINS.has(decision.domain)) {
|
|
249
|
+
emit('agent', { name: `executor:${decision.domain}` });
|
|
250
|
+
onStageEvent?.({ type: 'stage_start', stage: decision.domain });
|
|
251
|
+
const result = await runExecutor(finalInput, decision.domain, decision.recommendedModel, (name, args) => {
|
|
252
|
+
onStageEvent?.({ type: 'tool', stage: decision.domain, tool: name, target: extractTarget(args) });
|
|
237
253
|
onToolCall(name, args);
|
|
238
|
-
},
|
|
239
|
-
onStageEvent?.({ type: 'stage_done', stage:
|
|
254
|
+
}, onStatus, trimHistory(sessionMessages));
|
|
255
|
+
onStageEvent?.({ type: 'stage_done', stage: decision.domain });
|
|
240
256
|
sessionMessages.push(new AIMessage(result));
|
|
241
257
|
saveSession(sessionMessages);
|
|
258
|
+
emit('response', { text: result });
|
|
259
|
+
emit('done', { total_tokens: 0 }); // no token counting in executor path yet
|
|
242
260
|
return result;
|
|
243
261
|
}
|
|
244
|
-
if (decision.domain === 'refactoring') {
|
|
245
|
-
emit('agent', { name: 'refactorer' });
|
|
246
|
-
onStageEvent?.({ type: 'stage_start', stage: 'refactorer' });
|
|
247
|
-
const result = await runRefactorer(cleanedInput, (name, args) => {
|
|
248
|
-
onStageEvent?.({ type: 'tool', stage: 'refactorer', tool: name, target: extractTarget(args) });
|
|
249
|
-
onToolCall(name, args);
|
|
250
|
-
}, decision.recommendedModel);
|
|
251
|
-
onStageEvent?.({ type: 'stage_done', stage: 'refactorer' });
|
|
252
|
-
sessionMessages.push(new AIMessage(result));
|
|
253
|
-
saveSession(sessionMessages);
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
if (decision.domain === 'git') {
|
|
257
|
-
emit('agent', { name: 'git' });
|
|
258
|
-
onStageEvent?.({ type: 'stage_start', stage: 'git_agent' });
|
|
259
|
-
const result = await runGitAgent(cleanedInput, (name, args) => {
|
|
260
|
-
onStageEvent?.({ type: 'tool', stage: 'git_agent', tool: name, target: extractTarget(args) });
|
|
261
|
-
onToolCall(name, args);
|
|
262
|
-
}, decision.recommendedModel);
|
|
263
|
-
onStageEvent?.({ type: 'stage_done', stage: 'git_agent' });
|
|
264
|
-
sessionMessages.push(new AIMessage(result));
|
|
265
|
-
saveSession(sessionMessages);
|
|
266
|
-
return result;
|
|
267
|
-
}
|
|
268
|
-
if (decision.domain === 'coding') {
|
|
269
|
-
emit('agent', { name: 'coder' });
|
|
270
|
-
const graphResult = await runAgentGraph(cleanedInput, buildSystemPrompt(), onToolCall, onStatus ?? (() => { }), decision, onStageEvent // ← add this
|
|
271
|
-
);
|
|
272
|
-
if (graphResult.escalated && onEscalation) {
|
|
273
|
-
onEscalation({
|
|
274
|
-
userMessage: graphResult.userMessage,
|
|
275
|
-
plan: graphResult.plan,
|
|
276
|
-
trajectory: graphResult.trajectory,
|
|
277
|
-
finalOutput: graphResult.finalOutput ?? '',
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
if (graphResult.finalOutput) {
|
|
281
|
-
sessionMessages.push(new AIMessage(graphResult.finalOutput));
|
|
282
|
-
saveSession(sessionMessages);
|
|
283
|
-
return graphResult.finalOutput;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
262
|
emit('agent', { name: 'chat' });
|
|
263
|
+
const chatTools = decision.domain === 'chat' ? [] : chatFallbackTools;
|
|
287
264
|
const simpleAgent = createReactAgent({
|
|
288
265
|
llm: createLlm(decision.recommendedModel),
|
|
289
|
-
tools,
|
|
266
|
+
tools: chatTools,
|
|
290
267
|
stateModifier: new SystemMessage(systemPrompt)
|
|
291
268
|
});
|
|
292
269
|
const trimmed = trimHistory(sessionMessages);
|
|
270
|
+
emit('system_prompt', { agent: 'chat', text: systemPrompt.slice(0, 600) });
|
|
293
271
|
const eventStrem = simpleAgent.streamEvents({ messages: trimmed }, { version: 'v2' });
|
|
294
272
|
let finalResponse = '';
|
|
295
273
|
let totalInputTokens = 0;
|
|
296
274
|
let totalOutputTokens = 0;
|
|
275
|
+
let resolvedModel = null;
|
|
297
276
|
for await (const { event, data, name: eventName } of eventStrem) {
|
|
298
277
|
if (event === 'on_chat_model_stream') {
|
|
299
278
|
const chunk = data.chunk;
|
|
@@ -308,27 +287,49 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
308
287
|
onToken?.(token);
|
|
309
288
|
finalResponse += token;
|
|
310
289
|
}
|
|
311
|
-
// const token = data.chunk?.content;
|
|
312
|
-
// if(token && typeof token === 'string'){
|
|
313
|
-
// onToken?.(token);
|
|
314
|
-
// finalResponse += token;
|
|
315
|
-
// }
|
|
316
290
|
}
|
|
317
291
|
if (event === 'on_tool_start') {
|
|
318
292
|
onToolCall(eventName, data.input);
|
|
319
293
|
emit('tool_call', { name: eventName, input: data.input });
|
|
320
294
|
}
|
|
295
|
+
if (event === 'on_tool_end') {
|
|
296
|
+
const out = typeof data.output === 'string'
|
|
297
|
+
? data.output
|
|
298
|
+
: JSON.stringify(data.output ?? '');
|
|
299
|
+
emit('tool_response', { name: eventName, output: out.slice(0, 1000) });
|
|
300
|
+
}
|
|
321
301
|
if (event === 'on_chat_model_end') {
|
|
322
302
|
const usage = data.output?.usage_metadata;
|
|
303
|
+
// Prefer the X-Glitool-Resolved-Model header (captured via fetch wrapper).
|
|
304
|
+
// Falls back to LangChain's response_metadata.model_name, which on
|
|
305
|
+
// ChatOpenAI reports the REQUEST hint (the role), not the resolved name.
|
|
306
|
+
const fromHeader = getResolvedModelForCurrentRequest();
|
|
307
|
+
const fromMeta = data.output?.response_metadata?.model_name;
|
|
308
|
+
if (fromHeader)
|
|
309
|
+
resolvedModel = fromHeader;
|
|
310
|
+
else if (fromMeta)
|
|
311
|
+
resolvedModel = fromMeta;
|
|
323
312
|
if (usage) {
|
|
324
313
|
totalInputTokens += usage.input_tokens ?? 0;
|
|
325
314
|
totalOutputTokens += usage.output_tokens ?? 0;
|
|
326
|
-
emit('llm_call', {
|
|
315
|
+
emit('llm_call', {
|
|
316
|
+
tokens_in: usage.input_tokens ?? 0,
|
|
317
|
+
tokens_out: usage.output_tokens ?? 0,
|
|
318
|
+
model: resolvedModel ?? decision.recommendedModel,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const output = data.output;
|
|
322
|
+
let msgText = '';
|
|
323
|
+
if (typeof output?.content === 'string') {
|
|
324
|
+
msgText = output.content;
|
|
325
|
+
}
|
|
326
|
+
else if (Array.isArray(output?.content)) {
|
|
327
|
+
msgText = output.content.filter((c) => c.type === 'text').map((c) => c.text ?? '').join('');
|
|
327
328
|
}
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
finalResponse =
|
|
329
|
+
if (msgText) {
|
|
330
|
+
emit('llm_message', { text: msgText.slice(0, 800) });
|
|
331
|
+
if (!finalResponse) {
|
|
332
|
+
finalResponse = msgText;
|
|
332
333
|
onToken?.(finalResponse);
|
|
333
334
|
}
|
|
334
335
|
}
|
|
@@ -338,7 +339,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
|
|
|
338
339
|
sessionMessages.push(new AIMessage(finalResponse));
|
|
339
340
|
}
|
|
340
341
|
if (onUsage && (totalInputTokens + totalOutputTokens) > 0) {
|
|
341
|
-
const model = decision.recommendedModel;
|
|
342
|
+
const model = resolvedModel ?? decision.recommendedModel;
|
|
342
343
|
onUsage(totalInputTokens + totalOutputTokens, estimateCost(model, totalInputTokens, totalOutputTokens));
|
|
343
344
|
}
|
|
344
345
|
saveSession(sessionMessages);
|
package/dist/agents/coder.js
CHANGED
|
@@ -2,30 +2,39 @@ import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
|
|
2
2
|
import { makeLlm } from '../llm/factory.js';
|
|
3
3
|
import { SystemMessage, HumanMessage, BaseMessage } from "@langchain/core/messages";
|
|
4
4
|
import { StructuredTool } from "@langchain/core/tools";
|
|
5
|
-
import { listFilesTool, readFileTool, searchCodeTool, editFileTool, writeFileTool, bashTool } from '../tools/index.js';
|
|
5
|
+
import { listFilesTool, readFileTool, searchCodeTool, editFileTool, writeFileTool, bashTool, readBackgroundOutputTool } from '../tools/index.js';
|
|
6
6
|
import { scoreRisk, getRiskMessage } from "../trust/riskScorer.js";
|
|
7
7
|
import { log } from "../logger.js";
|
|
8
|
+
import { emit } from '../monitor.js';
|
|
8
9
|
export async function runCoder(plan, userMessage, onToolCall, model, onReasoning) {
|
|
9
10
|
const coderLlm = makeLlm(model);
|
|
11
|
+
emit('system_prompt', { agent: 'coder', text: plan.slice(0, 300) });
|
|
12
|
+
emit('enhanced_prompt', { text: userMessage.slice(0, 600) });
|
|
10
13
|
const coderAgent = createReactAgent({
|
|
11
14
|
llm: coderLlm,
|
|
12
|
-
tools: [listFilesTool, readFileTool, searchCodeTool, editFileTool, writeFileTool, bashTool],
|
|
15
|
+
tools: [listFilesTool, readFileTool, searchCodeTool, editFileTool, writeFileTool, bashTool, readBackgroundOutputTool],
|
|
13
16
|
stateModifier: new SystemMessage(`You are a coding execution agent. Execute the given plan step by step using tools.
|
|
14
17
|
|
|
18
|
+
Available tools: listFiles, readFile, searchCode, editFile, writeFile, bash, read_background_output. These are the ONLY tools that exist — do not call any other tool name (no "runShell", "exec", "shell", etc.).
|
|
19
|
+
|
|
15
20
|
GROUNDING RULES — these are not optional:
|
|
16
21
|
|
|
22
|
+
|
|
17
23
|
1. BEFORE editing any file, READ it first with readFile to confirm structure.
|
|
18
24
|
2. PREFER searchCode over readFile for navigation. Read whole files only when you'll actually edit them.
|
|
19
25
|
3. For UI features (slash commands, menus, palettes), search src/ui/, src/components/, src/cli/ first — don't trust the plan's filename blindly.
|
|
20
26
|
4. After every editFile, if the tool returned an error, STOP and read the file again. Do not retry with guesses.
|
|
21
|
-
5.
|
|
22
|
-
6.
|
|
23
|
-
7.
|
|
27
|
+
5. When building a new project from scratch you MAY create package.json/tsconfig.json. Never add dependencies to an EXISTING package.json unless explicitly asked.
|
|
28
|
+
6. Shell commands MUST be non-interactive — you have no keyboard, so any command that opens a prompt/wizard will hang and be killed. For scaffolders, pass flags that skip prompts and use defaults: e.g. "npx create-next-app@latest my-app --yes". Give scaffold/install commands a generous timeout (120000) since they download packages.
|
|
29
|
+
7. When a scaffolder creates a new subdirectory (e.g. "create-next-app my-app" makes ./my-app), ALL subsequent bash commands for that project MUST set cwd to that subdirectory: bash({ command: "npm install", cwd: "my-app" }). Otherwise "npm run dev"/"npm install" run in the parent and fail with "Missing script" or ENOENT.
|
|
30
|
+
8. Maximum 5 file reads per task. If you need more, you're doing it wrong — use searchCode instead.
|
|
31
|
+
9. If you can't safely complete the task, STOP and return a failure message. Do not invent.
|
|
24
32
|
|
|
25
33
|
Be surgical, not exhaustive. Most tasks need 2-4 tool calls, not 15. The validator will catch broken output — you don't need to over-verify.
|
|
26
34
|
|
|
27
35
|
Response style:
|
|
28
|
-
-
|
|
36
|
+
- REPORT OUTCOMES FAITHFULLY. If a tool returned an error or you could not verify a step worked, say so explicitly. Never claim success for steps that failed, were killed, or you could not confirm. "I tried X but the tool returned <error>" is correct. "X is complete" when you have no proof is a LIE — do not do this.
|
|
37
|
+
- Your final text should be 1-3 sentences summarizing what files you actually changed and why.
|
|
29
38
|
- Do NOT paste file contents in the response — the files are on disk; the user can read them.
|
|
30
39
|
- The validator runs tsc + ESLint after you finish — no need to verify those yourself.
|
|
31
40
|
- If a step is impossible (binary file, command blocked, etc.), say so explicitly and stop.
|
|
@@ -37,9 +46,14 @@ Response style:
|
|
|
37
46
|
for await (const chunk of stream) {
|
|
38
47
|
if (blocked)
|
|
39
48
|
break;
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
// Tool results node
|
|
50
|
+
const toolMsgs = chunk.tools?.messages;
|
|
51
|
+
if (toolMsgs?.length) {
|
|
52
|
+
for (const msg of toolMsgs) {
|
|
53
|
+
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content ?? '');
|
|
54
|
+
emit('tool_response', { name: msg.name ?? 'tool', output: content.slice(0, 1000) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
43
57
|
const agentMsgs = chunk.agent?.messages;
|
|
44
58
|
if (!agentMsgs?.length) {
|
|
45
59
|
log('coder:chunk', { keys: Object.keys(chunk).join(',') });
|
|
@@ -56,15 +70,18 @@ Response style:
|
|
|
56
70
|
getRiskMessage(toolCall.name, risk, toolCall.args);
|
|
57
71
|
if (risk === 'high') {
|
|
58
72
|
onToolCall(toolCall.name, toolCall.args);
|
|
73
|
+
emit('tool_call', { name: toolCall.name, input: toolCall.args });
|
|
59
74
|
result = `Blocked: I cannot write to sensitive files like ${toolCall.args?.filePath}.`;
|
|
60
75
|
blocked = true;
|
|
61
76
|
break;
|
|
62
77
|
}
|
|
63
78
|
onToolCall(toolCall.name, toolCall.args);
|
|
79
|
+
emit('tool_call', { name: toolCall.name, input: toolCall.args });
|
|
64
80
|
}
|
|
65
81
|
else if (text) {
|
|
66
82
|
onReasoning?.(text);
|
|
67
83
|
result = text;
|
|
84
|
+
emit('llm_message', { text: text.slice(0, 800) });
|
|
68
85
|
}
|
|
69
86
|
}
|
|
70
87
|
log('coder:chunk', { keys: Object.keys(chunk).join(',') });
|
package/dist/agents/debugger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { makeLlm } from '../llm/factory.js';
|
|
2
2
|
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
3
3
|
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
|
|
4
|
+
import { emit } from '../monitor.js';
|
|
4
5
|
import { listFilesTool, readFileTool, searchCodeTool, bashTool, editFileTool, } from '../tools/index.js';
|
|
5
6
|
const DEBUG_SYSTEM_PROMPT = `You are a debugging agent. You investigate first, then patch.
|
|
6
7
|
|
|
@@ -83,6 +84,8 @@ export async function runDebugger(userMessage, onToolCall, model) {
|
|
|
83
84
|
bashTool,
|
|
84
85
|
editFileTool,
|
|
85
86
|
];
|
|
87
|
+
emit('system_prompt', { agent: 'debugger', text: DEBUG_SYSTEM_PROMPT.slice(0, 600) });
|
|
88
|
+
emit('enhanced_prompt', { text: userMessage.slice(0, 600) });
|
|
86
89
|
const agent = createReactAgent({
|
|
87
90
|
llm,
|
|
88
91
|
tools,
|
|
@@ -93,17 +96,26 @@ export async function runDebugger(userMessage, onToolCall, model) {
|
|
|
93
96
|
for await (const { event, data, name: eventName } of stream) {
|
|
94
97
|
if (event === 'on_tool_start') {
|
|
95
98
|
onToolCall(eventName, data.input);
|
|
99
|
+
emit('tool_call', { name: eventName, input: data.input });
|
|
100
|
+
}
|
|
101
|
+
if (event === 'on_tool_end') {
|
|
102
|
+
const out = typeof data.output === 'string'
|
|
103
|
+
? data.output
|
|
104
|
+
: JSON.stringify(data.output ?? '');
|
|
105
|
+
emit('tool_response', { name: eventName, output: out.slice(0, 1000) });
|
|
96
106
|
}
|
|
97
107
|
if (event === 'on_chat_model_end') {
|
|
98
108
|
const output = data.output;
|
|
109
|
+
let content = '';
|
|
99
110
|
if (typeof output?.content === 'string') {
|
|
100
|
-
|
|
111
|
+
content = output.content;
|
|
101
112
|
}
|
|
102
113
|
else if (Array.isArray(output?.content)) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
content = output.content.filter((c) => c.type === 'text').map((c) => c.text ?? '').join('');
|
|
115
|
+
}
|
|
116
|
+
if (content) {
|
|
117
|
+
finalText = content;
|
|
118
|
+
emit('llm_message', { text: content.slice(0, 800) });
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
121
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
2
|
+
import { makeLlm } from '../llm/factory.js';
|
|
3
|
+
import { SystemMessage, HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
4
|
+
import { emit } from '../monitor.js';
|
|
5
|
+
import { listFilesTool, readFileTool, searchCodeTool, editFileTool, writeFileTool, bashTool, readBackgroundOutputTool, webFetchTool, askUserTool, } from '../tools/index.js';
|
|
6
|
+
const DOMAIN_RULES = {
|
|
7
|
+
debugging: `
|
|
8
|
+
## Debugging Rules
|
|
9
|
+
WORKFLOW — follow in order:
|
|
10
|
+
1. REPRODUCE — run the failing command or read the file mentioned. Start immediately, do not ask first.
|
|
11
|
+
2. DIAGNOSE — before touching anything, output:
|
|
12
|
+
## Diagnosis
|
|
13
|
+
- Root cause: one sentence
|
|
14
|
+
- Where: file.ts:LINE
|
|
15
|
+
- Why it fails: short explanation
|
|
16
|
+
3. PATCH — one minimal editFile. Smallest change that fixes the issue.
|
|
17
|
+
4. VERIFY — re-run the exact command from step 1.
|
|
18
|
+
|
|
19
|
+
STOP CONDITIONS (after you have started investigating):
|
|
20
|
+
- Same tool called twice with identical input → stop and report
|
|
21
|
+
- editFile failed twice for the same file → read the file fresh, try once more or give up
|
|
22
|
+
- 10 tool calls used → wrap up with what you have
|
|
23
|
+
|
|
24
|
+
Final response format:
|
|
25
|
+
## Diagnosis
|
|
26
|
+
...
|
|
27
|
+
## Fix
|
|
28
|
+
- file.ts:LINE — what changed
|
|
29
|
+
## Verification
|
|
30
|
+
- ran: \`<command>\`
|
|
31
|
+
- result: pass | fail | not verified — [reason]`,
|
|
32
|
+
coding: `
|
|
33
|
+
## Coding Rules
|
|
34
|
+
1. Read package.json first — understand the project type and available scripts
|
|
35
|
+
2. If you scaffold a new project into a subdirectory, ALL subsequent commands must use cwd pointing to that subdirectory
|
|
36
|
+
3. BEFORE editing any file, READ it first to confirm its structure
|
|
37
|
+
4. Do not add dependencies, helpers, or abstractions beyond what was explicitly asked
|
|
38
|
+
5. Shell commands must be non-interactive — use --yes / --defaults flags for scaffolders
|
|
39
|
+
6. After writing files, run the appropriate check (npx tsc --noEmit, npm run build)
|
|
40
|
+
7. Maximum 5 file reads — use searchCode for navigation instead
|
|
41
|
+
8. Run shell commands ONE AT A TIME — never call bash multiple times in the same step. Wait for each command's output before running the next.
|
|
42
|
+
9. To scaffold Next.js: use \`npx create-next-app@latest <name> --yes\`. Do NOT use --use-app-dir (deprecated in Next.js 14+). The project name must be the first argument before any flags.
|
|
43
|
+
10. For files over ~120 lines, write a SKELETON first with writeFile (just structure + imports + section headers as comments), then add each section with editFile. Never write a 200+ line file in one writeFile call — long generations get truncated mid-string and produce invalid syntax.`,
|
|
44
|
+
refactoring: `
|
|
45
|
+
## Refactoring Rules
|
|
46
|
+
1. Read ALL files you plan to touch before changing any of them
|
|
47
|
+
2. Make changes one file at a time, run tsc after each
|
|
48
|
+
3. Do not restructure, rename, or clean up anything beyond the stated scope
|
|
49
|
+
4. Preserve all existing behaviour — only change what was asked`,
|
|
50
|
+
git: `
|
|
51
|
+
## Git Rules
|
|
52
|
+
1. Run git status first — understand current state before acting
|
|
53
|
+
2. Confirm the branch name before any commit or push
|
|
54
|
+
3. Stage specific files by name — never use git add .
|
|
55
|
+
4. Never force push unless explicitly asked
|
|
56
|
+
5. Show the user a diff summary before committing`,
|
|
57
|
+
planning: `
|
|
58
|
+
## Planning Rules
|
|
59
|
+
Think through the full approach before writing any code.
|
|
60
|
+
Output a clear numbered plan first, then execute it yourself immediately after.
|
|
61
|
+
Do not stop after the plan — carry it out.
|
|
62
|
+
If a step fails, adapt — do not blindly continue to the next step.`,
|
|
63
|
+
review: `
|
|
64
|
+
## Review Rules
|
|
65
|
+
Read all changed or relevant files. Report findings as:
|
|
66
|
+
- BUG: file.ts:LINE — what the issue is and why it breaks
|
|
67
|
+
- SECURITY: file.ts:LINE — what the vulnerability is
|
|
68
|
+
- COMPLEXITY: file.ts:LINE — what could be simplified and how
|
|
69
|
+
- MISSING: what edge case or error path is unhandled
|
|
70
|
+
Be specific. Do not report style preferences. Only report things that cause real problems.`,
|
|
71
|
+
};
|
|
72
|
+
const BASE_PROMPT = `You are Glitool's execution agent. You solve coding tasks directly using tools.
|
|
73
|
+
|
|
74
|
+
CORE RULES — not optional:
|
|
75
|
+
1. READ BEFORE WRITING — always read a file before editing it
|
|
76
|
+
2. SEARCH BEFORE READING — use searchCode to locate symbols, read only when you'll edit
|
|
77
|
+
3. VERIFY HONESTLY — if verification failed or wasn't run, say so explicitly. Never claim success without evidence
|
|
78
|
+
4. NO SCOPE CREEP — do not add abstractions, helpers, or cleanup beyond what was asked
|
|
79
|
+
5. DIAGNOSE BEFORE SWITCHING — if a tool fails, understand why before trying something different
|
|
80
|
+
6. NON-INTERACTIVE SHELL — commands that open prompts will hang. Use --yes flags for scaffolders
|
|
81
|
+
7. CWD AWARENESS — if you scaffold a project into a subdirectory, use cwd for all subsequent commands
|
|
82
|
+
8. STOP ON REPEATED FAILURE — if the same tool call fails twice with the same input, stop and report
|
|
83
|
+
9. ACT, DON'T NARRATE — every message you emit MUST either (a) include a tool call, or (b) be your FINAL response after the task is genuinely complete. Mid-task messages with text only and no tool call ("Now let me set up the pages next…", "I'll create the components now…", "Portfolio created! Now let me…") will end the agent IMMEDIATELY — that text becomes the user's final answer and any planned next steps are lost. If you have more steps to do, just do them — call the next tool. Save commentary for ONE final message AFTER the last tool call.
|
|
84
|
+
|
|
85
|
+
TOOLS AVAILABLE: listFiles, readFile, searchCode, editFile, writeFile, bash, webFetch
|
|
86
|
+
Use bash for: running commands, checking output, starting servers
|
|
87
|
+
Use editFile for: modifying existing files (requires oldString + newString)
|
|
88
|
+
Use writeFile for: creating new files only
|
|
89
|
+
|
|
90
|
+
RESPONSE FORMAT:
|
|
91
|
+
- 2-4 sentences: what you changed, what command you ran to verify, whether it passed
|
|
92
|
+
- Do NOT paste file contents in your response
|
|
93
|
+
- If something failed, say exactly what failed and why`;
|
|
94
|
+
export async function runExecutor(userMessage, domain, model, onToolCall, onStatus, history = []) {
|
|
95
|
+
const domainRules = DOMAIN_RULES[domain] ?? '';
|
|
96
|
+
const systemPrompt = domainRules
|
|
97
|
+
? `${BASE_PROMPT}\n\n${domainRules}`
|
|
98
|
+
: BASE_PROMPT;
|
|
99
|
+
emit('system_prompt', { agent: `executor:${domain}`, text: systemPrompt.slice(0, 600) });
|
|
100
|
+
emit('enhanced_prompt', { text: userMessage.slice(0, 600) });
|
|
101
|
+
onStatus?.(`${domain.charAt(0).toUpperCase() + domain.slice(1)}...`);
|
|
102
|
+
const llm = makeLlm(model);
|
|
103
|
+
const agent = createReactAgent({
|
|
104
|
+
llm,
|
|
105
|
+
tools: [
|
|
106
|
+
listFilesTool, readFileTool, searchCodeTool,
|
|
107
|
+
editFileTool, writeFileTool, bashTool,
|
|
108
|
+
readBackgroundOutputTool, webFetchTool, askUserTool,
|
|
109
|
+
],
|
|
110
|
+
stateModifier: new SystemMessage(systemPrompt),
|
|
111
|
+
});
|
|
112
|
+
const stream = agent.streamEvents({ messages: [...history, new HumanMessage(userMessage)] }, { version: 'v2', recursionLimit: 50 });
|
|
113
|
+
let finalText = '';
|
|
114
|
+
for await (const { event, data, name: eventName } of stream) {
|
|
115
|
+
if (event === 'on_tool_start') {
|
|
116
|
+
onToolCall(eventName, data.input);
|
|
117
|
+
emit('tool_call', { name: eventName, input: data.input });
|
|
118
|
+
}
|
|
119
|
+
if (event === 'on_tool_end') {
|
|
120
|
+
const raw = data.output;
|
|
121
|
+
const out = typeof raw === 'string'
|
|
122
|
+
? raw
|
|
123
|
+
: (raw?.content ?? JSON.stringify(raw ?? ''));
|
|
124
|
+
emit('tool_response', { name: eventName, output: String(out).slice(0, 1000) });
|
|
125
|
+
}
|
|
126
|
+
if (event === 'on_chat_model_end') {
|
|
127
|
+
const output = data.output;
|
|
128
|
+
let content = '';
|
|
129
|
+
if (typeof output?.content === 'string')
|
|
130
|
+
content = output.content;
|
|
131
|
+
else if (Array.isArray(output?.content)) {
|
|
132
|
+
content = output.content
|
|
133
|
+
.filter((c) => c.type === 'text')
|
|
134
|
+
.map((c) => c.text ?? '')
|
|
135
|
+
.join('');
|
|
136
|
+
}
|
|
137
|
+
if (content) {
|
|
138
|
+
finalText = content;
|
|
139
|
+
emit('llm_message', { text: content.slice(0, 800) });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return finalText || 'No output.';
|
|
144
|
+
}
|