@arii/boomtick-mcp 0.6.1 → 0.6.2
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/.mcp/schemas/github.create_issue.json +72 -3
- package/.mcp/schemas/github.get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github.get_pr_diff.json +30 -3
- package/.mcp/schemas/github.issue_update.json +72 -3
- package/.mcp/schemas/github.search_open_prs.json +130 -3
- package/.mcp/schemas/github_create_issue.json +72 -3
- package/.mcp/schemas/github_get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github_get_pr_diff.json +30 -3
- package/.mcp/schemas/github_issue_update.json +72 -3
- package/.mcp/schemas/github_search_open_prs.json +130 -3
- package/.mcp/schemas/repo.check_jules_status.json +38 -0
- package/.mcp/schemas/repo.run_tests.json +31 -3
- package/.mcp/schemas/repo_check_jules_status.json +38 -0
- package/.mcp/schemas/repo_run_tests.json +31 -3
- package/CHANGELOG.md +8 -0
- package/README.md +22 -1
- package/actions/run-project-gate/action.yml +1 -1
- package/dist/config/dynamic.js +33 -0
- package/dist/config/github.js +16 -0
- package/dist/config/repo.js +28 -0
- package/dist/config.js +16 -81
- package/dist/http.js +10 -0
- package/dist/mcp/definitions.js +29 -7
- package/dist/mcp/http.js +18 -10
- package/dist/mcp/http.test.js +54 -4
- package/dist/mcp/server.js +35 -10
- package/dist/mcp/server.test.js +28 -1
- package/dist/tools/contract.js +77 -0
- package/dist/tools/github.comment_triage_summary.js +3 -10
- package/dist/tools/github.get_merge_conflict_files.js +4 -2
- package/dist/tools/github.get_pr_diff.js +3 -11
- package/dist/tools/jules/cancel-session.js +2 -9
- package/dist/tools/jules/cli.js +31 -0
- package/dist/tools/jules/get-messages.js +2 -9
- package/dist/tools/jules/list-sessions.js +4 -35
- package/dist/tools/jules/send-message.js +2 -9
- package/dist/tools/jules/session.js +26 -0
- package/dist/tools/jules/shared.js +2 -50
- package/dist/tools/jules/trigger-feedback.js +2 -10
- package/dist/tools/repo.check_jules_status.js +148 -0
- package/dist/tools/repo.check_jules_status.test.js +47 -0
- package/dist/tools/repo.run_tests.js +13 -1
- package/package.json +3 -2
- package/scripts/sync-contracts.ts +11 -2
- package/src/config/dynamic.ts +36 -0
- package/src/config/github.ts +16 -0
- package/src/config/repo.ts +36 -0
- package/src/config.ts +16 -89
- package/src/http.ts +13 -0
- package/src/mcp/definitions.ts +34 -6
- package/src/mcp/http.test.ts +65 -5
- package/src/mcp/http.ts +20 -14
- package/src/mcp/server.test.ts +34 -1
- package/src/mcp/server.ts +36 -11
- package/src/tools/contract.ts +85 -0
- package/src/tools/github.comment_triage_summary.ts +3 -12
- package/src/tools/github.get_merge_conflict_files.ts +5 -2
- package/src/tools/github.get_pr_diff.ts +3 -13
- package/src/tools/jules/cancel-session.ts +2 -11
- package/src/tools/jules/cli.ts +42 -0
- package/src/tools/jules/get-messages.ts +2 -11
- package/src/tools/jules/list-sessions.ts +5 -39
- package/src/tools/jules/send-message.ts +2 -11
- package/src/tools/jules/session.ts +28 -0
- package/src/tools/jules/shared.ts +2 -63
- package/src/tools/jules/trigger-feedback.ts +2 -13
- package/src/tools/repo.check_jules_status.test.ts +55 -0
- package/src/tools/repo.check_jules_status.ts +176 -0
- package/src/tools/repo.run_tests.ts +14 -1
package/dist/mcp/http.js
CHANGED
|
@@ -7,7 +7,7 @@ export function createMcpHttpServer(options) {
|
|
|
7
7
|
const server = http.createServer(async (req, res) => {
|
|
8
8
|
// CORS Headers
|
|
9
9
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
10
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
10
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
|
|
11
11
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
12
12
|
if (req.method === "OPTIONS") {
|
|
13
13
|
res.writeHead(204);
|
|
@@ -15,19 +15,27 @@ export function createMcpHttpServer(options) {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
18
|
-
// Endpoint 1: OAuth Protected Resource
|
|
19
|
-
if (url.pathname === "/.well-known/oauth-protected-resource" && req.method === "GET") {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"authorization_servers": [
|
|
18
|
+
// Endpoint 1: RFC 9728 OAuth Protected Resource Metadata Discovery
|
|
19
|
+
if (url.pathname === "/.well-known/oauth-protected-resource" && (req.method === "GET" || req.method === "HEAD")) {
|
|
20
|
+
const metadata = {
|
|
21
|
+
resource: process.env.MCP_RESOURCE_URI || process.env.BOOMTICK_RESOURCE_URI || "mcp://boomtick-mcp",
|
|
22
|
+
authorization_servers: [
|
|
24
23
|
process.env.MCP_OAUTH_AUTH_SERVER || "https://auth.boomtick.dev",
|
|
25
24
|
],
|
|
26
25
|
scopes_supported: ["mcp:read", "mcp:write"],
|
|
27
|
-
|
|
26
|
+
bearer_methods_supported: ["header"],
|
|
27
|
+
resource_documentation: process.env.MCP_RESOURCE_DOCS || "https://github.com/arii/boomtick",
|
|
28
|
+
};
|
|
29
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
30
|
+
if (req.method === "HEAD") {
|
|
31
|
+
res.end();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
res.end(JSON.stringify(metadata, null, 2));
|
|
35
|
+
}
|
|
28
36
|
return;
|
|
29
37
|
}
|
|
30
|
-
// Bearer Token Validation for
|
|
38
|
+
// Bearer Token / OAuth 2.1 Validation for Protected Routes (/sse & /messages)
|
|
31
39
|
if (url.pathname === "/sse" || url.pathname === "/messages") {
|
|
32
40
|
if (!bearerToken) {
|
|
33
41
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -44,7 +52,7 @@ export function createMcpHttpServer(options) {
|
|
|
44
52
|
if (!isMatch) {
|
|
45
53
|
res.writeHead(401, {
|
|
46
54
|
"Content-Type": "application/json",
|
|
47
|
-
"WWW-Authenticate": 'Bearer realm="boomtick-mcp", error="invalid_token"',
|
|
55
|
+
"WWW-Authenticate": 'Bearer realm="boomtick-mcp", error="invalid_token", error_description="Invalid or missing Bearer token"',
|
|
48
56
|
});
|
|
49
57
|
res.end(JSON.stringify({ error: "unauthorized", message: "Invalid or missing Bearer token" }));
|
|
50
58
|
return;
|
package/dist/mcp/http.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { createMcpHttpServer } from "./http.js";
|
|
4
|
+
import { BoomtickMCPServer } from "./server.js";
|
|
4
5
|
describe("createMcpHttpServer", () => {
|
|
5
6
|
let mcpServer;
|
|
6
7
|
let httpServerObj;
|
|
@@ -13,29 +14,54 @@ describe("createMcpHttpServer", () => {
|
|
|
13
14
|
httpServerObj.server.close();
|
|
14
15
|
}
|
|
15
16
|
});
|
|
16
|
-
it("should respond with OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
|
|
17
|
+
it("should respond with RFC 9728 OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
|
|
17
18
|
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
18
19
|
port = await httpServerObj.listen(0);
|
|
19
20
|
const res = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`);
|
|
20
21
|
expect(res.status).toBe(200);
|
|
22
|
+
expect(res.headers.get("content-type")).toContain("application/json");
|
|
21
23
|
const body = await res.json();
|
|
22
24
|
expect(body.resource).toBe("mcp://boomtick-mcp");
|
|
23
|
-
expect(body.authorization_servers).
|
|
25
|
+
expect(body.authorization_servers).toEqual(["https://auth.boomtick.dev"]);
|
|
24
26
|
expect(body.scopes_supported).toEqual(["mcp:read", "mcp:write"]);
|
|
27
|
+
expect(body.bearer_methods_supported).toEqual(["header"]);
|
|
28
|
+
expect(body.resource_documentation).toBe("https://github.com/arii/boomtick");
|
|
25
29
|
});
|
|
26
|
-
it("should
|
|
30
|
+
it("should handle HEAD requests for /.well-known/oauth-protected-resource", async () => {
|
|
31
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
32
|
+
port = await httpServerObj.listen(0);
|
|
33
|
+
const res = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`, {
|
|
34
|
+
method: "HEAD",
|
|
35
|
+
});
|
|
36
|
+
expect(res.status).toBe(200);
|
|
37
|
+
const text = await res.text();
|
|
38
|
+
expect(text).toBe("");
|
|
39
|
+
});
|
|
40
|
+
it("should handle OPTIONS preflight CORS requests", async () => {
|
|
41
|
+
httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
|
|
42
|
+
port = await httpServerObj.listen(0);
|
|
43
|
+
const res = await fetch(`http://localhost:${port}/sse`, {
|
|
44
|
+
method: "OPTIONS",
|
|
45
|
+
});
|
|
46
|
+
expect(res.status).toBe(204);
|
|
47
|
+
expect(res.headers.get("access-control-allow-origin")).toBe("*");
|
|
48
|
+
expect(res.headers.get("access-control-allow-methods")).toContain("GET");
|
|
49
|
+
expect(res.headers.get("access-control-allow-headers")).toContain("Authorization");
|
|
50
|
+
});
|
|
51
|
+
it("should reject protected requests when Bearer token is missing or invalid", async () => {
|
|
27
52
|
httpServerObj = createMcpHttpServer({ port: 0, bearerToken: "secret123", mcpServer });
|
|
28
53
|
port = await httpServerObj.listen(0);
|
|
29
54
|
// Request without token
|
|
30
55
|
const res1 = await fetch(`http://localhost:${port}/sse`);
|
|
31
56
|
expect(res1.status).toBe(401);
|
|
32
57
|
expect(res1.headers.get("www-authenticate")).toContain('Bearer realm="boomtick-mcp"');
|
|
58
|
+
expect(res1.headers.get("www-authenticate")).toContain('error="invalid_token"');
|
|
33
59
|
// Request with wrong token
|
|
34
60
|
const res2 = await fetch(`http://localhost:${port}/sse`, {
|
|
35
61
|
headers: { Authorization: "Bearer wrong" },
|
|
36
62
|
});
|
|
37
63
|
expect(res2.status).toBe(401);
|
|
38
|
-
//
|
|
64
|
+
// Public resource metadata accessible with or without token
|
|
39
65
|
const res3 = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`, {
|
|
40
66
|
headers: { Authorization: "Bearer secret123" },
|
|
41
67
|
});
|
|
@@ -55,4 +81,28 @@ describe("createMcpHttpServer", () => {
|
|
|
55
81
|
const body = await res.json();
|
|
56
82
|
expect(body.error).toContain("Server misconfiguration");
|
|
57
83
|
});
|
|
84
|
+
it("should return 400 for message requests without session or invalid session", async () => {
|
|
85
|
+
httpServerObj = createMcpHttpServer({ port: 0, bearerToken: "secret123", mcpServer });
|
|
86
|
+
port = await httpServerObj.listen(0);
|
|
87
|
+
const res = await fetch(`http://localhost:${port}/messages?sessionId=invalid_session`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: "Bearer secret123",
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "ping", id: 1 }),
|
|
94
|
+
});
|
|
95
|
+
expect(res.status).toBe(400);
|
|
96
|
+
const body = await res.json();
|
|
97
|
+
expect(body.error).toContain("Session not found or expired");
|
|
98
|
+
});
|
|
99
|
+
it("should allow BoomtickMCPServer to start HTTP server via run() when transport is http", async () => {
|
|
100
|
+
process.env.MCP_TRANSPORT = "http";
|
|
101
|
+
process.env.PORT = "0";
|
|
102
|
+
process.env.BOOMTICK_MCP_BEARER_TOKEN = "test-token";
|
|
103
|
+
const boomtickServer = new BoomtickMCPServer();
|
|
104
|
+
await boomtickServer.run();
|
|
105
|
+
// Verify instance initialized properly
|
|
106
|
+
expect(boomtickServer).toBeInstanceOf(BoomtickMCPServer);
|
|
107
|
+
});
|
|
58
108
|
});
|
package/dist/mcp/server.js
CHANGED
|
@@ -20,6 +20,7 @@ import { getWorkflowRunsHandler, GetWorkflowRunsInputSchema } from "../tools/rep
|
|
|
20
20
|
import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
|
|
21
21
|
import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
|
|
22
22
|
import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
|
|
23
|
+
import { checkJulesStatusHandler, CheckJulesStatusInputSchema } from "../tools/repo.check_jules_status.js";
|
|
23
24
|
import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
|
|
24
25
|
import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
|
|
25
26
|
import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
|
|
@@ -120,6 +121,14 @@ export class BoomtickMCPServer {
|
|
|
120
121
|
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
|
|
121
122
|
};
|
|
122
123
|
}
|
|
124
|
+
if (uri === "mcp://ci/latest-run" ||
|
|
125
|
+
uri === "repo://ci/latest-run" ||
|
|
126
|
+
uri === "repo://ci-latest-run") {
|
|
127
|
+
const workflowRuns = await getWorkflowRunsHandler(GetWorkflowRunsInputSchema.parse({}));
|
|
128
|
+
return {
|
|
129
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(workflowRuns, null, 2) }],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
123
132
|
if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
|
|
124
133
|
const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
|
|
125
134
|
return {
|
|
@@ -201,6 +210,9 @@ export class BoomtickMCPServer {
|
|
|
201
210
|
case "repo.create.repair.branch":
|
|
202
211
|
case "repo.create_repair_branch":
|
|
203
212
|
return createSuccessResult(await createRepairBranchHandler(CreateRepairBranchInputSchema.parse(normArgs)));
|
|
213
|
+
case "repo.check.jules.status":
|
|
214
|
+
case "repo.check_jules_status":
|
|
215
|
+
return createSuccessResult(await checkJulesStatusHandler(CheckJulesStatusInputSchema.parse(normArgs)));
|
|
204
216
|
case "repo.run.tests":
|
|
205
217
|
case "repo.run_tests":
|
|
206
218
|
return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(normArgs)));
|
|
@@ -335,20 +347,33 @@ export class BoomtickMCPServer {
|
|
|
335
347
|
process.exit(1);
|
|
336
348
|
}
|
|
337
349
|
}
|
|
338
|
-
async startHttp(port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10), bearerToken) {
|
|
339
|
-
this.prepareEnvironmentAndPreflight();
|
|
340
|
-
const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
|
|
341
|
-
return await mcpHttp.listen(port);
|
|
342
|
-
}
|
|
343
350
|
async run() {
|
|
344
351
|
this.prepareEnvironmentAndPreflight();
|
|
352
|
+
const args = process.argv.slice(2);
|
|
353
|
+
let port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10);
|
|
354
|
+
let bearerToken = process.env.BOOMTICK_MCP_BEARER_TOKEN || process.env.MCP_BEARER_TOKEN || process.env.AUTH_TOKEN;
|
|
355
|
+
for (let i = 0; i < args.length; i++) {
|
|
356
|
+
if (args[i] === "--port" && i + 1 < args.length) {
|
|
357
|
+
port = parseInt(args[i + 1], 10);
|
|
358
|
+
i++;
|
|
359
|
+
}
|
|
360
|
+
else if (args[i].startsWith("--port=")) {
|
|
361
|
+
port = parseInt(args[i].split("=")[1], 10);
|
|
362
|
+
}
|
|
363
|
+
else if (args[i] === "--bearer-token" && i + 1 < args.length) {
|
|
364
|
+
bearerToken = args[i + 1];
|
|
365
|
+
i++;
|
|
366
|
+
}
|
|
367
|
+
else if (args[i].startsWith("--bearer-token=")) {
|
|
368
|
+
bearerToken = args[i].split("=")[1];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
345
371
|
const isHttp = process.env.MCP_TRANSPORT === "http" ||
|
|
346
|
-
|
|
347
|
-
(process.env.PORT !== undefined && !
|
|
348
|
-
(process.env.HTTP_PORT !== undefined && !
|
|
372
|
+
args.includes("--http") ||
|
|
373
|
+
(process.env.PORT !== undefined && !args.includes("--stdio")) ||
|
|
374
|
+
(process.env.HTTP_PORT !== undefined && !args.includes("--stdio"));
|
|
349
375
|
if (isHttp) {
|
|
350
|
-
const
|
|
351
|
-
const mcpHttp = createMcpHttpServer({ port, mcpServer: this.server });
|
|
376
|
+
const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
|
|
352
377
|
await mcpHttp.listen(port);
|
|
353
378
|
return;
|
|
354
379
|
}
|
package/dist/mcp/server.test.js
CHANGED
|
@@ -79,12 +79,16 @@ describe("BoomtickMCPServer", () => {
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
describe("ListResources", () => {
|
|
82
|
-
it("should return resources list", async () => {
|
|
82
|
+
it("should return resources list containing workspace resources", async () => {
|
|
83
83
|
const handler = requestHandlers.get("resources/list");
|
|
84
84
|
expect(handler).toBeDefined();
|
|
85
85
|
const result = await handler({ method: "resources/list" });
|
|
86
86
|
expect(result.resources).toBeDefined();
|
|
87
87
|
expect(Array.isArray(result.resources)).toBe(true);
|
|
88
|
+
const uris = result.resources.map((r) => r.uri);
|
|
89
|
+
expect(uris).toContain("mcp://workspace/agent-contract");
|
|
90
|
+
expect(uris).toContain("mcp://git/changed-files");
|
|
91
|
+
expect(uris).toContain("mcp://ci/latest-run");
|
|
88
92
|
});
|
|
89
93
|
it("should return resource templates list", async () => {
|
|
90
94
|
const handler = requestHandlers.get("resources/templates/list");
|
|
@@ -150,6 +154,29 @@ describe("BoomtickMCPServer", () => {
|
|
|
150
154
|
expect(result.contents[0].uri).toBe("repo://ci/42");
|
|
151
155
|
expect(JSON.parse(result.contents[0].text)).toEqual({ status: "success" });
|
|
152
156
|
});
|
|
157
|
+
it("should handle mcp://git/changed-files", async () => {
|
|
158
|
+
const handler = requestHandlers.get("resources/read");
|
|
159
|
+
const changedFilesModule = await import("../tools/repo.get_changed_files.js");
|
|
160
|
+
vi.spyOn(changedFilesModule, "getChangedFilesHandler").mockResolvedValue({ files: ["src/index.ts"] });
|
|
161
|
+
const result = await handler({
|
|
162
|
+
method: "resources/read",
|
|
163
|
+
params: { uri: "mcp://git/changed-files" },
|
|
164
|
+
});
|
|
165
|
+
expect(result.contents[0].uri).toBe("mcp://git/changed-files");
|
|
166
|
+
expect(result.contents[0].mimeType).toBe("application/json");
|
|
167
|
+
expect(JSON.parse(result.contents[0].text)).toEqual({ files: ["src/index.ts"] });
|
|
168
|
+
});
|
|
169
|
+
it("should handle mcp://ci/latest-run", async () => {
|
|
170
|
+
const handler = requestHandlers.get("resources/read");
|
|
171
|
+
vi.spyOn(workflowRunsModule, "getWorkflowRunsHandler").mockResolvedValue({ workflowRuns: [{ id: 1, status: "completed", conclusion: "success" }] });
|
|
172
|
+
const result = await handler({
|
|
173
|
+
method: "resources/read",
|
|
174
|
+
params: { uri: "mcp://ci/latest-run" },
|
|
175
|
+
});
|
|
176
|
+
expect(result.contents[0].uri).toBe("mcp://ci/latest-run");
|
|
177
|
+
expect(result.contents[0].mimeType).toBe("application/json");
|
|
178
|
+
expect(JSON.parse(result.contents[0].text)).toEqual({ workflowRuns: [{ id: 1, status: "completed", conclusion: "success" }] });
|
|
179
|
+
});
|
|
153
180
|
it("should throw when resource is unknown", async () => {
|
|
154
181
|
const handler = requestHandlers.get("resources/read");
|
|
155
182
|
await expect(handler({
|
package/dist/tools/contract.js
CHANGED
|
@@ -1564,6 +1564,49 @@ export const GetMergeConflictFilesInputJsonSchema = {
|
|
|
1564
1564
|
"title": "GetMergeConflictFilesInput",
|
|
1565
1565
|
"type": "object"
|
|
1566
1566
|
};
|
|
1567
|
+
export const GetMergeConflictFilesResponseSchema = z.object({ "hasConflicts": z.boolean(), "conflictedFiles": z.array(z.string()).optional(), "prNumber": z.union([z.number().int(), z.null()]).default(null), "baseBranch": z.string(), "headRef": z.string() });
|
|
1568
|
+
export const GetMergeConflictFilesResponseJsonSchema = {
|
|
1569
|
+
"properties": {
|
|
1570
|
+
"hasConflicts": {
|
|
1571
|
+
"title": "Hasconflicts",
|
|
1572
|
+
"type": "boolean"
|
|
1573
|
+
},
|
|
1574
|
+
"conflictedFiles": {
|
|
1575
|
+
"items": {
|
|
1576
|
+
"type": "string"
|
|
1577
|
+
},
|
|
1578
|
+
"title": "Conflictedfiles",
|
|
1579
|
+
"type": "array"
|
|
1580
|
+
},
|
|
1581
|
+
"prNumber": {
|
|
1582
|
+
"anyOf": [
|
|
1583
|
+
{
|
|
1584
|
+
"type": "integer"
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
"type": "null"
|
|
1588
|
+
}
|
|
1589
|
+
],
|
|
1590
|
+
"default": null,
|
|
1591
|
+
"title": "Prnumber"
|
|
1592
|
+
},
|
|
1593
|
+
"baseBranch": {
|
|
1594
|
+
"title": "Basebranch",
|
|
1595
|
+
"type": "string"
|
|
1596
|
+
},
|
|
1597
|
+
"headRef": {
|
|
1598
|
+
"title": "Headref",
|
|
1599
|
+
"type": "string"
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
"required": [
|
|
1603
|
+
"hasConflicts",
|
|
1604
|
+
"baseBranch",
|
|
1605
|
+
"headRef"
|
|
1606
|
+
],
|
|
1607
|
+
"title": "GetMergeConflictFilesResponse",
|
|
1608
|
+
"type": "object"
|
|
1609
|
+
};
|
|
1567
1610
|
export const GetPackageScriptsInputSchema = z.object({ "filter": z.union([z.string(), z.null()]).describe("Optional glob pattern to filter script names.").default(null) });
|
|
1568
1611
|
export const GetPackageScriptsInputJsonSchema = {
|
|
1569
1612
|
"properties": {
|
|
@@ -3086,6 +3129,40 @@ export const RunTestsInputJsonSchema = {
|
|
|
3086
3129
|
"title": "RunTestsInput",
|
|
3087
3130
|
"type": "object"
|
|
3088
3131
|
};
|
|
3132
|
+
export const RunTestsResponseSchema = z.object({ "passed": z.boolean(), "exitCode": z.number().int(), "failedTests": z.array(z.string()).optional(), "results": z.array(z.record(z.string(), z.any())).optional() });
|
|
3133
|
+
export const RunTestsResponseJsonSchema = {
|
|
3134
|
+
"properties": {
|
|
3135
|
+
"passed": {
|
|
3136
|
+
"title": "Passed",
|
|
3137
|
+
"type": "boolean"
|
|
3138
|
+
},
|
|
3139
|
+
"exitCode": {
|
|
3140
|
+
"title": "Exitcode",
|
|
3141
|
+
"type": "integer"
|
|
3142
|
+
},
|
|
3143
|
+
"failedTests": {
|
|
3144
|
+
"items": {
|
|
3145
|
+
"type": "string"
|
|
3146
|
+
},
|
|
3147
|
+
"title": "Failedtests",
|
|
3148
|
+
"type": "array"
|
|
3149
|
+
},
|
|
3150
|
+
"results": {
|
|
3151
|
+
"items": {
|
|
3152
|
+
"additionalProperties": true,
|
|
3153
|
+
"type": "object"
|
|
3154
|
+
},
|
|
3155
|
+
"title": "Results",
|
|
3156
|
+
"type": "array"
|
|
3157
|
+
}
|
|
3158
|
+
},
|
|
3159
|
+
"required": [
|
|
3160
|
+
"passed",
|
|
3161
|
+
"exitCode"
|
|
3162
|
+
],
|
|
3163
|
+
"title": "RunTestsResponse",
|
|
3164
|
+
"type": "object"
|
|
3165
|
+
};
|
|
3089
3166
|
export const SearchDdgsInputSchema = z.object({ "query": z.string().describe("The search query."), "maxResults": z.union([z.number().int(), z.null()]).describe("Maximum number of results to return.").default(null) });
|
|
3090
3167
|
export const SearchDdgsInputJsonSchema = {
|
|
3091
3168
|
"properties": {
|
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./jules/cli.js";
|
|
3
3
|
export const CommentTriageSummaryInputSchema = z.object({
|
|
4
4
|
prNumber: z.number(),
|
|
5
5
|
body: z.string(),
|
|
6
6
|
});
|
|
7
7
|
export async function commentTriageSummaryHandler(args) {
|
|
8
|
-
const
|
|
8
|
+
const output = await runTDCli([
|
|
9
9
|
"gh",
|
|
10
10
|
"post-comment",
|
|
11
11
|
"--pr", args.prNumber.toString(),
|
|
12
12
|
"--body", args.body
|
|
13
|
-
]);
|
|
14
|
-
if (result.exitCode !== 0) {
|
|
15
|
-
throw new Error(`Failed to comment on PR: ${result.stderr}`);
|
|
16
|
-
}
|
|
17
|
-
const output = JSON.parse(result.stdout);
|
|
18
|
-
if (output.status === "error") {
|
|
19
|
-
throw new Error(`Failed to comment on PR: ${output.message}`);
|
|
20
|
-
}
|
|
13
|
+
], "comment on PR");
|
|
21
14
|
return {
|
|
22
15
|
success: true,
|
|
23
16
|
commentUrl: output.html_url || output.url
|
|
@@ -9,10 +9,12 @@ export async function getMergeConflictFilesHandler(args) {
|
|
|
9
9
|
const baseBranch = args.baseBranch || "main";
|
|
10
10
|
const headRef = args.headRef || "HEAD";
|
|
11
11
|
const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
|
|
12
|
+
const hasConflicts = conflictFiles.length > 0;
|
|
12
13
|
return {
|
|
13
|
-
|
|
14
|
+
hasConflicts,
|
|
15
|
+
conflictedFiles: conflictFiles,
|
|
16
|
+
prNumber: args.prNumber ?? null,
|
|
14
17
|
baseBranch,
|
|
15
18
|
headRef,
|
|
16
|
-
conflictFiles,
|
|
17
19
|
};
|
|
18
20
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runCommand } from "../lib/shell.js";
|
|
3
2
|
import { resolveRepo } from "../lib/git.js";
|
|
3
|
+
import { runTDCli } from "./jules/cli.js";
|
|
4
4
|
export const GetPrDiffInputSchema = z.object({
|
|
5
5
|
prNumber: z.number().describe("The number of the pull request to get the diff for."),
|
|
6
6
|
repo: z.string().optional().describe("The target repository override (e.g. org/repo)."),
|
|
@@ -8,17 +8,9 @@ export const GetPrDiffInputSchema = z.object({
|
|
|
8
8
|
export async function getPrDiffHandler(args) {
|
|
9
9
|
const params = GetPrDiffInputSchema.parse(args);
|
|
10
10
|
const targetRepo = resolveRepo(params.repo);
|
|
11
|
-
const
|
|
11
|
+
const output = await runTDCli([
|
|
12
12
|
"gh", "pr-diff", params.prNumber.toString(), "--repo", targetRepo
|
|
13
|
-
]);
|
|
14
|
-
if (result.exitCode !== 0) {
|
|
15
|
-
throw new Error(`Failed to get PR diff: ${result.stderr}`);
|
|
16
|
-
}
|
|
17
|
-
const output = JSON.parse(result.stdout);
|
|
18
|
-
if (output.status === "error") {
|
|
19
|
-
throw new Error(`Failed to get PR diff: ${output.message}`);
|
|
20
|
-
}
|
|
21
|
-
// Every TypeScript tool file must be a pure routing shim
|
|
13
|
+
], "get PR diff");
|
|
22
14
|
return {
|
|
23
15
|
prNumber: output.prNumber,
|
|
24
16
|
files: output.files,
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
export const CancelJulesSessionInputSchema = z.object({
|
|
4
4
|
sessionId: z.string(),
|
|
5
5
|
});
|
|
6
6
|
export async function cancelJulesSessionHandler(input) {
|
|
7
|
-
|
|
8
|
-
if (result.exitCode !== 0) {
|
|
9
|
-
throw new Error(`Failed to cancel session: ${result.stderr}`);
|
|
10
|
-
}
|
|
11
|
-
const output = JSON.parse(result.stdout);
|
|
12
|
-
if (output.status === "error") {
|
|
13
|
-
throw new Error(`Failed to cancel session: ${output.message}`);
|
|
14
|
-
}
|
|
7
|
+
await runTDCli(["agent", "cancel", input.sessionId], "cancel session");
|
|
15
8
|
return {
|
|
16
9
|
id: input.sessionId.replace("sessions/", ""),
|
|
17
10
|
status: "FAILED",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runCommand } from "../../lib/shell.js";
|
|
2
|
+
function tryParseJson(text) {
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(text);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export async function runTDCli(args, actionName) {
|
|
11
|
+
const result = await runCommand("td", args);
|
|
12
|
+
const stdout = result.stdout.trim();
|
|
13
|
+
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
14
|
+
if (output?.status === "error") {
|
|
15
|
+
const errorMsg = output.message ?? "Unknown error";
|
|
16
|
+
if (actionName) {
|
|
17
|
+
throw new Error(`Failed to ${actionName}: ${errorMsg}`);
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`td returned error: ${errorMsg}`);
|
|
20
|
+
}
|
|
21
|
+
if (result.exitCode !== 0) {
|
|
22
|
+
if (actionName) {
|
|
23
|
+
throw new Error(`Failed to ${actionName}: ${result.stderr || stdout}`);
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
26
|
+
}
|
|
27
|
+
if (!output) {
|
|
28
|
+
throw new Error(`td returned non-JSON output with exit code 0: ${stdout}`);
|
|
29
|
+
}
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
export const GetJulesMessagesInputSchema = z.object({
|
|
4
4
|
sessionId: z.string(),
|
|
5
5
|
});
|
|
6
6
|
export async function getJulesMessagesHandler(input) {
|
|
7
|
-
const
|
|
8
|
-
if (result.exitCode !== 0) {
|
|
9
|
-
throw new Error(`Failed to get messages: ${result.stderr}`);
|
|
10
|
-
}
|
|
11
|
-
const output = JSON.parse(result.stdout);
|
|
12
|
-
if (output.status === "error") {
|
|
13
|
-
throw new Error(`Failed to get messages: ${output.message}`);
|
|
14
|
-
}
|
|
7
|
+
const output = await runTDCli(["agent", "messages", input.sessionId], "get messages");
|
|
15
8
|
return {
|
|
16
9
|
id: input.sessionId.replace("sessions/", ""),
|
|
17
10
|
messages: output.messages,
|
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
|
+
import { parseJulesSession } from "./session.js";
|
|
3
4
|
export const ListJulesSessionsInputSchema = z.object({
|
|
4
5
|
pageSize: z.number().optional(),
|
|
5
6
|
pageToken: z.string().optional(),
|
|
6
7
|
});
|
|
7
8
|
export async function listJulesSessionsHandler(input) {
|
|
8
|
-
const
|
|
9
|
-
if (result.exitCode !== 0) {
|
|
10
|
-
throw new Error(`Failed to list sessions: ${result.stderr}`);
|
|
11
|
-
}
|
|
12
|
-
const output = JSON.parse(result.stdout);
|
|
13
|
-
if (output.status === "error") {
|
|
14
|
-
throw new Error(`Failed to list sessions: ${output.message}`);
|
|
15
|
-
}
|
|
9
|
+
const output = await runTDCli(["agent", "sync"], "list sessions");
|
|
16
10
|
const sessions = output.sessions || [];
|
|
17
|
-
return sessions.map((session) =>
|
|
18
|
-
const name = session.name || "";
|
|
19
|
-
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
20
|
-
let status = "PENDING";
|
|
21
|
-
if (session.state === "SUCCEEDED" || session.state === "COMPLETED")
|
|
22
|
-
status = "COMPLETED";
|
|
23
|
-
else if (session.state === "FAILED" || session.state === "CANCELLED" || session.state === "TERMINATED")
|
|
24
|
-
status = "FAILED";
|
|
25
|
-
else if (session.state === "IN_PROGRESS")
|
|
26
|
-
status = "IN_PROGRESS";
|
|
27
|
-
let pullRequestUrl;
|
|
28
|
-
if (session.outputs && Array.isArray(session.outputs)) {
|
|
29
|
-
for (const out of session.outputs) {
|
|
30
|
-
if (out.pullRequest && out.pullRequest.url) {
|
|
31
|
-
pullRequestUrl = out.pullRequest.url;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
id,
|
|
38
|
-
status,
|
|
39
|
-
createdAt: session.createTime ? new Date(session.createTime) : new Date(),
|
|
40
|
-
pullRequestUrl,
|
|
41
|
-
};
|
|
42
|
-
});
|
|
11
|
+
return sessions.map((session) => parseJulesSession(session));
|
|
43
12
|
}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
export const SendJulesMessageInputSchema = z.object({
|
|
4
4
|
sessionId: z.union([z.string(), z.array(z.string())]),
|
|
5
5
|
message: z.string(),
|
|
6
6
|
});
|
|
7
7
|
export async function sendJulesMessageHandler(input) {
|
|
8
8
|
const sessionIds = Array.isArray(input.sessionId) ? input.sessionId.join(",") : input.sessionId;
|
|
9
|
-
|
|
10
|
-
if (result.exitCode !== 0) {
|
|
11
|
-
throw new Error(`Failed to send message: ${result.stderr}`);
|
|
12
|
-
}
|
|
13
|
-
const output = JSON.parse(result.stdout);
|
|
14
|
-
if (output.status === "error") {
|
|
15
|
-
throw new Error(`Failed to send message: ${output.message}`);
|
|
16
|
-
}
|
|
9
|
+
await runTDCli(["agent", "send", sessionIds, input.message], "send message");
|
|
17
10
|
return {
|
|
18
11
|
id: Array.isArray(input.sessionId) ? input.sessionId.map(id => id.replace("sessions/", "")) : input.sessionId.replace("sessions/", ""),
|
|
19
12
|
status: "success",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function parseJulesSession(data) {
|
|
2
|
+
const name = data.name || "";
|
|
3
|
+
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
4
|
+
let status = "PENDING";
|
|
5
|
+
if (data.state === "SUCCEEDED" || data.state === "COMPLETED")
|
|
6
|
+
status = "COMPLETED";
|
|
7
|
+
else if (data.state === "FAILED" || data.state === "CANCELLED" || data.state === "TERMINATED")
|
|
8
|
+
status = "FAILED";
|
|
9
|
+
else if (data.state === "IN_PROGRESS")
|
|
10
|
+
status = "IN_PROGRESS";
|
|
11
|
+
let pullRequestUrl;
|
|
12
|
+
if (data.outputs && Array.isArray(data.outputs)) {
|
|
13
|
+
for (const out of data.outputs) {
|
|
14
|
+
if (out.pullRequest && out.pullRequest.url) {
|
|
15
|
+
pullRequestUrl = out.pullRequest.url;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
status,
|
|
23
|
+
createdAt: data.createTime ? new Date(data.createTime) : new Date(),
|
|
24
|
+
pullRequestUrl,
|
|
25
|
+
};
|
|
26
|
+
}
|