@hailer/mcp 0.1.11 → 0.1.12
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/settings.json +12 -0
- package/CLAUDE.md +37 -1
- package/ai-hub/dist/assets/index-8ce6041d.css +1 -0
- package/ai-hub/dist/assets/index-930f01ca.js +348 -0
- package/ai-hub/dist/index.html +15 -0
- package/ai-hub/dist/manifest.json +14 -0
- package/ai-hub/dist/vite.svg +1 -0
- package/dist/app.js +5 -0
- package/dist/client/agents/base.d.ts +5 -0
- package/dist/client/agents/base.js +9 -2
- package/dist/client/agents/definitions.js +85 -0
- package/dist/client/agents/orchestrator.d.ts +21 -0
- package/dist/client/agents/orchestrator.js +292 -1
- package/dist/client/bot-entrypoint.d.ts +7 -0
- package/dist/client/bot-entrypoint.js +103 -0
- package/dist/client/bot-runner.d.ts +35 -0
- package/dist/client/bot-runner.js +188 -0
- package/dist/client/factory.d.ts +4 -0
- package/dist/client/factory.js +10 -0
- package/dist/client/server.d.ts +8 -0
- package/dist/client/server.js +251 -0
- package/dist/client/types.d.ts +29 -0
- package/dist/client/types.js +4 -1
- package/dist/core.d.ts +3 -0
- package/dist/core.js +72 -0
- package/dist/mcp/hailer-clients.d.ts +4 -0
- package/dist/mcp/hailer-clients.js +16 -1
- package/dist/mcp/tools/app-scaffold.js +127 -5
- package/dist/mcp/tools/bot-config.d.ts +78 -0
- package/dist/mcp/tools/bot-config.js +442 -0
- package/dist/mcp-server.js +109 -1
- package/dist/modules/bug-reports/bug-config.d.ts +25 -0
- package/dist/modules/bug-reports/bug-config.js +187 -0
- package/dist/modules/bug-reports/bug-monitor.d.ts +108 -0
- package/dist/modules/bug-reports/bug-monitor.js +510 -0
- package/dist/modules/bug-reports/giuseppe-ai.d.ts +59 -0
- package/dist/modules/bug-reports/giuseppe-ai.js +335 -0
- package/dist/modules/bug-reports/giuseppe-bot.d.ts +109 -0
- package/dist/modules/bug-reports/giuseppe-bot.js +765 -0
- package/dist/modules/bug-reports/giuseppe-files.d.ts +52 -0
- package/dist/modules/bug-reports/giuseppe-files.js +338 -0
- package/dist/modules/bug-reports/giuseppe-git.d.ts +48 -0
- package/dist/modules/bug-reports/giuseppe-git.js +298 -0
- package/dist/modules/bug-reports/giuseppe-prompt.d.ts +5 -0
- package/dist/modules/bug-reports/giuseppe-prompt.js +94 -0
- package/dist/modules/bug-reports/index.d.ts +76 -0
- package/dist/modules/bug-reports/index.js +213 -0
- package/dist/modules/bug-reports/pending-classification-registry.d.ts +28 -0
- package/dist/modules/bug-reports/pending-classification-registry.js +50 -0
- package/dist/modules/bug-reports/pending-fix-registry.d.ts +30 -0
- package/dist/modules/bug-reports/pending-fix-registry.js +42 -0
- package/dist/modules/bug-reports/pending-registry.d.ts +27 -0
- package/dist/modules/bug-reports/pending-registry.js +49 -0
- package/dist/modules/bug-reports/types.d.ts +123 -0
- package/dist/modules/bug-reports/types.js +9 -0
- package/dist/services/bug-monitor.d.ts +23 -0
- package/dist/services/bug-monitor.js +275 -0
- package/lineup-manager/dist/assets/index-b30c809f.js +600 -0
- package/lineup-manager/dist/index.html +1 -1
- package/lineup-manager/dist/manifest.json +5 -5
- package/package.json +6 -2
- package/lineup-manager/dist/assets/index-e168f265.js +0 -600
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bug Reports Module - Bug Monitor Service
|
|
4
|
+
*
|
|
5
|
+
* Polls for new bug reports and dispatches them for processing.
|
|
6
|
+
* No hardcoded IDs - discovers workflow by name pattern.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.BugMonitor = void 0;
|
|
10
|
+
const logger_1 = require("../../lib/logger");
|
|
11
|
+
const bug_config_1 = require("./bug-config");
|
|
12
|
+
const bot_config_1 = require("../../mcp/tools/bot-config");
|
|
13
|
+
const hailer_clients_1 = require("../../mcp/hailer-clients");
|
|
14
|
+
const logger = (0, logger_1.createLogger)({ component: 'bug-monitor' });
|
|
15
|
+
class BugMonitor {
|
|
16
|
+
userContext;
|
|
17
|
+
interval;
|
|
18
|
+
unsubscribeFromSignals;
|
|
19
|
+
config;
|
|
20
|
+
discovery;
|
|
21
|
+
processedBugIds = new Map(); // id -> timestamp
|
|
22
|
+
processedMessageIds = new Map(); // id -> timestamp
|
|
23
|
+
static PROCESSED_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
24
|
+
handlers = [];
|
|
25
|
+
messageHandlers = [];
|
|
26
|
+
giuseppeDisabledHandlers = [];
|
|
27
|
+
watchedDiscussions = new Map(); // discussionId -> watch start timestamp
|
|
28
|
+
botUserIds = new Set(); // Bot user IDs to ignore
|
|
29
|
+
constructor(userContext) {
|
|
30
|
+
this.userContext = userContext;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register a bot user ID to ignore messages from
|
|
34
|
+
*/
|
|
35
|
+
registerBotUser(userId) {
|
|
36
|
+
this.botUserIds.add(userId);
|
|
37
|
+
logger.debug('Registered bot user to ignore', { userId });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a handler for new bugs
|
|
41
|
+
*/
|
|
42
|
+
onNewBug(handler) {
|
|
43
|
+
this.handlers.push(handler);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register a handler for discussion messages
|
|
47
|
+
*/
|
|
48
|
+
onMessage(handler) {
|
|
49
|
+
this.messageHandlers.push(handler);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a handler for when Giuseppe is disabled but a bug is found
|
|
53
|
+
*/
|
|
54
|
+
onGiuseppeDisabled(handler) {
|
|
55
|
+
this.giuseppeDisabledHandlers.push(handler);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Start watching a discussion for approval messages
|
|
59
|
+
*/
|
|
60
|
+
watchDiscussion(discussionId) {
|
|
61
|
+
this.watchedDiscussions.set(discussionId, Date.now());
|
|
62
|
+
logger.info('Watching discussion for approval', { discussionId });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Stop watching a discussion
|
|
66
|
+
*/
|
|
67
|
+
unwatchDiscussion(discussionId) {
|
|
68
|
+
this.watchedDiscussions.delete(discussionId);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clean up old entries from processed ID caches to prevent memory leaks
|
|
72
|
+
*/
|
|
73
|
+
cleanupProcessedCaches() {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const maxAge = BugMonitor.PROCESSED_MAX_AGE_MS;
|
|
76
|
+
for (const [id, timestamp] of this.processedBugIds.entries()) {
|
|
77
|
+
if (now - timestamp > maxAge) {
|
|
78
|
+
this.processedBugIds.delete(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const [id, timestamp] of this.processedMessageIds.entries()) {
|
|
82
|
+
if (now - timestamp > maxAge) {
|
|
83
|
+
this.processedMessageIds.delete(id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Start monitoring for bugs
|
|
89
|
+
*/
|
|
90
|
+
async start() {
|
|
91
|
+
logger.info('Starting Bug Monitor...');
|
|
92
|
+
try {
|
|
93
|
+
// Load config
|
|
94
|
+
this.config = await (0, bug_config_1.loadConfig)(this.userContext);
|
|
95
|
+
if (!this.config.enabled) {
|
|
96
|
+
logger.info('Bug Monitor is disabled in config');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Discover workflow
|
|
100
|
+
this.discovery = await (0, bug_config_1.discoverWorkflow)(this.userContext, this.config) ?? undefined;
|
|
101
|
+
if (!this.discovery) {
|
|
102
|
+
logger.warn(`Bug Reports workflow not found (pattern: "${this.config.workflowNamePattern}")`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!this.discovery.phases.new) {
|
|
106
|
+
logger.warn('No "New" phase found in Bug Reports workflow');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
logger.info('Bug Monitor started', {
|
|
110
|
+
workflowName: this.discovery.workflowName,
|
|
111
|
+
intervalMs: this.config.intervalMs,
|
|
112
|
+
autoFix: this.config.autoFix
|
|
113
|
+
});
|
|
114
|
+
// Initial check
|
|
115
|
+
await this.checkForNewBugs();
|
|
116
|
+
// Start polling interval
|
|
117
|
+
this.interval = setInterval(async () => {
|
|
118
|
+
await this.checkForNewBugs();
|
|
119
|
+
}, this.config.intervalMs);
|
|
120
|
+
// Subscribe to message signals instead of polling
|
|
121
|
+
const unsubscribe = (0, hailer_clients_1.subscribeToSignal)(this.userContext.apiKey, 'messenger.new', (eventData) => this.handleMessageSignal(eventData));
|
|
122
|
+
if (unsubscribe) {
|
|
123
|
+
this.unsubscribeFromSignals = unsubscribe;
|
|
124
|
+
logger.info('Subscribed to message signals for watched discussions');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
logger.warn('No signal subscription available - message watching disabled');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
logger.error('Failed to start Bug Monitor', { error });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Stop monitoring
|
|
136
|
+
*/
|
|
137
|
+
async stop() {
|
|
138
|
+
if (this.interval) {
|
|
139
|
+
clearInterval(this.interval);
|
|
140
|
+
this.interval = undefined;
|
|
141
|
+
}
|
|
142
|
+
if (this.unsubscribeFromSignals) {
|
|
143
|
+
this.unsubscribeFromSignals();
|
|
144
|
+
this.unsubscribeFromSignals = undefined;
|
|
145
|
+
}
|
|
146
|
+
logger.info('Bug Monitor stopped');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get current config
|
|
150
|
+
*/
|
|
151
|
+
getConfig() {
|
|
152
|
+
return this.config;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get workflow discovery result
|
|
156
|
+
*/
|
|
157
|
+
getDiscovery() {
|
|
158
|
+
return this.discovery;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check for new bugs in the "New" phase
|
|
162
|
+
*/
|
|
163
|
+
async checkForNewBugs() {
|
|
164
|
+
if (!this.discovery?.phases.new)
|
|
165
|
+
return;
|
|
166
|
+
try {
|
|
167
|
+
const { hailer } = this.userContext;
|
|
168
|
+
const result = await hailer.fetchActivityList(this.discovery.workflowId, this.discovery.phases.new, 50);
|
|
169
|
+
const activities = result?.activities || result || [];
|
|
170
|
+
const newBugs = activities.filter((a) => !this.processedBugIds.has(a._id));
|
|
171
|
+
if (newBugs.length > 0) {
|
|
172
|
+
logger.info(`Found ${newBugs.length} new bug report(s)`);
|
|
173
|
+
for (const activity of newBugs) {
|
|
174
|
+
const bug = await this.parseActivity(activity);
|
|
175
|
+
const wasProcessed = await this.processBug(bug);
|
|
176
|
+
if (wasProcessed) {
|
|
177
|
+
this.processedBugIds.set(bug.id, Date.now());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
logger.error('Failed to check for bugs', { error });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check for new messages in watched discussions
|
|
188
|
+
*/
|
|
189
|
+
async checkForNewMessages() {
|
|
190
|
+
// Skip message processing if Giuseppe is disabled
|
|
191
|
+
const botState = (0, bot_config_1.getBotState)();
|
|
192
|
+
if (!botState.giuseppe) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (this.watchedDiscussions.size === 0)
|
|
196
|
+
return;
|
|
197
|
+
// Periodic cleanup of old processed IDs
|
|
198
|
+
this.cleanupProcessedCaches();
|
|
199
|
+
const { hailer } = this.userContext;
|
|
200
|
+
for (const [discussionId, watchStartTime] of this.watchedDiscussions) {
|
|
201
|
+
try {
|
|
202
|
+
const result = await hailer.fetchDiscussionMessages(discussionId, 10);
|
|
203
|
+
const messages = result?.messages || [];
|
|
204
|
+
for (const msg of messages) {
|
|
205
|
+
const msgId = msg._id;
|
|
206
|
+
const msgTime = new Date(msg.createdHumanReadable || msg.created).getTime();
|
|
207
|
+
// Message content can be in 'content', 'msg', or 'message' field
|
|
208
|
+
const content = (msg.content || msg.msg || msg.message || '').trim();
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
// Skip already processed messages
|
|
211
|
+
if (this.processedMessageIds.has(msgId))
|
|
212
|
+
continue;
|
|
213
|
+
this.processedMessageIds.set(msgId, Date.now());
|
|
214
|
+
// Skip messages sent BEFORE we started watching
|
|
215
|
+
if (msgTime < watchStartTime)
|
|
216
|
+
continue;
|
|
217
|
+
// Skip messages older than 60 seconds (stale)
|
|
218
|
+
if (now - msgTime > 60000)
|
|
219
|
+
continue;
|
|
220
|
+
// Skip empty messages
|
|
221
|
+
if (!content)
|
|
222
|
+
continue;
|
|
223
|
+
// Skip messages from bot users (only process human messages)
|
|
224
|
+
// Try both 'user' and 'uid' fields for sender ID
|
|
225
|
+
const senderId = msg.user || msg.uid;
|
|
226
|
+
logger.debug('Message sender check', {
|
|
227
|
+
discussionId,
|
|
228
|
+
msgUser: msg.user,
|
|
229
|
+
msgUid: msg.uid,
|
|
230
|
+
senderId,
|
|
231
|
+
botUserIds: Array.from(this.botUserIds),
|
|
232
|
+
isBot: this.botUserIds.has(senderId)
|
|
233
|
+
});
|
|
234
|
+
if (senderId && this.botUserIds.has(senderId)) {
|
|
235
|
+
logger.debug('Skipping bot message', { discussionId, sender: senderId });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
logger.info('Processing approval message', {
|
|
239
|
+
discussionId,
|
|
240
|
+
messageId: msgId,
|
|
241
|
+
sender: senderId,
|
|
242
|
+
content: content.substring(0, 50)
|
|
243
|
+
});
|
|
244
|
+
// Notify message handlers
|
|
245
|
+
for (const handler of this.messageHandlers) {
|
|
246
|
+
try {
|
|
247
|
+
await handler(discussionId, content, senderId);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
logger.error('Message handler failed', { discussionId, error });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
logger.warn('Failed to check messages', { discussionId, error });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Handle incoming message signal (real-time, replaces polling)
|
|
262
|
+
*/
|
|
263
|
+
async handleMessageSignal(signalData) {
|
|
264
|
+
// Check if Giuseppe is disabled
|
|
265
|
+
const botState = (0, bot_config_1.getBotState)();
|
|
266
|
+
if (!botState.giuseppe) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const discussionId = signalData?.discussion;
|
|
270
|
+
if (!discussionId)
|
|
271
|
+
return;
|
|
272
|
+
// Check if we're watching this discussion
|
|
273
|
+
if (!this.watchedDiscussions.has(discussionId)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const senderId = signalData?.uid;
|
|
277
|
+
// Skip bot messages
|
|
278
|
+
if (senderId && this.botUserIds.has(senderId)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const msgId = signalData?.msg_id;
|
|
282
|
+
if (!msgId)
|
|
283
|
+
return;
|
|
284
|
+
// Skip already processed messages
|
|
285
|
+
if (this.processedMessageIds.has(msgId)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.processedMessageIds.set(msgId, Date.now());
|
|
289
|
+
try {
|
|
290
|
+
// Fetch full message content
|
|
291
|
+
const { hailer } = this.userContext;
|
|
292
|
+
const result = await hailer.fetchDiscussionMessages(discussionId, 5);
|
|
293
|
+
const messages = result?.messages || [];
|
|
294
|
+
const msg = messages.find((m) => m._id === msgId);
|
|
295
|
+
if (!msg)
|
|
296
|
+
return;
|
|
297
|
+
const content = (msg.content || msg.msg || msg.message || '').trim();
|
|
298
|
+
if (!content)
|
|
299
|
+
return;
|
|
300
|
+
// Skip stale messages (older than 60 seconds)
|
|
301
|
+
const msgTime = new Date(msg.createdHumanReadable || msg.created).getTime();
|
|
302
|
+
if (Date.now() - msgTime > 60000)
|
|
303
|
+
return;
|
|
304
|
+
logger.debug('Processing message from signal', { discussionId, msgId, senderId });
|
|
305
|
+
// Dispatch to handlers
|
|
306
|
+
for (const handler of this.messageHandlers) {
|
|
307
|
+
try {
|
|
308
|
+
await handler(discussionId, content, senderId);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
logger.error('Message handler failed', { discussionId, error });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
logger.warn('Failed to handle message signal', { discussionId, msgId, error });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Parse activity into BugReport
|
|
321
|
+
*/
|
|
322
|
+
async parseActivity(activity) {
|
|
323
|
+
const { hailer } = this.userContext;
|
|
324
|
+
// Get full activity details
|
|
325
|
+
const fullActivity = await hailer.fetchActivityById(activity._id);
|
|
326
|
+
const fields = fullActivity.fields || {};
|
|
327
|
+
// Extract field values using discovered field mappings
|
|
328
|
+
const bug = {
|
|
329
|
+
id: activity._id,
|
|
330
|
+
name: activity.name || 'Untitled Bug',
|
|
331
|
+
workflowId: this.discovery.workflowId,
|
|
332
|
+
phaseId: this.discovery.phases.new,
|
|
333
|
+
discussionId: fullActivity.discussion,
|
|
334
|
+
createdAt: activity.createdHumanReadable || new Date().toISOString(),
|
|
335
|
+
createdBy: fullActivity.createdBy || activity.createdBy,
|
|
336
|
+
description: '',
|
|
337
|
+
rawFields: fields
|
|
338
|
+
};
|
|
339
|
+
// Map fields by discovered field IDs
|
|
340
|
+
const fieldMapping = this.discovery.fields;
|
|
341
|
+
for (const [fieldId, fieldData] of Object.entries(fields)) {
|
|
342
|
+
const value = fieldData.value;
|
|
343
|
+
if (fieldId === fieldMapping.appId) {
|
|
344
|
+
bug.appId = String(value);
|
|
345
|
+
}
|
|
346
|
+
else if (fieldId === fieldMapping.appName) {
|
|
347
|
+
bug.appName = String(value);
|
|
348
|
+
}
|
|
349
|
+
else if (fieldId === fieldMapping.description) {
|
|
350
|
+
bug.description = String(value);
|
|
351
|
+
}
|
|
352
|
+
else if (fieldId === fieldMapping.stepsToReproduce) {
|
|
353
|
+
bug.stepsToReproduce = String(value);
|
|
354
|
+
}
|
|
355
|
+
else if (fieldId === fieldMapping.expectedBehavior) {
|
|
356
|
+
bug.expectedBehavior = String(value);
|
|
357
|
+
}
|
|
358
|
+
else if (fieldId === fieldMapping.actualBehavior) {
|
|
359
|
+
bug.actualBehavior = String(value);
|
|
360
|
+
}
|
|
361
|
+
else if (fieldId === fieldMapping.reportedBy) {
|
|
362
|
+
bug.reportedBy = String(value);
|
|
363
|
+
}
|
|
364
|
+
else if (fieldId === fieldMapping.priority) {
|
|
365
|
+
bug.priority = this.parsePriority(value);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Fallback: try to extract info from unmatched fields
|
|
369
|
+
if (!bug.description) {
|
|
370
|
+
bug.description = this.extractDescription(fields);
|
|
371
|
+
}
|
|
372
|
+
return bug;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Parse priority value
|
|
376
|
+
*/
|
|
377
|
+
parsePriority(value) {
|
|
378
|
+
const v = String(value).toLowerCase();
|
|
379
|
+
if (v.includes('critical') || v.includes('urgent'))
|
|
380
|
+
return 'critical';
|
|
381
|
+
if (v.includes('high'))
|
|
382
|
+
return 'high';
|
|
383
|
+
if (v.includes('medium') || v.includes('normal'))
|
|
384
|
+
return 'medium';
|
|
385
|
+
return 'low';
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Extract description from fields as fallback
|
|
389
|
+
*/
|
|
390
|
+
extractDescription(fields) {
|
|
391
|
+
// Find longest text field
|
|
392
|
+
let longest = '';
|
|
393
|
+
for (const fieldData of Object.values(fields)) {
|
|
394
|
+
if (typeof fieldData.value === 'string' && fieldData.value.length > longest.length) {
|
|
395
|
+
longest = fieldData.value;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return longest;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Process a bug through all handlers
|
|
402
|
+
*/
|
|
403
|
+
async processBug(bug) {
|
|
404
|
+
// Check if Giuseppe bot is enabled
|
|
405
|
+
const botState = (0, bot_config_1.getBotState)();
|
|
406
|
+
if (!botState.giuseppe) {
|
|
407
|
+
logger.debug('Giuseppe is disabled, skipping bug processing', { bugId: bug.id });
|
|
408
|
+
// Notify handlers (HAL can respond naturally)
|
|
409
|
+
for (const handler of this.giuseppeDisabledHandlers) {
|
|
410
|
+
try {
|
|
411
|
+
await handler(bug);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
logger.error('Giuseppe disabled handler failed', { bugId: bug.id, error });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return false; // Bug was NOT processed
|
|
418
|
+
}
|
|
419
|
+
logger.info('Processing bug', {
|
|
420
|
+
id: bug.id,
|
|
421
|
+
name: bug.name,
|
|
422
|
+
appId: bug.appId,
|
|
423
|
+
priority: bug.priority
|
|
424
|
+
});
|
|
425
|
+
// Notify in discussion if enabled
|
|
426
|
+
if (this.config?.notifyOnNew && bug.discussionId) {
|
|
427
|
+
await this.postNotification(bug);
|
|
428
|
+
}
|
|
429
|
+
// Dispatch to handlers
|
|
430
|
+
for (const handler of this.handlers) {
|
|
431
|
+
try {
|
|
432
|
+
await handler(bug, this.userContext);
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
logger.error('Bug handler failed', { bugId: bug.id, error });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return true; // Bug WAS processed
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Post notification to bug discussion
|
|
442
|
+
*/
|
|
443
|
+
async postNotification(bug) {
|
|
444
|
+
try {
|
|
445
|
+
const { hailer } = this.userContext;
|
|
446
|
+
// Check if already notified (look for our notification message)
|
|
447
|
+
if (bug.discussionId) {
|
|
448
|
+
try {
|
|
449
|
+
const result = await hailer.fetchDiscussionMessages(bug.discussionId, 20);
|
|
450
|
+
const messages = result?.messages || [];
|
|
451
|
+
const alreadyNotified = messages.some((msg) => {
|
|
452
|
+
const content = msg.content || msg.msg || msg.message || '';
|
|
453
|
+
return content.includes('Bug Monitor: New bug detected') || content.includes('**Classification:');
|
|
454
|
+
});
|
|
455
|
+
if (alreadyNotified) {
|
|
456
|
+
logger.debug('Bug already notified, skipping notification', { bugId: bug.id });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// Continue if check fails
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Join the discussion first (required to post messages)
|
|
465
|
+
try {
|
|
466
|
+
await hailer.joinActivityDiscussion(bug.id);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// May already be a member, continue
|
|
470
|
+
}
|
|
471
|
+
const message = [
|
|
472
|
+
'🐛 **Bug Monitor: New bug detected!**',
|
|
473
|
+
'',
|
|
474
|
+
bug.appId ? `**App:** ${bug.appName || 'Unknown'} (\`${bug.appId}\`)` : '',
|
|
475
|
+
bug.reportedBy ? `**Reported by:** ${bug.reportedBy}` : '',
|
|
476
|
+
bug.priority ? `**Priority:** ${bug.priority}` : '',
|
|
477
|
+
'',
|
|
478
|
+
bug.description ? `**Description:**\n${bug.description.substring(0, 500)}` : '',
|
|
479
|
+
'',
|
|
480
|
+
this.config?.autoFix ? '🔧 Auto-fix is enabled. Analyzing...' : '📋 Awaiting manual review.'
|
|
481
|
+
].filter(Boolean).join('\n');
|
|
482
|
+
await hailer.sendDiscussionMessage(bug.discussionId, message);
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
logger.warn('Failed to post notification', { bugId: bug.id, error });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Move bug to a different phase
|
|
490
|
+
*/
|
|
491
|
+
async moveBugToPhase(bugId, phase) {
|
|
492
|
+
const phaseId = this.discovery?.phases[phase];
|
|
493
|
+
if (!phaseId) {
|
|
494
|
+
logger.warn(`Phase "${phase}" not found`);
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const { hailer } = this.userContext;
|
|
499
|
+
await hailer.updateActivities([{ _id: bugId, phaseId }]);
|
|
500
|
+
logger.info('Moved bug to phase', { bugId, phase });
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
logger.error('Failed to move bug', { bugId, phase, error });
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
exports.BugMonitor = BugMonitor;
|
|
510
|
+
//# sourceMappingURL=bug-monitor.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Giuseppe AI Module - Claude API interactions for bug analysis and fix generation
|
|
3
|
+
*/
|
|
4
|
+
import type { BugReport, AppRegistryEntry } from './types';
|
|
5
|
+
/** Fix plan from Claude analysis */
|
|
6
|
+
export interface FixPlan {
|
|
7
|
+
analysis: string;
|
|
8
|
+
debugTrace?: string;
|
|
9
|
+
failurePoint?: string;
|
|
10
|
+
rootCause: string;
|
|
11
|
+
fix: {
|
|
12
|
+
files: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
action: 'edit' | 'create' | 'delete';
|
|
15
|
+
search?: string;
|
|
16
|
+
replace?: string;
|
|
17
|
+
content?: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
explanation: string;
|
|
21
|
+
testSuggestions?: string[];
|
|
22
|
+
}
|
|
23
|
+
export type BugClassification = 'bug' | 'feature_request' | 'unclear';
|
|
24
|
+
export interface ClassificationResult {
|
|
25
|
+
classification: BugClassification;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FileContent {
|
|
29
|
+
path: string;
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class GiuseppeAI {
|
|
33
|
+
private anthropic?;
|
|
34
|
+
constructor(apiKey?: string);
|
|
35
|
+
get isAvailable(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Classify a report as bug or feature request
|
|
38
|
+
*/
|
|
39
|
+
classifyReport(bug: BugReport): Promise<ClassificationResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Analyze bug and generate fix plan using Claude (two-phase approach)
|
|
42
|
+
* Phase 1: Send file list -> Claude picks relevant files
|
|
43
|
+
* Phase 2: Send those files -> Claude generates fix
|
|
44
|
+
*/
|
|
45
|
+
analyzeAndPlanFix(bug: BugReport, app: AppRegistryEntry, allFiles: string[], readFiles: (paths: string[]) => Promise<FileContent[]>): Promise<FixPlan | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Retry fix based on apply error (search string not found) - re-reads file and generates new fix
|
|
48
|
+
*/
|
|
49
|
+
retryFixFromApplyError(bug: BugReport, previousFix: FixPlan, applyError: string, currentFiles: FileContent[], attempt: number): Promise<FixPlan | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Retry fix based on build error - sends error to Claude to generate corrected fix
|
|
52
|
+
*/
|
|
53
|
+
retryFixFromError(bug: BugReport, previousFix: FixPlan, buildError: string, currentFiles: FileContent[], attempt: number): Promise<FixPlan | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Generate a new fix based on user feedback
|
|
56
|
+
*/
|
|
57
|
+
retryFixWithFeedback(bug: BugReport, previousFix: FixPlan, feedback: string, allSourceFiles: string[], currentFiles: FileContent[]): Promise<FixPlan | null>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=giuseppe-ai.d.ts.map
|