@hailer/mcp 0.1.14 → 0.1.16
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/.claude/agents/agent-giuseppe-app-builder.md +7 -6
- package/.claude/agents/agent-lars-code-inspector.md +26 -14
- package/dist/agents/bot-manager.d.ts +48 -0
- package/dist/agents/bot-manager.js +254 -0
- package/dist/agents/factory.d.ts +150 -0
- package/dist/agents/factory.js +650 -0
- package/dist/agents/giuseppe/ai.d.ts +83 -0
- package/dist/agents/giuseppe/ai.js +466 -0
- package/dist/agents/giuseppe/bot.d.ts +110 -0
- package/dist/agents/giuseppe/bot.js +780 -0
- package/dist/agents/giuseppe/config.d.ts +25 -0
- package/dist/agents/giuseppe/config.js +227 -0
- package/dist/agents/giuseppe/files.d.ts +52 -0
- package/dist/agents/giuseppe/files.js +338 -0
- package/dist/agents/giuseppe/git.d.ts +48 -0
- package/dist/agents/giuseppe/git.js +298 -0
- package/dist/agents/giuseppe/index.d.ts +97 -0
- package/dist/agents/giuseppe/index.js +258 -0
- package/dist/agents/giuseppe/lsp.d.ts +113 -0
- package/dist/agents/giuseppe/lsp.js +485 -0
- package/dist/agents/giuseppe/monitor.d.ts +118 -0
- package/dist/agents/giuseppe/monitor.js +621 -0
- package/dist/agents/giuseppe/prompt.d.ts +5 -0
- package/dist/agents/giuseppe/prompt.js +94 -0
- package/dist/agents/giuseppe/registries/pending-classification.d.ts +28 -0
- package/dist/agents/giuseppe/registries/pending-classification.js +50 -0
- package/dist/agents/giuseppe/registries/pending-fix.d.ts +30 -0
- package/dist/agents/giuseppe/registries/pending-fix.js +42 -0
- package/dist/agents/giuseppe/registries/pending.d.ts +27 -0
- package/dist/agents/giuseppe/registries/pending.js +49 -0
- package/dist/agents/giuseppe/specialist.d.ts +47 -0
- package/dist/agents/giuseppe/specialist.js +237 -0
- package/dist/agents/giuseppe/types.d.ts +123 -0
- package/dist/agents/giuseppe/types.js +9 -0
- package/dist/agents/hailer-expert/index.d.ts +8 -0
- package/dist/agents/hailer-expert/index.js +14 -0
- package/dist/agents/hal/daemon.d.ts +142 -0
- package/dist/agents/hal/daemon.js +1103 -0
- package/dist/agents/hal/definitions.d.ts +55 -0
- package/dist/agents/hal/definitions.js +263 -0
- package/dist/agents/hal/index.d.ts +3 -0
- package/dist/agents/hal/index.js +8 -0
- package/dist/agents/index.d.ts +18 -0
- package/dist/agents/index.js +48 -0
- package/dist/agents/shared/base.d.ts +216 -0
- package/dist/agents/shared/base.js +846 -0
- package/dist/agents/shared/services/agent-registry.d.ts +107 -0
- package/dist/agents/shared/services/agent-registry.js +629 -0
- package/dist/agents/shared/services/conversation-manager.d.ts +50 -0
- package/dist/agents/shared/services/conversation-manager.js +136 -0
- package/dist/agents/shared/services/mcp-client.d.ts +56 -0
- package/dist/agents/shared/services/mcp-client.js +124 -0
- package/dist/agents/shared/services/message-classifier.d.ts +37 -0
- package/dist/agents/shared/services/message-classifier.js +187 -0
- package/dist/agents/shared/services/message-formatter.d.ts +89 -0
- package/dist/agents/shared/services/message-formatter.js +371 -0
- package/dist/agents/shared/services/session-logger.d.ts +106 -0
- package/dist/agents/shared/services/session-logger.js +446 -0
- package/dist/agents/shared/services/tool-executor.d.ts +41 -0
- package/dist/agents/shared/services/tool-executor.js +169 -0
- package/dist/agents/shared/services/workspace-schema-cache.d.ts +125 -0
- package/dist/agents/shared/services/workspace-schema-cache.js +578 -0
- package/dist/agents/shared/specialist.d.ts +91 -0
- package/dist/agents/shared/specialist.js +399 -0
- package/dist/agents/shared/tool-schema-loader.d.ts +62 -0
- package/dist/agents/shared/tool-schema-loader.js +232 -0
- package/dist/agents/shared/types.d.ts +327 -0
- package/dist/agents/shared/types.js +121 -0
- package/dist/app.js +21 -4
- package/dist/cli.js +0 -0
- package/dist/client/agents/orchestrator.d.ts +1 -0
- package/dist/client/agents/orchestrator.js +12 -1
- package/dist/commands/seed-config.d.ts +9 -0
- package/dist/commands/seed-config.js +372 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +61 -1
- package/dist/core.d.ts +8 -0
- package/dist/core.js +137 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.js +2 -2
- package/dist/mcp/hailer-clients.d.ts +15 -0
- package/dist/mcp/hailer-clients.js +100 -6
- package/dist/mcp/signal-handler.d.ts +16 -5
- package/dist/mcp/signal-handler.js +173 -122
- package/dist/mcp/tools/activity.js +9 -1
- package/dist/mcp/tools/bot-config.d.ts +184 -9
- package/dist/mcp/tools/bot-config.js +2177 -163
- package/dist/mcp/tools/giuseppe-tools.d.ts +21 -0
- package/dist/mcp/tools/giuseppe-tools.js +525 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +42 -1
- package/dist/mcp/utils/hailer-api-client.js +128 -2
- package/dist/mcp/webhook-handler.d.ts +87 -0
- package/dist/mcp/webhook-handler.js +343 -0
- package/dist/mcp/workspace-cache.d.ts +5 -0
- package/dist/mcp/workspace-cache.js +11 -0
- package/dist/mcp-server.js +55 -5
- package/dist/modules/bug-reports/giuseppe-agent.d.ts +58 -0
- package/dist/modules/bug-reports/giuseppe-agent.js +467 -0
- package/dist/modules/bug-reports/giuseppe-ai.d.ts +25 -1
- package/dist/modules/bug-reports/giuseppe-ai.js +133 -2
- package/dist/modules/bug-reports/giuseppe-bot.d.ts +3 -2
- package/dist/modules/bug-reports/giuseppe-bot.js +75 -36
- package/dist/modules/bug-reports/giuseppe-daemon.d.ts +80 -0
- package/dist/modules/bug-reports/giuseppe-daemon.js +617 -0
- package/dist/modules/bug-reports/giuseppe-files.d.ts +12 -0
- package/dist/modules/bug-reports/giuseppe-files.js +37 -0
- package/dist/modules/bug-reports/giuseppe-lsp.d.ts +113 -0
- package/dist/modules/bug-reports/giuseppe-lsp.js +485 -0
- package/dist/modules/bug-reports/index.d.ts +1 -0
- package/dist/modules/bug-reports/index.js +31 -29
- package/package.json +5 -4
|
@@ -68,12 +68,107 @@ Respond with JSON only:
|
|
|
68
68
|
return { classification: 'unclear', reason: 'Classification error' };
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Detect user intent from a message (replaces magic word detection)
|
|
73
|
+
* Returns the intent and confidence level
|
|
74
|
+
*/
|
|
75
|
+
async detectIntent(message, context) {
|
|
76
|
+
if (!this.anthropic) {
|
|
77
|
+
// Fallback to simple keyword matching
|
|
78
|
+
const lower = message.toLowerCase().trim();
|
|
79
|
+
if (context === 'classification') {
|
|
80
|
+
if (lower.includes('fix') && !lower.includes('don\'t') && !lower.includes('not')) {
|
|
81
|
+
return { intent: 'fix_it', confidence: 'medium', explanation: 'Contains "fix"' };
|
|
82
|
+
}
|
|
83
|
+
if (lower.includes('not a bug') || lower.includes('feature')) {
|
|
84
|
+
return { intent: 'not_a_bug', confidence: 'medium', explanation: 'Contains "not a bug" or "feature"' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (lower === 'approved' || lower === 'yes' || lower === 'ship it' || lower === 'deploy') {
|
|
89
|
+
return { intent: 'approved', confidence: 'medium', explanation: 'Approval keyword' };
|
|
90
|
+
}
|
|
91
|
+
if (lower === 'denied' || lower === 'no' || lower === 'revert' || lower === 'cancel') {
|
|
92
|
+
return { intent: 'denied', confidence: 'medium', explanation: 'Denial keyword' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { intent: 'unknown', confidence: 'low', explanation: 'No matching keywords' };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const contextDescription = context === 'classification'
|
|
99
|
+
? `Giuseppe classified a bug report and is waiting for the user to confirm:
|
|
100
|
+
- "fix_it" = User wants Giuseppe to proceed with fixing the bug
|
|
101
|
+
- "not_a_bug" = User says this isn't a bug, it's a feature request or not an issue
|
|
102
|
+
- "clarify" = User is asking a question or providing more info
|
|
103
|
+
- "unknown" = Can't determine intent`
|
|
104
|
+
: `Giuseppe has created a fix and is waiting for approval to publish:
|
|
105
|
+
- "approved" = User approves publishing to production
|
|
106
|
+
- "denied" = User rejects the fix or wants changes
|
|
107
|
+
- "retry" = User wants Giuseppe to try a different approach
|
|
108
|
+
- "question" = User is asking about the fix
|
|
109
|
+
- "unknown" = Can't determine intent`;
|
|
110
|
+
const prompt = `Detect the user's intent from this message.
|
|
111
|
+
|
|
112
|
+
Context: ${contextDescription}
|
|
113
|
+
|
|
114
|
+
User message: "${message}"
|
|
115
|
+
|
|
116
|
+
Respond with JSON only (no markdown):
|
|
117
|
+
{"intent": "...", "confidence": "high|medium|low", "explanation": "brief reason"}`;
|
|
118
|
+
const response = await this.anthropic.messages.create({
|
|
119
|
+
model: 'claude-haiku-4-5-20251001',
|
|
120
|
+
max_tokens: 150,
|
|
121
|
+
messages: [{ role: 'user', content: prompt }]
|
|
122
|
+
});
|
|
123
|
+
const content = response.content[0];
|
|
124
|
+
if (content.type !== 'text') {
|
|
125
|
+
return { intent: 'unknown', confidence: 'low', explanation: 'AI response error' };
|
|
126
|
+
}
|
|
127
|
+
const parsed = JSON.parse(content.text.replace(/```json\n?|\n?```/g, '').trim());
|
|
128
|
+
logger.info('Intent detected', {
|
|
129
|
+
message: message.substring(0, 50),
|
|
130
|
+
intent: parsed.intent,
|
|
131
|
+
confidence: parsed.confidence
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
intent: parsed.intent || 'unknown',
|
|
135
|
+
confidence: parsed.confidence || 'low',
|
|
136
|
+
explanation: parsed.explanation || ''
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
logger.error('Intent detection failed', { error, message: message.substring(0, 50) });
|
|
141
|
+
return { intent: 'unknown', confidence: 'low', explanation: 'Detection error' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extract keywords from bug description for LSP filtering
|
|
146
|
+
*/
|
|
147
|
+
extractKeywords(description) {
|
|
148
|
+
// Common technical terms to look for
|
|
149
|
+
const techTerms = [
|
|
150
|
+
'click', 'drag', 'drop', 'scroll', 'hover', 'focus', 'blur',
|
|
151
|
+
'render', 'update', 'load', 'save', 'delete', 'create',
|
|
152
|
+
'button', 'input', 'form', 'modal', 'menu', 'list', 'table',
|
|
153
|
+
'position', 'animation', 'transition', 'style', 'color',
|
|
154
|
+
'error', 'crash', 'freeze', 'slow', 'lag', 'broken'
|
|
155
|
+
];
|
|
156
|
+
const words = description.toLowerCase()
|
|
157
|
+
.replace(/[^\w\s]/g, ' ')
|
|
158
|
+
.split(/\s+/)
|
|
159
|
+
.filter(w => w.length > 2);
|
|
160
|
+
// Find matching tech terms
|
|
161
|
+
const matchedTerms = techTerms.filter(term => words.some(w => w.includes(term) || term.includes(w)));
|
|
162
|
+
// Also include unique words from description (nouns/verbs likely)
|
|
163
|
+
const uniqueWords = words.filter(w => w.length > 4 && !['the', 'and', 'but', 'for', 'not', 'with', 'this', 'that', 'when', 'have', 'from'].includes(w));
|
|
164
|
+
return [...new Set([...matchedTerms, ...uniqueWords])].slice(0, 10);
|
|
165
|
+
}
|
|
71
166
|
/**
|
|
72
167
|
* Analyze bug and generate fix plan using Claude (two-phase approach)
|
|
73
168
|
* Phase 1: Send file list -> Claude picks relevant files
|
|
74
169
|
* Phase 2: Send those files -> Claude generates fix
|
|
75
170
|
*/
|
|
76
|
-
async analyzeAndPlanFix(bug, app, allFiles, readFiles) {
|
|
171
|
+
async analyzeAndPlanFix(bug, app, allFiles, readFiles, runLspAnalysis) {
|
|
77
172
|
if (!this.anthropic) {
|
|
78
173
|
logger.warn('Anthropic client not initialized - cannot generate fix');
|
|
79
174
|
return null;
|
|
@@ -120,9 +215,45 @@ Return ONLY a JSON array of file paths, maximum 10 files. Example: ["src/App.tsx
|
|
|
120
215
|
relevantFiles = allFiles.filter(f => f.startsWith('src/')).slice(0, 5);
|
|
121
216
|
}
|
|
122
217
|
logger.info('Phase 1 complete: Files to examine', { files: relevantFiles });
|
|
218
|
+
// Phase 1.5: Run TARGETED LSP analysis on relevant files only
|
|
219
|
+
// Extract keywords from bug description for filtering
|
|
220
|
+
const bugKeywords = this.extractKeywords(bug.description);
|
|
221
|
+
logger.info('Extracted bug keywords for LSP filtering', { keywords: bugKeywords });
|
|
222
|
+
let lspFindings = null;
|
|
223
|
+
if (runLspAnalysis) {
|
|
224
|
+
lspFindings = await runLspAnalysis(relevantFiles, bugKeywords);
|
|
225
|
+
logger.info('Targeted LSP analysis complete', {
|
|
226
|
+
files: relevantFiles.length,
|
|
227
|
+
unusedProps: lspFindings.unusedProps.length,
|
|
228
|
+
issues: lspFindings.issues.length
|
|
229
|
+
});
|
|
230
|
+
}
|
|
123
231
|
// Phase 2: Read selected files and generate fix
|
|
124
232
|
const sourceFiles = await readFiles(relevantFiles);
|
|
125
233
|
logger.info('Phase 2: Generating fix', { filesRead: sourceFiles.length });
|
|
234
|
+
// Build LSP findings section if available
|
|
235
|
+
let lspSection = '';
|
|
236
|
+
if (lspFindings && (lspFindings.unusedProps.length > 0 || lspFindings.issues.length > 0)) {
|
|
237
|
+
lspSection = '\n\n=== LSP CODE ANALYSIS (HIGH PRIORITY) ===\n';
|
|
238
|
+
lspSection += 'IMPORTANT: These are REAL code issues detected by static analysis. Fix these FIRST.\n\n';
|
|
239
|
+
if (lspFindings.unusedProps.length > 0) {
|
|
240
|
+
lspSection += 'UNUSED PROPS (declared in interface but never used in component):\n';
|
|
241
|
+
lspFindings.unusedProps.forEach(p => {
|
|
242
|
+
lspSection += `- ${p.file}:${p.line} - prop "${p.name}" is declared but never destructured/used\n`;
|
|
243
|
+
// Add hint if prop name matches bug keywords
|
|
244
|
+
if (bug.description.toLowerCase().includes(p.name.toLowerCase())) {
|
|
245
|
+
lspSection += ` ^ THIS PROP NAME MATCHES THE BUG DESCRIPTION - VERY LIKELY THE ROOT CAUSE!\n`;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (lspFindings.issues.length > 0) {
|
|
250
|
+
lspSection += '\nDEAD CODE (functions/variables declared but never called):\n';
|
|
251
|
+
lspFindings.issues.forEach(i => {
|
|
252
|
+
lspSection += `- ${i.file}:${i.line} - ${i.message}\n`;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
lspSection += '\nThe fix should USE these unused props/functions, not add workaround code.\n';
|
|
256
|
+
}
|
|
126
257
|
const phase2Prompt = `
|
|
127
258
|
Bug Report:
|
|
128
259
|
- Title: ${bug.name}
|
|
@@ -131,7 +262,7 @@ ${bug.stepsToReproduce ? `- Steps to reproduce: ${bug.stepsToReproduce}` : ''}
|
|
|
131
262
|
${bug.expectedBehavior ? `- Expected: ${bug.expectedBehavior}` : ''}
|
|
132
263
|
${bug.actualBehavior ? `- Actual: ${bug.actualBehavior}` : ''}
|
|
133
264
|
- Priority: ${bug.priority || 'unknown'}
|
|
134
|
-
|
|
265
|
+
${lspSection}
|
|
135
266
|
App: ${app.name}
|
|
136
267
|
Project path: ${app.projectPath}
|
|
137
268
|
|
|
@@ -18,6 +18,7 @@ export declare class GiuseppeBot {
|
|
|
18
18
|
private ai;
|
|
19
19
|
private git;
|
|
20
20
|
private files;
|
|
21
|
+
private lsp;
|
|
21
22
|
private appsRegistry;
|
|
22
23
|
constructor(userContext: UserContext, config: BugReportsConfig, monitor: BugMonitor);
|
|
23
24
|
/**
|
|
@@ -38,9 +39,9 @@ export declare class GiuseppeBot {
|
|
|
38
39
|
*/
|
|
39
40
|
proceedWithFix(bug: BugReport): Promise<FixResult>;
|
|
40
41
|
/**
|
|
41
|
-
* Handle a message in a bug discussion -
|
|
42
|
+
* Handle a message in a bug discussion - uses LLM to understand intent
|
|
42
43
|
*/
|
|
43
|
-
handleDiscussionMessage(discussionId: string, message: string,
|
|
44
|
+
handleDiscussionMessage(discussionId: string, message: string, _senderId: string): Promise<{
|
|
44
45
|
approved: boolean;
|
|
45
46
|
published: boolean;
|
|
46
47
|
retrying?: boolean;
|
|
@@ -20,12 +20,9 @@ const app_scaffold_1 = require("../../mcp/tools/app-scaffold");
|
|
|
20
20
|
const giuseppe_ai_1 = require("./giuseppe-ai");
|
|
21
21
|
const giuseppe_git_1 = require("./giuseppe-git");
|
|
22
22
|
const giuseppe_files_1 = require("./giuseppe-files");
|
|
23
|
+
const giuseppe_lsp_1 = require("./giuseppe-lsp");
|
|
24
|
+
const discussion_lock_1 = require("../../lib/discussion-lock");
|
|
23
25
|
const logger = (0, logger_1.createLogger)({ component: 'giuseppe-bot' });
|
|
24
|
-
// Magic words for approval flow
|
|
25
|
-
const MAGIC_WORD_APPROVED = 'approved';
|
|
26
|
-
const MAGIC_WORD_DENIED = 'denied';
|
|
27
|
-
const MAGIC_WORD_FIX_IT = 'fix it';
|
|
28
|
-
const MAGIC_WORD_NOT_A_BUG = 'not a bug';
|
|
29
26
|
// Session Log (Context Log) field IDs for logging
|
|
30
27
|
const SESSION_LOG_CONFIG = {
|
|
31
28
|
workflowId: '695784898d347a6c707ee397',
|
|
@@ -44,6 +41,7 @@ class GiuseppeBot {
|
|
|
44
41
|
ai;
|
|
45
42
|
git;
|
|
46
43
|
files;
|
|
44
|
+
lsp;
|
|
47
45
|
appsRegistry = new Map();
|
|
48
46
|
constructor(userContext, config, monitor) {
|
|
49
47
|
this.userContext = userContext;
|
|
@@ -54,6 +52,7 @@ class GiuseppeBot {
|
|
|
54
52
|
this.ai = new giuseppe_ai_1.GiuseppeAI(apiKey);
|
|
55
53
|
this.git = new giuseppe_git_1.GiuseppeGit();
|
|
56
54
|
this.files = new giuseppe_files_1.GiuseppeFiles(process.env.DEV_APPS_PATH);
|
|
55
|
+
this.lsp = new giuseppe_lsp_1.GiuseppeLsp();
|
|
57
56
|
// Load apps registry from config
|
|
58
57
|
if (config.appsRegistry) {
|
|
59
58
|
for (const [appId, entry] of Object.entries(config.appsRegistry)) {
|
|
@@ -130,6 +129,10 @@ class GiuseppeBot {
|
|
|
130
129
|
bugName: bug.name,
|
|
131
130
|
appId: bug.appId
|
|
132
131
|
});
|
|
132
|
+
// Acquire lock on this discussion to prevent other bots (Orchestrator) from responding
|
|
133
|
+
if (bug.discussionId) {
|
|
134
|
+
(0, discussion_lock_1.acquireDiscussionLock)(bug.discussionId, 'giuseppe', 10 * 60 * 1000); // 10 minute lock
|
|
135
|
+
}
|
|
133
136
|
const result = {
|
|
134
137
|
success: false,
|
|
135
138
|
summary: '',
|
|
@@ -236,9 +239,28 @@ class GiuseppeBot {
|
|
|
236
239
|
}
|
|
237
240
|
result.log?.push(`Found app project: ${app.projectPath}`);
|
|
238
241
|
await this.reportProgress(bug, `📁 Found app project: ${app.name}`);
|
|
242
|
+
// Note: LSP analysis now runs AFTER Phase 1 identifies relevant files
|
|
243
|
+
// This is more efficient - we only analyze files related to the bug
|
|
239
244
|
// 2. Analyze and generate fix
|
|
240
245
|
const allFiles = await this.files.scanSourceFiles(app.projectPath);
|
|
241
|
-
|
|
246
|
+
// Create LSP analysis callback - runs TARGETED analysis on specific files
|
|
247
|
+
const runLspAnalysis = async (files, keywords) => {
|
|
248
|
+
logger.info('Running targeted LSP analysis', { files: files.length, keywords });
|
|
249
|
+
const analysis = await this.lsp.analyzeRelevantFiles(app.projectPath, files, keywords);
|
|
250
|
+
return {
|
|
251
|
+
unusedProps: (analysis.unusedProps || []).map((p) => ({
|
|
252
|
+
name: p.name,
|
|
253
|
+
line: p.line,
|
|
254
|
+
file: p.file || 'unknown'
|
|
255
|
+
})),
|
|
256
|
+
issues: (analysis.issues || []).map(i => ({
|
|
257
|
+
file: i.file,
|
|
258
|
+
line: i.line,
|
|
259
|
+
message: i.message
|
|
260
|
+
}))
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
const fixPlan = await this.ai.analyzeAndPlanFix(bug, app, allFiles, (paths) => this.files.readSelectedFiles(app.projectPath, paths), runLspAnalysis);
|
|
242
264
|
if (!fixPlan) {
|
|
243
265
|
result.summary = 'Could not generate fix plan';
|
|
244
266
|
result.log?.push('Fix plan generation failed');
|
|
@@ -283,6 +305,12 @@ class GiuseppeBot {
|
|
|
283
305
|
await this.reportProgress(bug, `✏️ Applied fixes to ${applyResult.files.length} file(s)`);
|
|
284
306
|
const buildResult = await this.buildAndTest(app);
|
|
285
307
|
if (buildResult.success) {
|
|
308
|
+
// Run LSP analysis to check for remaining issues
|
|
309
|
+
const lspResult = await this.lsp.validateFix(app.projectPath);
|
|
310
|
+
if (!lspResult.valid) {
|
|
311
|
+
result.log?.push(`LSP found ${lspResult.errors.length} issue(s)`);
|
|
312
|
+
await this.reportProgress(bug, `⚠️ Build passed but LSP found issues:\n${lspResult.errors.slice(0, 3).join('\n')}`);
|
|
313
|
+
}
|
|
286
314
|
fixSuccess = true;
|
|
287
315
|
result.log?.push(`Build successful (attempt ${attempt})`);
|
|
288
316
|
await this.reportProgress(bug, '✅ Build successful');
|
|
@@ -366,56 +394,64 @@ ${currentFixPlan.explanation}
|
|
|
366
394
|
}
|
|
367
395
|
}
|
|
368
396
|
/**
|
|
369
|
-
* Handle a message in a bug discussion -
|
|
397
|
+
* Handle a message in a bug discussion - uses LLM to understand intent
|
|
370
398
|
*/
|
|
371
|
-
async handleDiscussionMessage(discussionId, message,
|
|
372
|
-
// Normalize: replace non-breaking spaces (0xA0) with regular spaces (0x20)
|
|
373
|
-
// Hailer chat may send NBSP instead of regular spaces
|
|
374
|
-
const messageLower = message.toLowerCase().replace(/\u00A0/g, ' ').trim();
|
|
399
|
+
async handleDiscussionMessage(discussionId, message, _senderId) {
|
|
375
400
|
logger.debug('handleDiscussionMessage called', {
|
|
376
401
|
discussionId,
|
|
377
402
|
message: message.substring(0, 50),
|
|
378
403
|
pendingClassificationsSize: pending_classification_registry_1.pendingClassificationRegistry.size,
|
|
379
404
|
hasPendingForDiscussion: pending_classification_registry_1.pendingClassificationRegistry.has(discussionId)
|
|
380
405
|
});
|
|
381
|
-
// Check for pending classification first (waiting for
|
|
406
|
+
// Check for pending classification first (waiting for user to confirm fix or mark as not-a-bug)
|
|
382
407
|
const pendingClassification = pending_classification_registry_1.pendingClassificationRegistry.get(discussionId);
|
|
383
408
|
if (pendingClassification) {
|
|
384
|
-
//
|
|
385
|
-
const
|
|
386
|
-
logger.debug('
|
|
409
|
+
// Use LLM to understand user intent (replaces hardcoded magic words)
|
|
410
|
+
const { intent, confidence } = await this.ai.detectIntent(message, 'classification');
|
|
411
|
+
logger.debug('Classification intent detected', {
|
|
387
412
|
discussionId,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
magicWordHex: hexDump(MAGIC_WORD_NOT_A_BUG),
|
|
392
|
-
isFixIt: messageLower === MAGIC_WORD_FIX_IT || messageLower === 'fix',
|
|
393
|
-
isNotABug: messageLower === MAGIC_WORD_NOT_A_BUG,
|
|
394
|
-
includesNotABug: messageLower.includes('not a bug'),
|
|
395
|
-
includesFeature: messageLower.includes('feature')
|
|
413
|
+
message: message.substring(0, 50),
|
|
414
|
+
intent,
|
|
415
|
+
confidence
|
|
396
416
|
});
|
|
397
|
-
if (
|
|
398
|
-
logger.debug('
|
|
417
|
+
if (intent === 'fix_it' && confidence !== 'low') {
|
|
418
|
+
logger.debug('User wants to fix - calling handleFixItConfirmation');
|
|
399
419
|
return await this.handleFixItConfirmation(discussionId, pendingClassification);
|
|
400
420
|
}
|
|
401
|
-
if (
|
|
402
|
-
logger.debug('
|
|
421
|
+
if (intent === 'not_a_bug' && confidence !== 'low') {
|
|
422
|
+
logger.debug('User says not a bug - calling handleNotABugConfirmation');
|
|
403
423
|
return await this.handleNotABugConfirmation(discussionId, pendingClassification);
|
|
404
424
|
}
|
|
405
|
-
|
|
406
|
-
|
|
425
|
+
// Unknown or clarify intent - ignore and wait for clearer response
|
|
426
|
+
logger.debug('Unclear intent - waiting for clearer response', { intent, confidence });
|
|
407
427
|
return { approved: false, published: false };
|
|
408
428
|
}
|
|
409
|
-
// Check for pending fix (waiting for
|
|
429
|
+
// Check for pending fix (waiting for approval to publish)
|
|
410
430
|
const pendingFix = pending_fix_registry_1.pendingFixRegistry.get(discussionId);
|
|
411
431
|
if (!pendingFix) {
|
|
412
432
|
return { approved: false, published: false };
|
|
413
433
|
}
|
|
414
|
-
//
|
|
415
|
-
|
|
434
|
+
// Use LLM to understand approval intent
|
|
435
|
+
const { intent, confidence } = await this.ai.detectIntent(message, 'approval');
|
|
436
|
+
logger.debug('Approval intent detected', {
|
|
437
|
+
discussionId,
|
|
438
|
+
message: message.substring(0, 50),
|
|
439
|
+
intent,
|
|
440
|
+
confidence
|
|
441
|
+
});
|
|
442
|
+
if (intent === 'approved' && confidence !== 'low') {
|
|
416
443
|
return await this.handleApproval(discussionId, pendingFix);
|
|
417
444
|
}
|
|
418
|
-
|
|
445
|
+
if (intent === 'denied' && confidence !== 'low') {
|
|
446
|
+
// Handle denial - revert changes
|
|
447
|
+
return await this.handleDenial(discussionId, pendingFix);
|
|
448
|
+
}
|
|
449
|
+
if (intent === 'retry' && confidence !== 'low') {
|
|
450
|
+
// User wants to try again - not implemented yet
|
|
451
|
+
await this.sendDiscussionMessage(discussionId, "🔄 Retry is not implemented yet. Please say 'approved' to publish or 'denied' to cancel.");
|
|
452
|
+
return { approved: false, published: false };
|
|
453
|
+
}
|
|
454
|
+
// Unknown intent - let HAL or user clarify
|
|
419
455
|
return { approved: false, published: false };
|
|
420
456
|
}
|
|
421
457
|
/**
|
|
@@ -426,11 +462,14 @@ ${currentFixPlan.explanation}
|
|
|
426
462
|
// Remove from pending classifications
|
|
427
463
|
pending_classification_registry_1.pendingClassificationRegistry.unregister(discussionId);
|
|
428
464
|
await this.sendDiscussionMessage(discussionId, `✅ Got it! Starting bug fix...`);
|
|
429
|
-
// Proceed with the actual fix
|
|
465
|
+
// Proceed with the actual fix (creates commit, stores pending fix awaiting approval)
|
|
430
466
|
const result = await this.proceedWithFix(pending.bug);
|
|
467
|
+
// Return false for both - the CLASSIFICATION was approved ("fix it"),
|
|
468
|
+
// but the FIX itself is not yet approved (waiting for "approved" message)
|
|
469
|
+
// The fix was applied locally but NOT published to production
|
|
431
470
|
return {
|
|
432
|
-
approved:
|
|
433
|
-
published:
|
|
471
|
+
approved: false, // Fix not approved yet, just classification
|
|
472
|
+
published: false, // Not published, just committed locally
|
|
434
473
|
error: result.success ? undefined : result.summary
|
|
435
474
|
};
|
|
436
475
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Giuseppe Daemon - LLM-Driven Bug Fixer Agent
|
|
3
|
+
*
|
|
4
|
+
* A fully autonomous AI agent for fixing bugs in Hailer apps.
|
|
5
|
+
* Unlike the old GiuseppeBot with hardcoded magic words, this daemon:
|
|
6
|
+
* - Understands natural language (no "fix it" / "approved" keywords)
|
|
7
|
+
* - Reasons about problems (git conflicts, build failures)
|
|
8
|
+
* - Decides when to ask for approval vs proceed
|
|
9
|
+
* - Has tools for all bug-fixing operations
|
|
10
|
+
*/
|
|
11
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
12
|
+
import { ChatAgentDaemon, ChatAgentDaemonConfig, IncomingMessage } from "../../client/agents/base";
|
|
13
|
+
import { HailerSignal } from "../../mcp/signal-handler";
|
|
14
|
+
import { ToolInput } from "../../client/types";
|
|
15
|
+
import type { BugMonitor } from "./bug-monitor";
|
|
16
|
+
import type { UserContext } from "../../mcp/UserContextCache";
|
|
17
|
+
export interface GiuseppeDaemonConfig extends ChatAgentDaemonConfig {
|
|
18
|
+
/** Bug monitor for phase transitions */
|
|
19
|
+
bugMonitor: BugMonitor;
|
|
20
|
+
/** User context for API access */
|
|
21
|
+
userContext: UserContext;
|
|
22
|
+
/** Base path for app projects */
|
|
23
|
+
appsBasePath: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class GiuseppeDaemon extends ChatAgentDaemon {
|
|
26
|
+
private giuseppeLogger;
|
|
27
|
+
private bugMonitor;
|
|
28
|
+
private userContext;
|
|
29
|
+
private appsBasePath;
|
|
30
|
+
private ai;
|
|
31
|
+
private git;
|
|
32
|
+
private files;
|
|
33
|
+
private lsp;
|
|
34
|
+
private bugDiscussions;
|
|
35
|
+
constructor(config: GiuseppeDaemonConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Register a bug discussion so Giuseppe knows to respond to it
|
|
38
|
+
*/
|
|
39
|
+
registerBugDiscussion(discussionId: string, bugId: string, bugName: string, workflowId: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Unregister a bug discussion (bug resolved/closed)
|
|
42
|
+
*/
|
|
43
|
+
unregisterBugDiscussion(discussionId: string): void;
|
|
44
|
+
protected getAgentName(): {
|
|
45
|
+
firstName: string;
|
|
46
|
+
lastName: string;
|
|
47
|
+
};
|
|
48
|
+
protected getAgentDescription(): string;
|
|
49
|
+
protected getPositionDetails(): {
|
|
50
|
+
name: string;
|
|
51
|
+
purpose: string;
|
|
52
|
+
personaTone: string;
|
|
53
|
+
coreCapabilities: string;
|
|
54
|
+
boundaries: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Only respond to messages in registered bug discussions
|
|
58
|
+
*/
|
|
59
|
+
protected extractIncomingMessage(signal: HailerSignal): Promise<IncomingMessage | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Giuseppe's tools for bug fixing
|
|
62
|
+
*/
|
|
63
|
+
protected getToolWhitelist(): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Custom tools for bug fixing operations
|
|
66
|
+
*/
|
|
67
|
+
protected getTools(): Anthropic.Tool[];
|
|
68
|
+
/**
|
|
69
|
+
* Execute Giuseppe's custom tools
|
|
70
|
+
*/
|
|
71
|
+
protected executeCustomTool(name: string, rawInput: ToolInput): Promise<{
|
|
72
|
+
success: boolean;
|
|
73
|
+
result: any;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* System prompt for Giuseppe - natural conversation about bug fixing
|
|
77
|
+
*/
|
|
78
|
+
protected getSystemPrompt(): string;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=giuseppe-daemon.d.ts.map
|