@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.
- 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 +2 -2
- package/dist/modules/bug-reports/giuseppe-bot.js +66 -42
- 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 +84 -13
- package/dist/modules/bug-reports/giuseppe-lsp.js +403 -61
- package/dist/modules/bug-reports/index.d.ts +1 -0
- package/dist/modules/bug-reports/index.js +31 -29
- 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
|