@hailer/mcp 0.2.4 → 0.2.6
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-marketplace-publisher.md +120 -0
- package/.claude/agents/agent-marketplace-reviewer.md +133 -0
- package/.claude/commands/help.md +28 -0
- package/.claude/commands/help:agents.md +71 -0
- package/.claude/commands/help:commands.md +39 -0
- package/.claude/commands/help:faq.md +69 -0
- package/.claude/commands/help:plugins.md +50 -0
- package/.claude/commands/help:tools.md +75 -0
- package/.claude/commands/install-plugin.md +261 -0
- package/.claude/commands/list-plugins.md +42 -0
- package/.claude/commands/marketplace-setup.md +33 -0
- package/.claude/commands/publish-plugin.md +55 -0
- package/.claude/commands/uninstall-plugin.md +87 -0
- package/.opencode/agent/ada.md +35 -0
- package/.opencode/agent/alejandro.md +39 -0
- package/.opencode/agent/bjorn.md +36 -0
- package/.opencode/agent/builder.md +39 -0
- package/.opencode/agent/dmitri.md +40 -0
- package/.opencode/agent/giuseppe.md +37 -0
- package/.opencode/agent/gunther.md +39 -0
- package/.opencode/agent/helga.md +36 -0
- package/.opencode/agent/ingrid.md +39 -0
- package/.opencode/agent/kenji.md +53 -0
- package/.opencode/agent/lars.md +28 -0
- package/.opencode/agent/marketplace-publisher.md +44 -0
- package/.opencode/agent/marketplace-reviewer.md +42 -0
- package/.opencode/agent/nora.md +47 -0
- package/.opencode/agent/svetlana.md +39 -0
- package/.opencode/agent/viktor.md +34 -0
- package/.opencode/agent/yevgeni.md +37 -0
- package/.opencode/commands/help-agents.md +34 -0
- package/.opencode/commands/help-commands.md +32 -0
- package/.opencode/commands/help-faq.md +29 -0
- package/.opencode/commands/help-plugins.md +28 -0
- package/.opencode/commands/help-tools.md +40 -0
- package/.opencode/commands/help.md +22 -0
- package/.opencode/commands/install-plugin.md +16 -0
- package/.opencode/commands/list-plugins.md +9 -0
- package/.opencode/commands/marketplace-setup.md +9 -0
- package/.opencode/commands/publish-plugin.md +19 -0
- package/.opencode/commands/tool-builder.md +27 -0
- package/.opencode/commands/uninstall-plugin.md +16 -0
- package/.opencode/commands/ws-pull.md +19 -0
- package/.opencode/opencode.json +19 -0
- package/CHANGELOG.md +89 -0
- package/CLAUDE.md +83 -36
- package/dist/app.js +15 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +3 -0
- package/dist/core.d.ts +3 -4
- package/dist/core.js +23 -65
- package/dist/mcp/tools/bug-fixer-tools.d.ts +24 -0
- package/dist/mcp/tools/bug-fixer-tools.js +602 -31
- package/dist/mcp/tools/file.d.ts +5 -1
- package/dist/mcp/tools/file.js +122 -4
- package/dist/mcp/utils/hailer-api-client.d.ts +13 -0
- package/dist/mcp/utils/hailer-api-client.js +64 -0
- package/dist/mcp-server.js +6 -4
- package/package.json +1 -1
- package/mcp-system-prompt.txt +0 -127
|
@@ -39,7 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.bugFixerTools = exports.bugFixerPublishAppTool = exports.bugFixerGitRevertTool = exports.bugFixerGitPushTool = exports.bugFixerGitCommitTool = exports.bugFixerGitPullTool = exports.bugFixerGitStatusTool = exports.bugFixerRunBuildTool = exports.bugFixerApplyFixTool = exports.bugFixerWriteFileTool = exports.bugFixerReadFileTool = exports.bugFixerListFilesTool = exports.bugFixerFindAppTool = void 0;
|
|
42
|
+
exports.bugFixerTools = exports.markBugFixedTool = exports.markBugDeclinedTool = exports.bugFixerRetryFixTool = exports.bugFixerPublishFixTool = exports.bugFixerMarkDeclinedTool = exports.bugFixerStartFixTool = exports.bugFixerAnalyzeBugTool = exports.bugFixerPublishAppTool = exports.bugFixerGitRevertTool = exports.bugFixerGitPushTool = exports.bugFixerGitCommitTool = exports.bugFixerGitPullTool = exports.bugFixerGitStatusTool = exports.bugFixerRunBuildTool = exports.bugFixerApplyFixTool = exports.bugFixerWriteFileTool = exports.bugFixerReadFileTool = exports.bugFixerListFilesTool = exports.bugFixerFindAppTool = void 0;
|
|
43
43
|
const zod_1 = require("zod");
|
|
44
44
|
const child_process_1 = require("child_process");
|
|
45
45
|
const fs = __importStar(require("fs/promises"));
|
|
@@ -47,36 +47,76 @@ const path = __importStar(require("path"));
|
|
|
47
47
|
const tool_registry_1 = require("../tool-registry");
|
|
48
48
|
const logger_1 = require("../../lib/logger");
|
|
49
49
|
const config_1 = require("../../config");
|
|
50
|
+
const pending_classification_1 = require("../../agents/bug-fixer/registries/pending-classification");
|
|
51
|
+
const pending_fix_1 = require("../../agents/bug-fixer/registries/pending-fix");
|
|
50
52
|
const logger = (0, logger_1.createLogger)({ component: "bug-fixer-tools" });
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
// Parse all configured app paths
|
|
54
|
+
function getAppPaths() {
|
|
55
|
+
const paths = [];
|
|
56
|
+
// Multiple paths (comma-separated)
|
|
57
|
+
if (config_1.environment.DEV_APPS_PATHS) {
|
|
58
|
+
paths.push(...config_1.environment.DEV_APPS_PATHS.split(',').map(p => p.trim()).filter(p => p.length > 0));
|
|
59
|
+
}
|
|
60
|
+
// Single path (fallback)
|
|
61
|
+
if (config_1.environment.DEV_APPS_PATH && !paths.includes(config_1.environment.DEV_APPS_PATH)) {
|
|
62
|
+
paths.push(config_1.environment.DEV_APPS_PATH);
|
|
63
|
+
}
|
|
64
|
+
// Default to cwd if nothing configured
|
|
65
|
+
if (paths.length === 0) {
|
|
66
|
+
paths.push(process.cwd());
|
|
67
|
+
}
|
|
68
|
+
return paths;
|
|
69
|
+
}
|
|
70
|
+
// Cache for scanned apps
|
|
71
|
+
let appsCache = null;
|
|
72
|
+
// Helper to scan for apps across all configured directories
|
|
53
73
|
async function scanApps() {
|
|
74
|
+
// Return cached if available
|
|
75
|
+
if (appsCache)
|
|
76
|
+
return appsCache;
|
|
54
77
|
const apps = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
78
|
+
const appPaths = getAppPaths();
|
|
79
|
+
for (const basePath of appPaths) {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fs.readdir(basePath, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !["node_modules", "dist"].includes(entry.name)) {
|
|
84
|
+
const appPath = path.join(basePath, entry.name);
|
|
85
|
+
// Try to read manifest for appId
|
|
86
|
+
let manifest = null;
|
|
66
87
|
try {
|
|
67
|
-
await fs.
|
|
68
|
-
|
|
88
|
+
const content = await fs.readFile(path.join(appPath, "manifest.json"), "utf-8");
|
|
89
|
+
manifest = JSON.parse(content);
|
|
69
90
|
}
|
|
70
91
|
catch {
|
|
71
|
-
|
|
92
|
+
try {
|
|
93
|
+
const content = await fs.readFile(path.join(appPath, "public", "manifest.json"), "utf-8");
|
|
94
|
+
manifest = JSON.parse(content);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Not a Hailer app
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (manifest) {
|
|
101
|
+
apps.push({
|
|
102
|
+
name: manifest.name || entry.name,
|
|
103
|
+
path: appPath,
|
|
104
|
+
appId: manifest.appId
|
|
105
|
+
});
|
|
72
106
|
}
|
|
73
107
|
}
|
|
74
108
|
}
|
|
75
109
|
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
logger.warn("Failed to scan apps directory", { path: basePath, error: e });
|
|
112
|
+
}
|
|
76
113
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
// Cache results
|
|
115
|
+
appsCache = apps;
|
|
116
|
+
logger.info("Scanned apps for bug fixer tools", {
|
|
117
|
+
paths: appPaths,
|
|
118
|
+
found: apps.map(a => ({ name: a.name, appId: a.appId?.substring(0, 8) || "none" }))
|
|
119
|
+
});
|
|
80
120
|
return apps;
|
|
81
121
|
}
|
|
82
122
|
// Find App Tool
|
|
@@ -86,13 +126,14 @@ exports.bugFixerFindAppTool = {
|
|
|
86
126
|
description: "Find a Hailer app project by name or from bug title",
|
|
87
127
|
schema: zod_1.z.object({
|
|
88
128
|
searchTerm: zod_1.z.string().describe("App name or bug title to search for").optional(),
|
|
89
|
-
name: zod_1.z.string().describe("Alias for searchTerm - app name to search for").optional()
|
|
90
|
-
|
|
91
|
-
|
|
129
|
+
name: zod_1.z.string().describe("Alias for searchTerm - app name to search for").optional(),
|
|
130
|
+
appName: zod_1.z.string().describe("Alias for searchTerm - app name to search for").optional()
|
|
131
|
+
}).refine(data => data.searchTerm || data.name || data.appName, {
|
|
132
|
+
message: "Either searchTerm, name, or appName is required"
|
|
92
133
|
}),
|
|
93
134
|
async execute(args, _context) {
|
|
94
|
-
// Accept
|
|
95
|
-
const searchTerm = (args.searchTerm || args.name || '').toLowerCase();
|
|
135
|
+
// Accept searchTerm, name, or appName
|
|
136
|
+
const searchTerm = (args.searchTerm || args.name || args.appName || '').toLowerCase();
|
|
96
137
|
const apps = await scanApps();
|
|
97
138
|
for (const app of apps) {
|
|
98
139
|
if (app.name.toLowerCase().includes(searchTerm) ||
|
|
@@ -113,9 +154,14 @@ exports.bugFixerListFilesTool = {
|
|
|
113
154
|
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
114
155
|
description: "List source files in a Hailer app project",
|
|
115
156
|
schema: zod_1.z.object({
|
|
116
|
-
appPath: zod_1.z.string().describe("Path to the app project")
|
|
157
|
+
appPath: zod_1.z.string().describe("Path to the app project").optional(),
|
|
158
|
+
app: zod_1.z.string().describe("Alias for appPath - path to the app project").optional()
|
|
159
|
+
}).refine(data => data.appPath || data.app, {
|
|
160
|
+
message: "Either appPath or app is required"
|
|
117
161
|
}),
|
|
118
162
|
async execute(args, _context) {
|
|
163
|
+
// Accept either appPath or app
|
|
164
|
+
const appPath = args.appPath || args.app || '';
|
|
119
165
|
const files = [];
|
|
120
166
|
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
121
167
|
const ignoreDirs = ["node_modules", "dist", "build", ".git"];
|
|
@@ -126,7 +172,7 @@ exports.bugFixerListFilesTool = {
|
|
|
126
172
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
127
173
|
for (const entry of entries) {
|
|
128
174
|
const fullPath = path.join(dir, entry.name);
|
|
129
|
-
const relativePath = path.relative(
|
|
175
|
+
const relativePath = path.relative(appPath, fullPath);
|
|
130
176
|
if (entry.isDirectory()) {
|
|
131
177
|
if (!ignoreDirs.includes(entry.name) && !entry.name.startsWith(".")) {
|
|
132
178
|
await scan(fullPath, depth + 1);
|
|
@@ -141,7 +187,7 @@ exports.bugFixerListFilesTool = {
|
|
|
141
187
|
}
|
|
142
188
|
catch { /* skip */ }
|
|
143
189
|
};
|
|
144
|
-
await scan(
|
|
190
|
+
await scan(appPath);
|
|
145
191
|
return {
|
|
146
192
|
content: [{ type: "text", text: JSON.stringify({ files: files.slice(0, 50), total: files.length }) }]
|
|
147
193
|
};
|
|
@@ -233,10 +279,21 @@ Example:
|
|
|
233
279
|
}),
|
|
234
280
|
async execute(args, _context) {
|
|
235
281
|
try {
|
|
236
|
-
// Resolve app path
|
|
282
|
+
// Resolve app path - try absolute first, then search in configured directories
|
|
237
283
|
let appFullPath = args.appPath;
|
|
238
284
|
if (!path.isAbsolute(args.appPath)) {
|
|
239
|
-
|
|
285
|
+
// Try to find in scanned apps
|
|
286
|
+
const apps = await scanApps();
|
|
287
|
+
const matchedApp = apps.find(a => a.name.toLowerCase() === args.appPath.toLowerCase() ||
|
|
288
|
+
a.path.endsWith(`/${args.appPath}`));
|
|
289
|
+
if (matchedApp) {
|
|
290
|
+
appFullPath = matchedApp.path;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// Fallback: try first configured path
|
|
294
|
+
const basePaths = getAppPaths();
|
|
295
|
+
appFullPath = path.join(basePaths[0], args.appPath);
|
|
296
|
+
}
|
|
240
297
|
}
|
|
241
298
|
// Check app exists
|
|
242
299
|
try {
|
|
@@ -507,13 +564,518 @@ exports.bugFixerPublishAppTool = {
|
|
|
507
564
|
}
|
|
508
565
|
}
|
|
509
566
|
};
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// HIGH-LEVEL WORKFLOW TOOLS (LLM-driven workflow control)
|
|
569
|
+
// ============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Analyze Bug Tool - Read bug report and get classification info
|
|
572
|
+
* The LLM uses this to understand the bug and decide next steps
|
|
573
|
+
*/
|
|
574
|
+
exports.bugFixerAnalyzeBugTool = {
|
|
575
|
+
name: "bug_fixer_analyze_bug",
|
|
576
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
577
|
+
description: `Read a bug report and get classification information.
|
|
578
|
+
Returns the bug details and classification if already analyzed.
|
|
579
|
+
Use this to understand the current state of a bug discussion.`,
|
|
580
|
+
schema: zod_1.z.object({
|
|
581
|
+
discussionId: zod_1.z.string().describe("The discussion ID for the bug report").optional(),
|
|
582
|
+
activityId: zod_1.z.string().describe("Alias - the bug activity ID (will look up discussion)").optional()
|
|
583
|
+
}).refine(data => data.discussionId || data.activityId, {
|
|
584
|
+
message: "Either discussionId or activityId is required"
|
|
585
|
+
}),
|
|
586
|
+
async execute(args, context) {
|
|
587
|
+
try {
|
|
588
|
+
// Accept either discussionId or look up from activityId
|
|
589
|
+
let discussionId = args.discussionId;
|
|
590
|
+
if (!discussionId && args.activityId) {
|
|
591
|
+
// Try to find discussionId from pending registries by activityId
|
|
592
|
+
const allPending = pending_classification_1.pendingClassificationRegistry.getAll();
|
|
593
|
+
for (const pending of allPending) {
|
|
594
|
+
if (pending.bugId === args.activityId) {
|
|
595
|
+
discussionId = pending.discussionId;
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// If still not found, fetch activity to get discussion
|
|
600
|
+
if (!discussionId) {
|
|
601
|
+
const activity = await context.hailer.fetchActivityById(args.activityId);
|
|
602
|
+
if (activity?.discussion) {
|
|
603
|
+
discussionId = activity.discussion;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (!discussionId) {
|
|
608
|
+
return {
|
|
609
|
+
content: [{
|
|
610
|
+
type: "text",
|
|
611
|
+
text: JSON.stringify({
|
|
612
|
+
status: "error",
|
|
613
|
+
message: "Could not find discussion for the given ID"
|
|
614
|
+
})
|
|
615
|
+
}]
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
// Check if we have pending classification info
|
|
619
|
+
const pending = pending_classification_1.pendingClassificationRegistry.get(discussionId);
|
|
620
|
+
if (pending) {
|
|
621
|
+
return {
|
|
622
|
+
content: [{
|
|
623
|
+
type: "text",
|
|
624
|
+
text: JSON.stringify({
|
|
625
|
+
status: "pending_confirmation",
|
|
626
|
+
bugId: pending.bugId,
|
|
627
|
+
bugName: pending.bugName,
|
|
628
|
+
appId: pending.appId,
|
|
629
|
+
appName: pending.appName,
|
|
630
|
+
classification: pending.classification,
|
|
631
|
+
reason: pending.reason,
|
|
632
|
+
description: pending.bug.description,
|
|
633
|
+
stepsToReproduce: pending.bug.stepsToReproduce,
|
|
634
|
+
message: "Bug has been classified. Waiting for user to confirm whether to proceed with fix."
|
|
635
|
+
})
|
|
636
|
+
}]
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
// Check if we have a pending fix
|
|
640
|
+
const pendingFix = pending_fix_1.pendingFixRegistry.get(args.discussionId);
|
|
641
|
+
if (pendingFix) {
|
|
642
|
+
return {
|
|
643
|
+
content: [{
|
|
644
|
+
type: "text",
|
|
645
|
+
text: JSON.stringify({
|
|
646
|
+
status: pendingFix.state === "awaiting_test" ? "fix_applied" : "awaiting_explanation",
|
|
647
|
+
bugId: pendingFix.bugId,
|
|
648
|
+
appId: pendingFix.appId,
|
|
649
|
+
fixSummary: pendingFix.fixSummary,
|
|
650
|
+
filesModified: pendingFix.filesModified,
|
|
651
|
+
message: pendingFix.state === "awaiting_test"
|
|
652
|
+
? "Fix has been applied. Waiting for user to test and approve."
|
|
653
|
+
: "Fix was rejected. Waiting for user to explain what's wrong."
|
|
654
|
+
})
|
|
655
|
+
}]
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
content: [{
|
|
660
|
+
type: "text",
|
|
661
|
+
text: JSON.stringify({
|
|
662
|
+
status: "not_found",
|
|
663
|
+
message: "No pending bug found for this discussion. The bug may have already been processed."
|
|
664
|
+
})
|
|
665
|
+
}]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
return {
|
|
670
|
+
content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
/**
|
|
676
|
+
* Start Fix Tool - Begin the bug fix process
|
|
677
|
+
* This triggers the full fix workflow: find app → analyze → apply fix → build
|
|
678
|
+
*/
|
|
679
|
+
exports.bugFixerStartFixTool = {
|
|
680
|
+
name: "bug_fixer_start_fix",
|
|
681
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
682
|
+
description: `Start the bug fix process. Call this when the user confirms they want you to fix the bug.
|
|
683
|
+
This will: find the app, analyze the code, generate and apply a fix, and run the build.
|
|
684
|
+
The user should test the fix afterward.`,
|
|
685
|
+
schema: zod_1.z.object({
|
|
686
|
+
discussionId: zod_1.z.string().describe("The discussion ID for the bug report")
|
|
687
|
+
}),
|
|
688
|
+
async execute(args, _context) {
|
|
689
|
+
try {
|
|
690
|
+
const pending = pending_classification_1.pendingClassificationRegistry.get(args.discussionId);
|
|
691
|
+
if (!pending) {
|
|
692
|
+
return {
|
|
693
|
+
content: [{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: JSON.stringify({
|
|
696
|
+
success: false,
|
|
697
|
+
error: "No pending bug classification found for this discussion. Cannot start fix."
|
|
698
|
+
})
|
|
699
|
+
}]
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
// Trigger the fix via the registry callback
|
|
703
|
+
const triggered = await pending_classification_1.pendingClassificationRegistry.triggerFixIt(args.discussionId);
|
|
704
|
+
if (triggered) {
|
|
705
|
+
return {
|
|
706
|
+
content: [{
|
|
707
|
+
type: "text",
|
|
708
|
+
text: JSON.stringify({
|
|
709
|
+
success: true,
|
|
710
|
+
message: "Fix process started. The bot will analyze and apply the fix, then notify the user to test."
|
|
711
|
+
})
|
|
712
|
+
}]
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
return {
|
|
717
|
+
content: [{
|
|
718
|
+
type: "text",
|
|
719
|
+
text: JSON.stringify({
|
|
720
|
+
success: false,
|
|
721
|
+
error: "Failed to trigger fix process. The callback may not be registered."
|
|
722
|
+
})
|
|
723
|
+
}]
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
return {
|
|
729
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
/**
|
|
735
|
+
* Mark Declined Tool - Mark bug as not-a-bug and close it
|
|
736
|
+
*/
|
|
737
|
+
exports.bugFixerMarkDeclinedTool = {
|
|
738
|
+
name: "bug_fixer_mark_declined",
|
|
739
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
740
|
+
description: `Mark a bug report as "not a bug" and move it to the Declined phase.
|
|
741
|
+
Call this when the user confirms it's actually a feature request or not a real bug.`,
|
|
742
|
+
schema: zod_1.z.object({
|
|
743
|
+
discussionId: zod_1.z.string().describe("The discussion ID for the bug report")
|
|
744
|
+
}),
|
|
745
|
+
async execute(args, _context) {
|
|
746
|
+
try {
|
|
747
|
+
const pending = pending_classification_1.pendingClassificationRegistry.get(args.discussionId);
|
|
748
|
+
if (!pending) {
|
|
749
|
+
return {
|
|
750
|
+
content: [{
|
|
751
|
+
type: "text",
|
|
752
|
+
text: JSON.stringify({
|
|
753
|
+
success: false,
|
|
754
|
+
error: "No pending bug classification found for this discussion."
|
|
755
|
+
})
|
|
756
|
+
}]
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
// Trigger the not-a-bug action via the registry callback
|
|
760
|
+
const triggered = await pending_classification_1.pendingClassificationRegistry.triggerNotABug(args.discussionId);
|
|
761
|
+
if (triggered) {
|
|
762
|
+
return {
|
|
763
|
+
content: [{
|
|
764
|
+
type: "text",
|
|
765
|
+
text: JSON.stringify({
|
|
766
|
+
success: true,
|
|
767
|
+
bugId: pending.bugId,
|
|
768
|
+
message: "Bug marked as declined (not a bug). Moved to Declined phase."
|
|
769
|
+
})
|
|
770
|
+
}]
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
return {
|
|
775
|
+
content: [{
|
|
776
|
+
type: "text",
|
|
777
|
+
text: JSON.stringify({
|
|
778
|
+
success: false,
|
|
779
|
+
error: "Failed to decline bug. The callback may not be registered."
|
|
780
|
+
})
|
|
781
|
+
}]
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
catch (e) {
|
|
786
|
+
return {
|
|
787
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }]
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
/**
|
|
793
|
+
* Publish Fix Tool - Publish the fix to production
|
|
794
|
+
*/
|
|
795
|
+
exports.bugFixerPublishFixTool = {
|
|
796
|
+
name: "bug_fixer_publish_fix",
|
|
797
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
798
|
+
description: `Publish a tested fix to production and mark the bug as Fixed.
|
|
799
|
+
Call this ONLY after the user has tested the fix and approved it.`,
|
|
800
|
+
schema: zod_1.z.object({
|
|
801
|
+
discussionId: zod_1.z.string().describe("The discussion ID for the bug report")
|
|
802
|
+
}),
|
|
803
|
+
async execute(args, _context) {
|
|
804
|
+
try {
|
|
805
|
+
// Trigger approval via the registry callback
|
|
806
|
+
const triggered = await pending_fix_1.pendingFixRegistry.triggerApproval(args.discussionId);
|
|
807
|
+
if (triggered) {
|
|
808
|
+
return {
|
|
809
|
+
content: [{
|
|
810
|
+
type: "text",
|
|
811
|
+
text: JSON.stringify({
|
|
812
|
+
success: true,
|
|
813
|
+
message: "Fix is being published to production. This may take a moment."
|
|
814
|
+
})
|
|
815
|
+
}]
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
// Get more info about why it failed
|
|
820
|
+
const pending = pending_fix_1.pendingFixRegistry.get(args.discussionId);
|
|
821
|
+
if (!pending) {
|
|
822
|
+
return {
|
|
823
|
+
content: [{
|
|
824
|
+
type: "text",
|
|
825
|
+
text: JSON.stringify({
|
|
826
|
+
success: false,
|
|
827
|
+
error: "No pending fix found for this discussion. Cannot publish."
|
|
828
|
+
})
|
|
829
|
+
}]
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
if (pending.state !== "awaiting_test") {
|
|
833
|
+
return {
|
|
834
|
+
content: [{
|
|
835
|
+
type: "text",
|
|
836
|
+
text: JSON.stringify({
|
|
837
|
+
success: false,
|
|
838
|
+
error: `Cannot publish - fix is in state '${pending.state}'. User must test and approve first.`
|
|
839
|
+
})
|
|
840
|
+
}]
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
content: [{
|
|
845
|
+
type: "text",
|
|
846
|
+
text: JSON.stringify({
|
|
847
|
+
success: false,
|
|
848
|
+
error: "Failed to trigger publish. The approval callback may not be registered."
|
|
849
|
+
})
|
|
850
|
+
}]
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
catch (e) {
|
|
855
|
+
return {
|
|
856
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }]
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
/**
|
|
862
|
+
* Retry Fix Tool - Retry a fix with additional context/explanation
|
|
863
|
+
*/
|
|
864
|
+
exports.bugFixerRetryFixTool = {
|
|
865
|
+
name: "bug_fixer_retry_fix",
|
|
866
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
867
|
+
description: `Retry fixing a bug with additional context from the user.
|
|
868
|
+
Call this when the user explains why the previous fix didn't work.`,
|
|
869
|
+
schema: zod_1.z.object({
|
|
870
|
+
discussionId: zod_1.z.string().describe("The discussion ID for the bug report"),
|
|
871
|
+
explanation: zod_1.z.string().describe("User's explanation of what's still broken or wrong")
|
|
872
|
+
}),
|
|
873
|
+
async execute(args, _context) {
|
|
874
|
+
try {
|
|
875
|
+
const pending = pending_fix_1.pendingFixRegistry.get(args.discussionId);
|
|
876
|
+
if (!pending) {
|
|
877
|
+
return {
|
|
878
|
+
content: [{
|
|
879
|
+
type: "text",
|
|
880
|
+
text: JSON.stringify({
|
|
881
|
+
success: false,
|
|
882
|
+
error: "No pending fix found for this discussion."
|
|
883
|
+
})
|
|
884
|
+
}]
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
// Trigger retry via the registry callback
|
|
888
|
+
const triggered = await pending_fix_1.pendingFixRegistry.triggerRetry(args.discussionId, args.explanation);
|
|
889
|
+
if (triggered) {
|
|
890
|
+
return {
|
|
891
|
+
content: [{
|
|
892
|
+
type: "text",
|
|
893
|
+
text: JSON.stringify({
|
|
894
|
+
success: true,
|
|
895
|
+
message: "Retry started with user feedback. The bot will generate a new fix."
|
|
896
|
+
})
|
|
897
|
+
}]
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
return {
|
|
902
|
+
content: [{
|
|
903
|
+
type: "text",
|
|
904
|
+
text: JSON.stringify({
|
|
905
|
+
success: false,
|
|
906
|
+
error: "Failed to trigger retry. The callback may not be registered."
|
|
907
|
+
})
|
|
908
|
+
}]
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
catch (e) {
|
|
913
|
+
return {
|
|
914
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: e.message }) }]
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
// Mark Bug Declined Tool - handles everything automatically
|
|
920
|
+
exports.markBugDeclinedTool = {
|
|
921
|
+
name: "mark_bug_declined",
|
|
922
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
923
|
+
description: "Mark a bug as declined (not a bug / won't fix). Automatically sets phase to Declined, status to 'Won't Fix', and leaves the discussion.",
|
|
924
|
+
schema: zod_1.z.object({
|
|
925
|
+
bugId: zod_1.z.string().describe("The bug activity ID"),
|
|
926
|
+
workflowId: zod_1.z.string().describe("The workflow ID for the Bug Reports workflow"),
|
|
927
|
+
discussionId: zod_1.z.string().describe("The discussion ID to leave after marking declined"),
|
|
928
|
+
reason: zod_1.z.string().optional().describe("Optional reason for declining"),
|
|
929
|
+
}),
|
|
930
|
+
async execute(args, context) {
|
|
931
|
+
const { bugId, workflowId, discussionId, reason } = args;
|
|
932
|
+
const { hailer } = context;
|
|
933
|
+
try {
|
|
934
|
+
// 1. Get workflow phases from cached init data (reliable method used by list_workflow_phases)
|
|
935
|
+
const workflowData = context.init.processes?.find((p) => p._id === workflowId);
|
|
936
|
+
if (!workflowData) {
|
|
937
|
+
return {
|
|
938
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
939
|
+
success: false,
|
|
940
|
+
error: `Workflow ${workflowId} not found`
|
|
941
|
+
}) }]
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const phases = workflowData.phases || {};
|
|
945
|
+
const declinedPhase = Object.entries(phases).find(([_, phase]) => phase.name?.toLowerCase() === 'declined');
|
|
946
|
+
if (!declinedPhase) {
|
|
947
|
+
return {
|
|
948
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
949
|
+
success: false,
|
|
950
|
+
error: "Could not find 'Declined' phase in workflow",
|
|
951
|
+
availablePhases: Object.values(phases).map((p) => p.name)
|
|
952
|
+
}) }]
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const [declinedPhaseId, declinedPhaseData] = declinedPhase;
|
|
956
|
+
// 2. Get workflow schema to find status field ID
|
|
957
|
+
const schema = await hailer.getWorkflowSchema(workflowId, declinedPhaseId);
|
|
958
|
+
const statusField = Object.entries(schema.fields || {}).find(([_, f]) => f.key === 'status' || f.label?.toLowerCase() === 'status');
|
|
959
|
+
// 3. Update activity with phase AND status
|
|
960
|
+
const updateData = {
|
|
961
|
+
_id: bugId,
|
|
962
|
+
phaseId: declinedPhaseId,
|
|
963
|
+
};
|
|
964
|
+
if (statusField) {
|
|
965
|
+
updateData.fields = { [statusField[0]]: "Won't Fix" };
|
|
966
|
+
}
|
|
967
|
+
await hailer.updateActivities([updateData]);
|
|
968
|
+
// 4. Leave the discussion
|
|
969
|
+
try {
|
|
970
|
+
await hailer.leaveDiscussion(discussionId);
|
|
971
|
+
}
|
|
972
|
+
catch (e) {
|
|
973
|
+
// Ignore leave errors - might already be out
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
977
|
+
success: true,
|
|
978
|
+
phase: "Declined",
|
|
979
|
+
status: "Won't Fix",
|
|
980
|
+
leftDiscussion: true,
|
|
981
|
+
reason: reason || "Not a bug"
|
|
982
|
+
}) }]
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
return {
|
|
987
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
988
|
+
success: false,
|
|
989
|
+
error: error instanceof Error ? error.message : String(error)
|
|
990
|
+
}) }]
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
// Mark Bug Fixed Tool - handles everything automatically
|
|
996
|
+
exports.markBugFixedTool = {
|
|
997
|
+
name: "mark_bug_fixed",
|
|
998
|
+
group: tool_registry_1.ToolGroup.BOT_INTERNAL,
|
|
999
|
+
description: "Mark a bug as fixed. Automatically sets phase to Fixed, status to 'Fixed', and leaves the discussion. Only use AFTER fix is published and user confirmed it works!",
|
|
1000
|
+
schema: zod_1.z.object({
|
|
1001
|
+
bugId: zod_1.z.string().describe("The bug activity ID"),
|
|
1002
|
+
workflowId: zod_1.z.string().describe("The workflow ID for the Bug Reports workflow"),
|
|
1003
|
+
discussionId: zod_1.z.string().describe("The discussion ID to leave after marking fixed"),
|
|
1004
|
+
fixSummary: zod_1.z.string().optional().describe("Optional summary of what was fixed"),
|
|
1005
|
+
}),
|
|
1006
|
+
async execute(args, context) {
|
|
1007
|
+
const { bugId, workflowId, discussionId, fixSummary } = args;
|
|
1008
|
+
const { hailer } = context;
|
|
1009
|
+
try {
|
|
1010
|
+
// 1. Get workflow phases from cached init data (reliable method used by list_workflow_phases)
|
|
1011
|
+
const workflowData = context.init.processes?.find((p) => p._id === workflowId);
|
|
1012
|
+
if (!workflowData) {
|
|
1013
|
+
return {
|
|
1014
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1015
|
+
success: false,
|
|
1016
|
+
error: `Workflow ${workflowId} not found`
|
|
1017
|
+
}) }]
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
const phases = workflowData.phases || {};
|
|
1021
|
+
const fixedPhase = Object.entries(phases).find(([_, phase]) => phase.name?.toLowerCase() === 'fixed');
|
|
1022
|
+
if (!fixedPhase) {
|
|
1023
|
+
return {
|
|
1024
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1025
|
+
success: false,
|
|
1026
|
+
error: "Could not find 'Fixed' phase in workflow",
|
|
1027
|
+
availablePhases: Object.values(phases).map((p) => p.name)
|
|
1028
|
+
}) }]
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const [fixedPhaseId, fixedPhaseData] = fixedPhase;
|
|
1032
|
+
// 2. Get workflow schema to find status field ID
|
|
1033
|
+
const schema = await hailer.getWorkflowSchema(workflowId, fixedPhaseId);
|
|
1034
|
+
const statusField = Object.entries(schema.fields || {}).find(([_, f]) => f.key === 'status' || f.label?.toLowerCase() === 'status');
|
|
1035
|
+
// 3. Update activity with phase AND status
|
|
1036
|
+
const updateData = {
|
|
1037
|
+
_id: bugId,
|
|
1038
|
+
phaseId: fixedPhaseId,
|
|
1039
|
+
};
|
|
1040
|
+
if (statusField) {
|
|
1041
|
+
updateData.fields = { [statusField[0]]: "Fixed" };
|
|
1042
|
+
}
|
|
1043
|
+
await hailer.updateActivities([updateData]);
|
|
1044
|
+
// 4. Leave the discussion
|
|
1045
|
+
try {
|
|
1046
|
+
await hailer.leaveDiscussion(discussionId);
|
|
1047
|
+
}
|
|
1048
|
+
catch (e) {
|
|
1049
|
+
// Ignore leave errors - might already be out
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1053
|
+
success: true,
|
|
1054
|
+
phase: "Fixed",
|
|
1055
|
+
status: "Fixed",
|
|
1056
|
+
leftDiscussion: true,
|
|
1057
|
+
fixSummary: fixSummary || "Bug fixed"
|
|
1058
|
+
}) }]
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
return {
|
|
1063
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1064
|
+
success: false,
|
|
1065
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1066
|
+
}) }]
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
510
1071
|
// Export all tools
|
|
511
1072
|
exports.bugFixerTools = [
|
|
1073
|
+
// Low-level tools
|
|
512
1074
|
exports.bugFixerFindAppTool,
|
|
513
1075
|
exports.bugFixerListFilesTool,
|
|
514
1076
|
exports.bugFixerReadFileTool,
|
|
515
1077
|
exports.bugFixerWriteFileTool,
|
|
516
|
-
exports.bugFixerApplyFixTool,
|
|
1078
|
+
exports.bugFixerApplyFixTool,
|
|
517
1079
|
exports.bugFixerRunBuildTool,
|
|
518
1080
|
exports.bugFixerGitStatusTool,
|
|
519
1081
|
exports.bugFixerGitPullTool,
|
|
@@ -521,5 +1083,14 @@ exports.bugFixerTools = [
|
|
|
521
1083
|
exports.bugFixerGitPushTool,
|
|
522
1084
|
exports.bugFixerGitRevertTool,
|
|
523
1085
|
exports.bugFixerPublishAppTool,
|
|
1086
|
+
// High-level workflow tools (LLM-driven)
|
|
1087
|
+
exports.bugFixerAnalyzeBugTool,
|
|
1088
|
+
exports.bugFixerStartFixTool,
|
|
1089
|
+
exports.bugFixerMarkDeclinedTool,
|
|
1090
|
+
exports.bugFixerPublishFixTool,
|
|
1091
|
+
exports.bugFixerRetryFixTool,
|
|
1092
|
+
// Standalone phase/status tools (no registry dependency)
|
|
1093
|
+
exports.markBugDeclinedTool,
|
|
1094
|
+
exports.markBugFixedTool,
|
|
524
1095
|
];
|
|
525
1096
|
//# sourceMappingURL=bug-fixer-tools.js.map
|