@hailer/mcp 0.1.15 → 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 +2 -2
  103. package/dist/modules/bug-reports/giuseppe-bot.js +66 -42
  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 +84 -13
  109. package/dist/modules/bug-reports/giuseppe-lsp.js +403 -61
  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 +3 -2
@@ -0,0 +1,621 @@
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 config_1 = require("./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
+ skipNotifications = false; // Skip old-style notifications (for conversational mode)
30
+ constructor(userContext) {
31
+ this.userContext = userContext;
32
+ }
33
+ /**
34
+ * Disable old-style notifications (for conversational mode)
35
+ * Daemon will handle introductions instead
36
+ */
37
+ setSkipNotifications(skip) {
38
+ this.skipNotifications = skip;
39
+ logger.debug('Skip notifications set', { skip });
40
+ }
41
+ /**
42
+ * Register a bot user ID to ignore messages from
43
+ */
44
+ registerBotUser(userId) {
45
+ this.botUserIds.add(userId);
46
+ logger.debug('Registered bot user to ignore', { userId });
47
+ }
48
+ /**
49
+ * Register a handler for new bugs
50
+ */
51
+ onNewBug(handler) {
52
+ this.handlers.push(handler);
53
+ }
54
+ /**
55
+ * Register a handler for discussion messages
56
+ */
57
+ onMessage(handler) {
58
+ this.messageHandlers.push(handler);
59
+ }
60
+ /**
61
+ * Register a handler for when Giuseppe is disabled but a bug is found
62
+ */
63
+ onGiuseppeDisabled(handler) {
64
+ this.giuseppeDisabledHandlers.push(handler);
65
+ }
66
+ /**
67
+ * Start watching a discussion for approval messages
68
+ */
69
+ watchDiscussion(discussionId) {
70
+ this.watchedDiscussions.set(discussionId, Date.now());
71
+ logger.info('Watching discussion for approval', { discussionId });
72
+ }
73
+ /**
74
+ * Stop watching a discussion
75
+ */
76
+ unwatchDiscussion(discussionId) {
77
+ this.watchedDiscussions.delete(discussionId);
78
+ }
79
+ /**
80
+ * Clean up old entries from processed ID caches to prevent memory leaks
81
+ */
82
+ cleanupProcessedCaches() {
83
+ const now = Date.now();
84
+ const maxAge = BugMonitor.PROCESSED_MAX_AGE_MS;
85
+ for (const [id, timestamp] of this.processedBugIds.entries()) {
86
+ if (now - timestamp > maxAge) {
87
+ this.processedBugIds.delete(id);
88
+ }
89
+ }
90
+ for (const [id, timestamp] of this.processedMessageIds.entries()) {
91
+ if (now - timestamp > maxAge) {
92
+ this.processedMessageIds.delete(id);
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Start monitoring for bugs
98
+ */
99
+ async start() {
100
+ logger.info('Starting Bug Monitor...');
101
+ try {
102
+ // Load config
103
+ this.config = await (0, config_1.loadConfig)(this.userContext);
104
+ if (!this.config.enabled) {
105
+ logger.info('Bug Monitor is disabled in config');
106
+ return;
107
+ }
108
+ // Discover workflow
109
+ this.discovery = await (0, config_1.discoverWorkflow)(this.userContext, this.config) ?? undefined;
110
+ if (!this.discovery) {
111
+ logger.warn(`Bug Reports workflow not found (pattern: "${this.config.workflowNamePattern}")`);
112
+ return;
113
+ }
114
+ if (!this.discovery.phases.new) {
115
+ logger.warn('No "New" phase found in Bug Reports workflow');
116
+ return;
117
+ }
118
+ logger.info('Bug Monitor started', {
119
+ workflowName: this.discovery.workflowName,
120
+ autoFix: this.config.autoFix
121
+ });
122
+ // Initial check for any bugs that arrived while offline
123
+ await this.checkForNewBugs();
124
+ // Subscribe to activity creation signals (replaces polling)
125
+ const activityUnsubscribe = (0, hailer_clients_1.subscribeToSignal)(this.userContext.apiKey, 'activities.created', (eventData) => this.handleActivitySignal(eventData));
126
+ // Subscribe to message signals for watched discussions
127
+ const messageUnsubscribe = (0, hailer_clients_1.subscribeToSignal)(this.userContext.apiKey, 'messenger.new', (eventData) => this.handleMessageSignal(eventData));
128
+ // Store unsubscribe functions
129
+ const unsubscribers = [];
130
+ if (activityUnsubscribe) {
131
+ unsubscribers.push(activityUnsubscribe);
132
+ logger.info('Subscribed to activity signals for real-time bug detection');
133
+ }
134
+ else {
135
+ logger.warn('No activity signal subscription available - falling back to polling');
136
+ // Fallback to polling if signal subscription fails
137
+ this.interval = setInterval(async () => {
138
+ await this.checkForNewBugs();
139
+ }, this.config.intervalMs);
140
+ }
141
+ if (messageUnsubscribe) {
142
+ unsubscribers.push(messageUnsubscribe);
143
+ logger.info('Subscribed to message signals for watched discussions');
144
+ }
145
+ if (unsubscribers.length > 0) {
146
+ this.unsubscribeFromSignals = () => {
147
+ for (const unsub of unsubscribers) {
148
+ unsub();
149
+ }
150
+ };
151
+ }
152
+ }
153
+ catch (error) {
154
+ logger.error('Failed to start Bug Monitor', { error });
155
+ }
156
+ }
157
+ /**
158
+ * Stop monitoring
159
+ */
160
+ async stop() {
161
+ if (this.interval) {
162
+ clearInterval(this.interval);
163
+ this.interval = undefined;
164
+ }
165
+ if (this.unsubscribeFromSignals) {
166
+ this.unsubscribeFromSignals();
167
+ this.unsubscribeFromSignals = undefined;
168
+ }
169
+ logger.info('Bug Monitor stopped');
170
+ }
171
+ /**
172
+ * Get current config
173
+ */
174
+ getConfig() {
175
+ return this.config;
176
+ }
177
+ /**
178
+ * Get workflow discovery result
179
+ */
180
+ getDiscovery() {
181
+ return this.discovery;
182
+ }
183
+ /**
184
+ * Check for new bugs in the "New" phase
185
+ */
186
+ async checkForNewBugs() {
187
+ if (!this.discovery?.phases.new)
188
+ return;
189
+ try {
190
+ const { hailer } = this.userContext;
191
+ const result = await hailer.fetchActivityList(this.discovery.workflowId, this.discovery.phases.new, 50);
192
+ const activities = result?.activities || result || [];
193
+ const newBugs = activities.filter((a) => !this.processedBugIds.has(a._id));
194
+ if (newBugs.length > 0) {
195
+ logger.info(`Found ${newBugs.length} new bug report(s)`);
196
+ for (const activity of newBugs) {
197
+ const bug = await this.parseActivity(activity);
198
+ const wasProcessed = await this.processBug(bug);
199
+ if (wasProcessed) {
200
+ this.processedBugIds.set(bug.id, Date.now());
201
+ }
202
+ }
203
+ }
204
+ }
205
+ catch (error) {
206
+ logger.error('Failed to check for bugs', { error });
207
+ }
208
+ }
209
+ /**
210
+ * Check for new messages in watched discussions
211
+ */
212
+ async checkForNewMessages() {
213
+ // Skip message processing if Giuseppe is disabled
214
+ const botState = (0, bot_config_1.getBotState)();
215
+ if (!botState.giuseppe) {
216
+ return;
217
+ }
218
+ if (this.watchedDiscussions.size === 0)
219
+ return;
220
+ // Periodic cleanup of old processed IDs
221
+ this.cleanupProcessedCaches();
222
+ const { hailer } = this.userContext;
223
+ for (const [discussionId, watchStartTime] of this.watchedDiscussions) {
224
+ try {
225
+ const result = await hailer.fetchDiscussionMessages(discussionId, 10);
226
+ const messages = result?.messages || [];
227
+ for (const msg of messages) {
228
+ const msgId = msg._id;
229
+ const msgTime = new Date(msg.createdHumanReadable || msg.created).getTime();
230
+ // Message content can be in 'content', 'msg', or 'message' field
231
+ const content = (msg.content || msg.msg || msg.message || '').trim();
232
+ const now = Date.now();
233
+ // Skip already processed messages
234
+ if (this.processedMessageIds.has(msgId))
235
+ continue;
236
+ this.processedMessageIds.set(msgId, Date.now());
237
+ // Skip messages sent BEFORE we started watching
238
+ if (msgTime < watchStartTime)
239
+ continue;
240
+ // Skip messages older than 60 seconds (stale)
241
+ if (now - msgTime > 60000)
242
+ continue;
243
+ // Skip empty messages
244
+ if (!content)
245
+ continue;
246
+ // Skip messages from bot users (only process human messages)
247
+ // Try both 'user' and 'uid' fields for sender ID
248
+ const senderId = msg.user || msg.uid;
249
+ logger.debug('Message sender check', {
250
+ discussionId,
251
+ msgUser: msg.user,
252
+ msgUid: msg.uid,
253
+ senderId,
254
+ botUserIds: Array.from(this.botUserIds),
255
+ isBot: this.botUserIds.has(senderId)
256
+ });
257
+ if (senderId && this.botUserIds.has(senderId)) {
258
+ logger.debug('Skipping bot message', { discussionId, sender: senderId });
259
+ continue;
260
+ }
261
+ logger.info('Processing approval message', {
262
+ discussionId,
263
+ messageId: msgId,
264
+ sender: senderId,
265
+ content: content.substring(0, 50)
266
+ });
267
+ // Notify message handlers
268
+ for (const handler of this.messageHandlers) {
269
+ try {
270
+ await handler(discussionId, content, senderId);
271
+ }
272
+ catch (error) {
273
+ logger.error('Message handler failed', { discussionId, error });
274
+ }
275
+ }
276
+ }
277
+ }
278
+ catch (error) {
279
+ logger.warn('Failed to check messages', { discussionId, error });
280
+ }
281
+ }
282
+ }
283
+ /**
284
+ * Handle incoming message signal (real-time, replaces polling)
285
+ */
286
+ async handleMessageSignal(signalData) {
287
+ // Check if Giuseppe is disabled
288
+ const botState = (0, bot_config_1.getBotState)();
289
+ if (!botState.giuseppe) {
290
+ return;
291
+ }
292
+ const discussionId = signalData?.discussion;
293
+ if (!discussionId)
294
+ return;
295
+ // Check if we're watching this discussion
296
+ if (!this.watchedDiscussions.has(discussionId)) {
297
+ return;
298
+ }
299
+ const senderId = signalData?.uid;
300
+ // Skip bot messages
301
+ if (senderId && this.botUserIds.has(senderId)) {
302
+ return;
303
+ }
304
+ const msgId = signalData?.msg_id;
305
+ if (!msgId)
306
+ return;
307
+ // Skip already processed messages
308
+ if (this.processedMessageIds.has(msgId)) {
309
+ return;
310
+ }
311
+ this.processedMessageIds.set(msgId, Date.now());
312
+ try {
313
+ // Fetch full message content
314
+ const { hailer } = this.userContext;
315
+ const result = await hailer.fetchDiscussionMessages(discussionId, 5);
316
+ const messages = result?.messages || [];
317
+ const msg = messages.find((m) => m._id === msgId);
318
+ if (!msg)
319
+ return;
320
+ const content = (msg.content || msg.msg || msg.message || '').trim();
321
+ if (!content)
322
+ return;
323
+ // Skip stale messages (older than 60 seconds)
324
+ const msgTime = new Date(msg.createdHumanReadable || msg.created).getTime();
325
+ if (Date.now() - msgTime > 60000)
326
+ return;
327
+ logger.debug('Processing message from signal', { discussionId, msgId, senderId });
328
+ // Dispatch to handlers
329
+ for (const handler of this.messageHandlers) {
330
+ try {
331
+ await handler(discussionId, content, senderId);
332
+ }
333
+ catch (error) {
334
+ logger.error('Message handler failed', { discussionId, error });
335
+ }
336
+ }
337
+ }
338
+ catch (error) {
339
+ logger.warn('Failed to handle message signal', { discussionId, msgId, error });
340
+ }
341
+ }
342
+ /**
343
+ * Handle incoming activity created signal (real-time, replaces polling)
344
+ */
345
+ async handleActivitySignal(signalData) {
346
+ if (!this.discovery?.phases.new)
347
+ return;
348
+ // Extract activity ID from signal - can be _id, id, or activity_id (array)
349
+ let activityId = signalData?._id || signalData?.id;
350
+ if (!activityId && Array.isArray(signalData?.activity_id) && signalData.activity_id.length > 0) {
351
+ activityId = signalData.activity_id[0];
352
+ }
353
+ if (!activityId) {
354
+ logger.debug('Activity signal missing ID', { signalData: JSON.stringify(signalData).slice(0, 200) });
355
+ return;
356
+ }
357
+ // Early filter using signal data (avoid API call if not our workflow/phase)
358
+ const signalWorkflowId = signalData?.processId;
359
+ const signalPhaseId = signalData?.phase;
360
+ if (signalWorkflowId && signalWorkflowId !== this.discovery.workflowId) {
361
+ // Not our bug workflow, skip silently
362
+ return;
363
+ }
364
+ if (signalPhaseId && signalPhaseId !== this.discovery.phases.new) {
365
+ // Not in "New" phase, skip silently
366
+ return;
367
+ }
368
+ // Skip if already processed
369
+ if (this.processedBugIds.has(activityId)) {
370
+ return;
371
+ }
372
+ logger.info('Processing activity signal', { activityId, signalWorkflowId, signalPhaseId });
373
+ try {
374
+ const { hailer } = this.userContext;
375
+ // Fetch full activity to verify and get details
376
+ const activity = await hailer.fetchActivityById(activityId);
377
+ // Double-check workflow (in case signal didn't include it)
378
+ if (activity.processId !== this.discovery.workflowId) {
379
+ return;
380
+ }
381
+ // Double-check phase (in case signal didn't include it)
382
+ if (activity.phaseId !== this.discovery.phases.new) {
383
+ logger.debug('Activity not in New phase, skipping', {
384
+ activityId,
385
+ phaseId: activity.phaseId,
386
+ expectedPhaseId: this.discovery.phases.new
387
+ });
388
+ return;
389
+ }
390
+ logger.info('New bug detected via signal', { activityId, name: activity.name });
391
+ // Parse and process
392
+ const bug = await this.parseActivity(activity);
393
+ const wasProcessed = await this.processBug(bug);
394
+ if (wasProcessed) {
395
+ this.processedBugIds.set(bug.id, Date.now());
396
+ }
397
+ }
398
+ catch (error) {
399
+ logger.warn('Failed to handle activity signal', { activityId, error });
400
+ }
401
+ }
402
+ /**
403
+ * Parse activity into BugReport
404
+ */
405
+ async parseActivity(activity) {
406
+ const { hailer } = this.userContext;
407
+ // Get full activity details
408
+ const fullActivity = await hailer.fetchActivityById(activity._id);
409
+ const fields = fullActivity.fields || {};
410
+ // Extract field values using discovered field mappings
411
+ const bug = {
412
+ id: activity._id,
413
+ name: activity.name || 'Untitled Bug',
414
+ workflowId: this.discovery.workflowId,
415
+ phaseId: this.discovery.phases.new,
416
+ discussionId: fullActivity.discussion,
417
+ createdAt: activity.createdHumanReadable || new Date().toISOString(),
418
+ createdBy: fullActivity.createdBy || activity.createdBy,
419
+ description: '',
420
+ rawFields: fields
421
+ };
422
+ // Map fields by discovered field IDs
423
+ const fieldMapping = this.discovery.fields;
424
+ for (const [fieldId, fieldData] of Object.entries(fields)) {
425
+ const value = fieldData.value;
426
+ if (fieldId === fieldMapping.appId) {
427
+ bug.appId = String(value);
428
+ }
429
+ else if (fieldId === fieldMapping.appName) {
430
+ bug.appName = String(value);
431
+ }
432
+ else if (fieldId === fieldMapping.description) {
433
+ bug.description = String(value);
434
+ }
435
+ else if (fieldId === fieldMapping.stepsToReproduce) {
436
+ bug.stepsToReproduce = String(value);
437
+ }
438
+ else if (fieldId === fieldMapping.expectedBehavior) {
439
+ bug.expectedBehavior = String(value);
440
+ }
441
+ else if (fieldId === fieldMapping.actualBehavior) {
442
+ bug.actualBehavior = String(value);
443
+ }
444
+ else if (fieldId === fieldMapping.reportedBy) {
445
+ bug.reportedBy = String(value);
446
+ }
447
+ else if (fieldId === fieldMapping.priority) {
448
+ bug.priority = this.parsePriority(value);
449
+ }
450
+ }
451
+ // Fallback: try to extract info from unmatched fields
452
+ if (!bug.description) {
453
+ bug.description = this.extractDescription(fields);
454
+ logger.debug('Used fallback description extraction', {
455
+ bugId: bug.id,
456
+ descriptionLength: bug.description.length,
457
+ fieldIds: Object.keys(fields)
458
+ });
459
+ }
460
+ logger.debug('Parsed bug report', {
461
+ bugId: bug.id,
462
+ name: bug.name,
463
+ appName: bug.appName,
464
+ descriptionPreview: bug.description?.substring(0, 50)
465
+ });
466
+ return bug;
467
+ }
468
+ /**
469
+ * Parse priority value
470
+ */
471
+ parsePriority(value) {
472
+ const v = String(value).toLowerCase();
473
+ if (v.includes('critical') || v.includes('urgent'))
474
+ return 'critical';
475
+ if (v.includes('high'))
476
+ return 'high';
477
+ if (v.includes('medium') || v.includes('normal'))
478
+ return 'medium';
479
+ return 'low';
480
+ }
481
+ /**
482
+ * Extract description from fields as fallback
483
+ */
484
+ extractDescription(fields) {
485
+ // Skip values that look like IDs (24 char hex)
486
+ const isLikelyId = (v) => /^[a-f0-9]{24}$/i.test(v);
487
+ // First: look for field with "description" in key/label
488
+ for (const [fieldId, fieldData] of Object.entries(fields)) {
489
+ const label = (fieldData.label || fieldData.key || fieldId || '').toLowerCase();
490
+ if (label.includes('description') && typeof fieldData.value === 'string' && !isLikelyId(fieldData.value)) {
491
+ return fieldData.value;
492
+ }
493
+ }
494
+ // Second: find longest text that's not an ID
495
+ let longest = '';
496
+ for (const fieldData of Object.values(fields)) {
497
+ const value = fieldData.value;
498
+ if (typeof value === 'string' && value.length > longest.length && !isLikelyId(value)) {
499
+ longest = value;
500
+ }
501
+ }
502
+ return longest;
503
+ }
504
+ /**
505
+ * Process a bug through all handlers
506
+ */
507
+ async processBug(bug) {
508
+ // Check if Giuseppe bot is enabled
509
+ const botState = (0, bot_config_1.getBotState)();
510
+ if (!botState.giuseppe) {
511
+ logger.debug('Giuseppe is disabled, skipping bug processing', { bugId: bug.id });
512
+ // Notify handlers (HAL can respond naturally)
513
+ for (const handler of this.giuseppeDisabledHandlers) {
514
+ try {
515
+ await handler(bug);
516
+ }
517
+ catch (error) {
518
+ logger.error('Giuseppe disabled handler failed', { bugId: bug.id, error });
519
+ }
520
+ }
521
+ return false; // Bug was NOT processed
522
+ }
523
+ logger.info('Processing bug', {
524
+ id: bug.id,
525
+ name: bug.name,
526
+ appId: bug.appId,
527
+ priority: bug.priority
528
+ });
529
+ // Notify in discussion if enabled (skip in conversational mode - daemon handles intro)
530
+ if (this.config?.notifyOnNew && bug.discussionId && !this.skipNotifications) {
531
+ await this.postNotification(bug);
532
+ }
533
+ // Dispatch to handlers
534
+ for (const handler of this.handlers) {
535
+ try {
536
+ await handler(bug, this.userContext);
537
+ }
538
+ catch (error) {
539
+ // Properly serialize error for logging (Error objects don't JSON.stringify well)
540
+ const errorMessage = error instanceof Error ? error.message : String(error);
541
+ const errorStack = error instanceof Error ? error.stack : undefined;
542
+ logger.error('Bug handler failed', {
543
+ bugId: bug.id,
544
+ errorMessage,
545
+ errorStack
546
+ });
547
+ }
548
+ }
549
+ return true; // Bug WAS processed
550
+ }
551
+ /**
552
+ * Post notification to bug discussion
553
+ */
554
+ async postNotification(bug) {
555
+ try {
556
+ const { hailer } = this.userContext;
557
+ // Check if already notified (look for our notification message)
558
+ if (bug.discussionId) {
559
+ try {
560
+ const result = await hailer.fetchDiscussionMessages(bug.discussionId, 20);
561
+ const messages = result?.messages || [];
562
+ const alreadyNotified = messages.some((msg) => {
563
+ const content = msg.content || msg.msg || msg.message || '';
564
+ return content.includes('Bug Monitor: New bug detected') || content.includes('**Classification:');
565
+ });
566
+ if (alreadyNotified) {
567
+ logger.debug('Bug already notified, skipping notification', { bugId: bug.id });
568
+ return;
569
+ }
570
+ }
571
+ catch {
572
+ // Continue if check fails
573
+ }
574
+ }
575
+ // Join the discussion first (required to post messages)
576
+ try {
577
+ await hailer.joinActivityDiscussion(bug.id);
578
+ }
579
+ catch {
580
+ // May already be a member, continue
581
+ }
582
+ const message = [
583
+ '🐛 **Bug Monitor: New bug detected!**',
584
+ '',
585
+ bug.appId ? `**App:** ${bug.appName || 'Unknown'} (\`${bug.appId}\`)` : '',
586
+ bug.reportedBy ? `**Reported by:** ${bug.reportedBy}` : '',
587
+ bug.priority ? `**Priority:** ${bug.priority}` : '',
588
+ '',
589
+ bug.description ? `**Description:**\n${bug.description.substring(0, 500)}` : '',
590
+ '',
591
+ this.config?.autoFix ? '🔧 Auto-fix is enabled. Analyzing...' : '📋 Awaiting manual review.'
592
+ ].filter(Boolean).join('\n');
593
+ await hailer.sendDiscussionMessage(bug.discussionId, message);
594
+ }
595
+ catch (error) {
596
+ logger.warn('Failed to post notification', { bugId: bug.id, error });
597
+ }
598
+ }
599
+ /**
600
+ * Move bug to a different phase
601
+ */
602
+ async moveBugToPhase(bugId, phase) {
603
+ const phaseId = this.discovery?.phases[phase];
604
+ if (!phaseId) {
605
+ logger.warn(`Phase "${phase}" not found`);
606
+ return false;
607
+ }
608
+ try {
609
+ const { hailer } = this.userContext;
610
+ await hailer.updateActivities([{ _id: bugId, phaseId }]);
611
+ logger.info('Moved bug to phase', { bugId, phase });
612
+ return true;
613
+ }
614
+ catch (error) {
615
+ logger.error('Failed to move bug', { bugId, phase, error });
616
+ return false;
617
+ }
618
+ }
619
+ }
620
+ exports.BugMonitor = BugMonitor;
621
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Giuseppe System Prompt - Expert TypeScript/React debugger prompt
3
+ */
4
+ export declare const GIUSEPPE_SYSTEM_PROMPT = "<identity>\nYou are Giuseppe, an expert TypeScript/React debugger for Hailer apps.\nYou fix bugs by TRACING code paths, not by guessing.\n</identity>\n\n<expertise>\nTYPESCRIPT: Strict types, interfaces, generics, type guards\nREACT: Hooks, state, effects, memoization, dependency arrays\nCHAKRA UI: Box, Flex, useColorModeValue, responsive props\nHAILER SDK: useHailerContext, useActivity, useWorkflows, activity fields\nCANVAS: 2D context, drawing, hit detection, coordinate systems\n</expertise>\n\n<debugging-process>\nSTEP 1: TRACE THE CODE PATH\n- What function handles the user action? (onClick, onMouseMove, etc.)\n- What state/props does it use?\n- What conditions might fail?\n\nSTEP 2: CHECK REACT REACTIVITY\n- Is state being SET? (setState called)\n- Is component RE-RENDERING? (check useCallback/useMemo deps)\n- Are closures STALE? (missing deps = old values)\n\nSTEP 3: IDENTIFY EXACT FAILURE POINT\nGOOD: \"handleClick uses selectedId but renderList deps array is missing selectedId\"\nBAD: \"Something might be wrong with the click handler\"\n\nSTEP 4: MINIMAL FIX\n- Add missing dependency to array\n- Add missing prop to component\n- Fix the one broken thing - don't refactor\n</debugging-process>\n\n<react-patterns>\nDEPENDENCY ARRAY BUGS (most common!):\n- useCallback/useMemo/useEffect use state but deps array missing it\n- Symptom: UI doesn't update when state changes\n- Fix: Add ALL used state variables to deps array\n\nSTATE UPDATE BUGS:\n- setState called but component doesn't re-render\n- Check if state is used in memoized callback with stale closure\n\nPROP DRILLING BUGS:\n- Prop passed but not used in child\n- Prop used but not passed from parent\n\nEVENT HANDLER BUGS:\n- Handler defined but not connected to element\n- Handler connected but wrong event type\n</react-patterns>\n\n<hailer-sdk>\nuseHailerContext() - workspace, user, permissions\nuseActivity(id) - load single activity by ID\nuseWorkflows() - list available workflows\nActivity fields keyed by FIELD ID not label\nAlways handle loading states\nuseToast() for notifications\n</hailer-sdk>\n\n<output-format>\n{\n \"debugTrace\": \"Step-by-step: function X calls Y, which uses state Z, but useCallback deps missing Z\",\n \"failurePoint\": \"File.tsx line N - deps array missing stateVariable\",\n \"rootCause\": \"Stale closure - useCallback keeps old value of stateVariable\",\n \"fix\": {\n \"files\": [{\n \"path\": \"src/components/File.tsx\",\n \"action\": \"edit\",\n \"search\": \"exact code to find\",\n \"replace\": \"fixed code\"\n }]\n },\n \"explanation\": \"Added stateVariable to deps array so callback updates when state changes\",\n \"testSuggestions\": [\"How to verify the fix works\"]\n}\n</output-format>\n\n<rules>\n1. TRACE before fixing - follow the code path\n2. CHECK DEPS ARRAYS - most common React bug\n3. Fix ONE thing - minimal change\n4. Use EXACT file paths from provided code\n5. Search string must EXACTLY match file content\n</rules>";
5
+ //# sourceMappingURL=prompt.d.ts.map