@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.
Files changed (112) hide show
  1. package/.claude/agents/agent-giuseppe-app-builder.md +7 -6
  2. package/.claude/agents/agent-lars-code-inspector.md +26 -14
  3. package/dist/agents/bot-manager.d.ts +48 -0
  4. package/dist/agents/bot-manager.js +254 -0
  5. package/dist/agents/factory.d.ts +150 -0
  6. package/dist/agents/factory.js +650 -0
  7. package/dist/agents/giuseppe/ai.d.ts +83 -0
  8. package/dist/agents/giuseppe/ai.js +466 -0
  9. package/dist/agents/giuseppe/bot.d.ts +110 -0
  10. package/dist/agents/giuseppe/bot.js +780 -0
  11. package/dist/agents/giuseppe/config.d.ts +25 -0
  12. package/dist/agents/giuseppe/config.js +227 -0
  13. package/dist/agents/giuseppe/files.d.ts +52 -0
  14. package/dist/agents/giuseppe/files.js +338 -0
  15. package/dist/agents/giuseppe/git.d.ts +48 -0
  16. package/dist/agents/giuseppe/git.js +298 -0
  17. package/dist/agents/giuseppe/index.d.ts +97 -0
  18. package/dist/agents/giuseppe/index.js +258 -0
  19. package/dist/agents/giuseppe/lsp.d.ts +113 -0
  20. package/dist/agents/giuseppe/lsp.js +485 -0
  21. package/dist/agents/giuseppe/monitor.d.ts +118 -0
  22. package/dist/agents/giuseppe/monitor.js +621 -0
  23. package/dist/agents/giuseppe/prompt.d.ts +5 -0
  24. package/dist/agents/giuseppe/prompt.js +94 -0
  25. package/dist/agents/giuseppe/registries/pending-classification.d.ts +28 -0
  26. package/dist/agents/giuseppe/registries/pending-classification.js +50 -0
  27. package/dist/agents/giuseppe/registries/pending-fix.d.ts +30 -0
  28. package/dist/agents/giuseppe/registries/pending-fix.js +42 -0
  29. package/dist/agents/giuseppe/registries/pending.d.ts +27 -0
  30. package/dist/agents/giuseppe/registries/pending.js +49 -0
  31. package/dist/agents/giuseppe/specialist.d.ts +47 -0
  32. package/dist/agents/giuseppe/specialist.js +237 -0
  33. package/dist/agents/giuseppe/types.d.ts +123 -0
  34. package/dist/agents/giuseppe/types.js +9 -0
  35. package/dist/agents/hailer-expert/index.d.ts +8 -0
  36. package/dist/agents/hailer-expert/index.js +14 -0
  37. package/dist/agents/hal/daemon.d.ts +142 -0
  38. package/dist/agents/hal/daemon.js +1103 -0
  39. package/dist/agents/hal/definitions.d.ts +55 -0
  40. package/dist/agents/hal/definitions.js +263 -0
  41. package/dist/agents/hal/index.d.ts +3 -0
  42. package/dist/agents/hal/index.js +8 -0
  43. package/dist/agents/index.d.ts +18 -0
  44. package/dist/agents/index.js +48 -0
  45. package/dist/agents/shared/base.d.ts +216 -0
  46. package/dist/agents/shared/base.js +846 -0
  47. package/dist/agents/shared/services/agent-registry.d.ts +107 -0
  48. package/dist/agents/shared/services/agent-registry.js +629 -0
  49. package/dist/agents/shared/services/conversation-manager.d.ts +50 -0
  50. package/dist/agents/shared/services/conversation-manager.js +136 -0
  51. package/dist/agents/shared/services/mcp-client.d.ts +56 -0
  52. package/dist/agents/shared/services/mcp-client.js +124 -0
  53. package/dist/agents/shared/services/message-classifier.d.ts +37 -0
  54. package/dist/agents/shared/services/message-classifier.js +187 -0
  55. package/dist/agents/shared/services/message-formatter.d.ts +89 -0
  56. package/dist/agents/shared/services/message-formatter.js +371 -0
  57. package/dist/agents/shared/services/session-logger.d.ts +106 -0
  58. package/dist/agents/shared/services/session-logger.js +446 -0
  59. package/dist/agents/shared/services/tool-executor.d.ts +41 -0
  60. package/dist/agents/shared/services/tool-executor.js +169 -0
  61. package/dist/agents/shared/services/workspace-schema-cache.d.ts +125 -0
  62. package/dist/agents/shared/services/workspace-schema-cache.js +578 -0
  63. package/dist/agents/shared/specialist.d.ts +91 -0
  64. package/dist/agents/shared/specialist.js +399 -0
  65. package/dist/agents/shared/tool-schema-loader.d.ts +62 -0
  66. package/dist/agents/shared/tool-schema-loader.js +232 -0
  67. package/dist/agents/shared/types.d.ts +327 -0
  68. package/dist/agents/shared/types.js +121 -0
  69. package/dist/app.js +21 -4
  70. package/dist/cli.js +0 -0
  71. package/dist/client/agents/orchestrator.d.ts +1 -0
  72. package/dist/client/agents/orchestrator.js +12 -1
  73. package/dist/commands/seed-config.d.ts +9 -0
  74. package/dist/commands/seed-config.js +372 -0
  75. package/dist/config.d.ts +10 -0
  76. package/dist/config.js +61 -1
  77. package/dist/core.d.ts +8 -0
  78. package/dist/core.js +137 -6
  79. package/dist/lib/discussion-lock.d.ts +42 -0
  80. package/dist/lib/discussion-lock.js +110 -0
  81. package/dist/mcp/UserContextCache.js +2 -2
  82. package/dist/mcp/hailer-clients.d.ts +15 -0
  83. package/dist/mcp/hailer-clients.js +100 -6
  84. package/dist/mcp/signal-handler.d.ts +16 -5
  85. package/dist/mcp/signal-handler.js +173 -122
  86. package/dist/mcp/tools/activity.js +9 -1
  87. package/dist/mcp/tools/bot-config.d.ts +184 -9
  88. package/dist/mcp/tools/bot-config.js +2177 -163
  89. package/dist/mcp/tools/giuseppe-tools.d.ts +21 -0
  90. package/dist/mcp/tools/giuseppe-tools.js +525 -0
  91. package/dist/mcp/utils/hailer-api-client.d.ts +42 -1
  92. package/dist/mcp/utils/hailer-api-client.js +128 -2
  93. package/dist/mcp/webhook-handler.d.ts +87 -0
  94. package/dist/mcp/webhook-handler.js +343 -0
  95. package/dist/mcp/workspace-cache.d.ts +5 -0
  96. package/dist/mcp/workspace-cache.js +11 -0
  97. package/dist/mcp-server.js +55 -5
  98. package/dist/modules/bug-reports/giuseppe-agent.d.ts +58 -0
  99. package/dist/modules/bug-reports/giuseppe-agent.js +467 -0
  100. package/dist/modules/bug-reports/giuseppe-ai.d.ts +25 -1
  101. package/dist/modules/bug-reports/giuseppe-ai.js +133 -2
  102. package/dist/modules/bug-reports/giuseppe-bot.d.ts +3 -2
  103. package/dist/modules/bug-reports/giuseppe-bot.js +75 -36
  104. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +80 -0
  105. package/dist/modules/bug-reports/giuseppe-daemon.js +617 -0
  106. package/dist/modules/bug-reports/giuseppe-files.d.ts +12 -0
  107. package/dist/modules/bug-reports/giuseppe-files.js +37 -0
  108. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +113 -0
  109. package/dist/modules/bug-reports/giuseppe-lsp.js +485 -0
  110. package/dist/modules/bug-reports/index.d.ts +1 -0
  111. package/dist/modules/bug-reports/index.js +31 -29
  112. 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 - state machine for approval flow
42
+ * Handle a message in a bug discussion - uses LLM to understand intent
42
43
  */
43
- handleDiscussionMessage(discussionId: string, message: string, senderId: string): Promise<{
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
- const fixPlan = await this.ai.analyzeAndPlanFix(bug, app, allFiles, (paths) => this.files.readSelectedFiles(app.projectPath, paths));
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 - state machine for approval flow
397
+ * Handle a message in a bug discussion - uses LLM to understand intent
370
398
  */
371
- async handleDiscussionMessage(discussionId, message, senderId) {
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 "fix it" or "not a bug")
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
- // Hex dump for debugging character encoding issues
385
- const hexDump = (str) => Array.from(str).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ');
386
- logger.debug('Checking message against magic words', {
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
- messageLower,
389
- messageLowerHex: hexDump(messageLower),
390
- MAGIC_WORD_NOT_A_BUG,
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 (messageLower === MAGIC_WORD_FIX_IT || messageLower === 'fix') {
398
- logger.debug('Matched fix it - calling handleFixItConfirmation');
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 (messageLower === MAGIC_WORD_NOT_A_BUG || messageLower.includes('not a bug') || messageLower.includes('feature')) {
402
- logger.debug('Matched not a bug - calling handleNotABugConfirmation');
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
- logger.debug('No magic word match - ignoring message');
406
- // Other messages while waiting for classification - ignore
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 "approved" or "denied")
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
- // Giuseppe ONLY handles "approved" - HAL handles everything else (denial, explanations)
415
- if (messageLower === MAGIC_WORD_APPROVED) {
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
- // All other messages: return false, let HAL handle
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: true,
433
- published: result.success,
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