@arii/boomtick-mcp 0.6.1 → 0.7.0
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/boomtick.health.json +22 -3
- package/.mcp/schemas/boomtick_health.json +22 -3
- package/.mcp/schemas/github.checkout_branch.json +16 -3
- package/.mcp/schemas/github.comment_triage_summary.json +16 -3
- package/.mcp/schemas/github.create_issue.json +72 -3
- package/.mcp/schemas/github.create_pull_request.json +16 -3
- package/.mcp/schemas/github.get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github.get_pr.json +12 -3
- package/.mcp/schemas/github.get_pr_commits.json +12 -3
- package/.mcp/schemas/github.get_pr_diff.json +30 -3
- package/.mcp/schemas/github.get_pr_reviews.json +24 -3
- package/.mcp/schemas/github.issue_comment.json +19 -3
- package/.mcp/schemas/github.issue_update.json +72 -3
- package/.mcp/schemas/github.issue_view.json +17 -3
- package/.mcp/schemas/github.open_replacement_pr.json +16 -3
- package/.mcp/schemas/github.search_open_prs.json +130 -3
- package/.mcp/schemas/github_checkout_branch.json +16 -3
- package/.mcp/schemas/github_comment_triage_summary.json +16 -3
- package/.mcp/schemas/github_create_issue.json +72 -3
- package/.mcp/schemas/github_create_pull_request.json +16 -3
- package/.mcp/schemas/github_get_merge_conflict_files.json +40 -3
- package/.mcp/schemas/github_get_pr.json +12 -3
- package/.mcp/schemas/github_get_pr_commits.json +12 -3
- package/.mcp/schemas/github_get_pr_diff.json +30 -3
- package/.mcp/schemas/github_get_pr_reviews.json +24 -3
- package/.mcp/schemas/github_issue_comment.json +19 -3
- package/.mcp/schemas/github_issue_update.json +72 -3
- package/.mcp/schemas/github_issue_view.json +17 -3
- package/.mcp/schemas/github_open_replacement_pr.json +16 -3
- package/.mcp/schemas/github_search_open_prs.json +130 -3
- package/.mcp/schemas/jules.cancel_session.json +38 -3
- package/.mcp/schemas/jules.create_session.json +52 -3
- package/.mcp/schemas/jules.get_messages.json +19 -3
- package/.mcp/schemas/jules.get_pr.json +23 -3
- package/.mcp/schemas/jules.get_session.json +38 -3
- package/.mcp/schemas/jules.list_sessions.json +53 -3
- package/.mcp/schemas/jules.send_message.json +31 -3
- package/.mcp/schemas/jules.trigger_feedback.json +28 -3
- package/.mcp/schemas/jules_cancel_session.json +38 -3
- package/.mcp/schemas/jules_create_session.json +52 -3
- package/.mcp/schemas/jules_get_messages.json +19 -3
- package/.mcp/schemas/jules_get_pr.json +23 -3
- package/.mcp/schemas/jules_get_session.json +38 -3
- package/.mcp/schemas/jules_list_sessions.json +53 -3
- package/.mcp/schemas/jules_send_message.json +31 -3
- package/.mcp/schemas/jules_trigger_feedback.json +28 -3
- package/.mcp/schemas/repo.check_jules_status.json +128 -0
- package/.mcp/schemas/repo.commit_patch.json +23 -3
- package/.mcp/schemas/repo.create_branch.json +28 -3
- package/.mcp/schemas/repo.create_repair_branch.json +31 -3
- package/.mcp/schemas/repo.get_changed_files.json +11 -3
- package/.mcp/schemas/repo.get_command_schema.json +29 -3
- package/.mcp/schemas/repo.get_package_scripts.json +11 -3
- package/.mcp/schemas/repo.get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo.logs.json +14 -3
- package/.mcp/schemas/repo.multi_exec.json +89 -0
- package/.mcp/schemas/repo.read_agent_context.json +117 -5
- package/.mcp/schemas/repo.read_ci_logs.json +46 -3
- package/.mcp/schemas/repo.run_tests.json +31 -3
- package/.mcp/schemas/repo_check_jules_status.json +128 -0
- package/.mcp/schemas/repo_commit_patch.json +23 -3
- package/.mcp/schemas/repo_create_branch.json +28 -3
- package/.mcp/schemas/repo_create_repair_branch.json +31 -3
- package/.mcp/schemas/repo_get_changed_files.json +11 -3
- package/.mcp/schemas/repo_get_command_schema.json +29 -3
- package/.mcp/schemas/repo_get_package_scripts.json +11 -3
- package/.mcp/schemas/repo_get_workflow_runs.json +12 -3
- package/.mcp/schemas/repo_logs.json +14 -3
- package/.mcp/schemas/repo_multi_exec.json +89 -0
- package/.mcp/schemas/repo_read_agent_context.json +117 -5
- package/.mcp/schemas/repo_read_ci_logs.json +46 -3
- package/.mcp/schemas/repo_run_tests.json +31 -3
- package/CHANGELOG.md +22 -0
- package/README.md +29 -6
- package/actions/run-project-gate/action.yml +1 -1
- package/dist/config/dynamic.js +34 -0
- package/dist/config/github.js +16 -0
- package/dist/config/repo.js +28 -0
- package/dist/config.js +16 -81
- package/dist/config.test.js +10 -3
- package/dist/http.js +10 -0
- package/dist/mcp/definitions.js +76 -41
- package/dist/mcp/http.js +32 -9
- package/dist/mcp/http.test.js +62 -4
- package/dist/mcp/server.js +69 -10
- package/dist/mcp/server.test.js +59 -1
- package/dist/tools/contract.js +1079 -45
- 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/create-session.js +12 -2
- package/dist/tools/jules/create-session.test.js +7 -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.multi_exec.js +21 -0
- package/dist/tools/repo.multi_exec.test.js +13 -0
- package/dist/tools/repo.read_agent_context.js +65 -3
- package/dist/tools/repo.read_agent_context.test.js +30 -1
- 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 +37 -0
- package/src/config/github.ts +16 -0
- package/src/config/repo.ts +36 -0
- package/src/config.test.ts +11 -3
- package/src/config.ts +16 -89
- package/src/http.ts +13 -0
- package/src/mcp/definitions.ts +110 -41
- package/src/mcp/http.test.ts +75 -5
- package/src/mcp/http.ts +40 -14
- package/src/mcp/server.test.ts +72 -1
- package/src/mcp/server.ts +72 -11
- package/src/tools/contract.ts +1194 -36
- 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/create-session.test.ts +9 -0
- package/src/tools/jules/create-session.ts +13 -2
- 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.multi_exec.test.ts +15 -0
- package/src/tools/repo.multi_exec.ts +23 -0
- package/src/tools/repo.read_agent_context.test.ts +35 -1
- package/src/tools/repo.read_agent_context.ts +68 -3
- package/src/tools/repo.run_tests.ts +14 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./jules/cli.js";
|
|
3
3
|
|
|
4
4
|
export const CommentTriageSummaryInputSchema = z.object({
|
|
5
5
|
prNumber: z.number(),
|
|
@@ -7,21 +7,12 @@ export const CommentTriageSummaryInputSchema = z.object({
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
export async function commentTriageSummaryHandler(args: z.infer<typeof CommentTriageSummaryInputSchema>) {
|
|
10
|
-
const
|
|
10
|
+
const output = await runTDCli([
|
|
11
11
|
"gh",
|
|
12
12
|
"post-comment",
|
|
13
13
|
"--pr", args.prNumber.toString(),
|
|
14
14
|
"--body", args.body
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
if (result.exitCode !== 0) {
|
|
18
|
-
throw new Error(`Failed to comment on PR: ${result.stderr}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const output = JSON.parse(result.stdout);
|
|
22
|
-
if (output.status === "error") {
|
|
23
|
-
throw new Error(`Failed to comment on PR: ${output.message}`);
|
|
24
|
-
}
|
|
15
|
+
], "comment on PR");
|
|
25
16
|
|
|
26
17
|
return {
|
|
27
18
|
success: true,
|
|
@@ -12,10 +12,13 @@ export async function getMergeConflictFilesHandler(args: z.infer<typeof GetMerge
|
|
|
12
12
|
const headRef = args.headRef || "HEAD";
|
|
13
13
|
const conflictFiles = await getMergeConflictFiles(baseBranch, headRef);
|
|
14
14
|
|
|
15
|
+
const hasConflicts = conflictFiles.length > 0;
|
|
16
|
+
|
|
15
17
|
return {
|
|
16
|
-
|
|
18
|
+
hasConflicts,
|
|
19
|
+
conflictedFiles: conflictFiles,
|
|
20
|
+
prNumber: args.prNumber ?? null,
|
|
17
21
|
baseBranch,
|
|
18
22
|
headRef,
|
|
19
|
-
conflictFiles,
|
|
20
23
|
};
|
|
21
24
|
}
|
|
@@ -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
|
|
|
5
5
|
export const GetPrDiffInputSchema = z.object({
|
|
6
6
|
prNumber: z.number().describe("The number of the pull request to get the diff for."),
|
|
@@ -11,20 +11,10 @@ export async function getPrDiffHandler(args: z.infer<typeof GetPrDiffInputSchema
|
|
|
11
11
|
const params = GetPrDiffInputSchema.parse(args);
|
|
12
12
|
const targetRepo = resolveRepo(params.repo);
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const output = await runTDCli([
|
|
15
15
|
"gh", "pr-diff", params.prNumber.toString(), "--repo", targetRepo
|
|
16
|
-
]);
|
|
16
|
+
], "get PR diff");
|
|
17
17
|
|
|
18
|
-
if (result.exitCode !== 0) {
|
|
19
|
-
throw new Error(`Failed to get PR diff: ${result.stderr}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const output = JSON.parse(result.stdout);
|
|
23
|
-
if (output.status === "error") {
|
|
24
|
-
throw new Error(`Failed to get PR diff: ${output.message}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Every TypeScript tool file must be a pure routing shim
|
|
28
18
|
return {
|
|
29
19
|
prNumber: output.prNumber,
|
|
30
20
|
files: output.files,
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { JulesSession } from "../types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { runTDCli } from "./cli.js";
|
|
4
4
|
|
|
5
5
|
export const CancelJulesSessionInputSchema = z.object({
|
|
6
6
|
sessionId: z.string(),
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
export async function cancelJulesSessionHandler(input: z.infer<typeof CancelJulesSessionInputSchema>): Promise<JulesSession> {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (result.exitCode !== 0) {
|
|
13
|
-
throw new Error(`Failed to cancel session: ${result.stderr}`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const output = JSON.parse(result.stdout);
|
|
17
|
-
if (output.status === "error") {
|
|
18
|
-
throw new Error(`Failed to cancel session: ${output.message}`);
|
|
19
|
-
}
|
|
10
|
+
await runTDCli(["agent", "cancel", input.sessionId], "cancel session");
|
|
20
11
|
|
|
21
12
|
return {
|
|
22
13
|
id: input.sessionId.replace("sessions/", ""),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { runCommand } from "../../lib/shell.js";
|
|
2
|
+
|
|
3
|
+
export interface TDCliResponse {
|
|
4
|
+
status?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function tryParseJson(text: string): TDCliResponse | null {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(text) as TDCliResponse;
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runTDCli(args: string[], actionName?: string): Promise<TDCliResponse> {
|
|
18
|
+
const result = await runCommand("td", args);
|
|
19
|
+
const stdout = result.stdout.trim();
|
|
20
|
+
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
21
|
+
|
|
22
|
+
if (output?.status === "error") {
|
|
23
|
+
const errorMsg = output.message ?? "Unknown error";
|
|
24
|
+
if (actionName) {
|
|
25
|
+
throw new Error(`Failed to ${actionName}: ${errorMsg}`);
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`td returned error: ${errorMsg}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (result.exitCode !== 0) {
|
|
31
|
+
if (actionName) {
|
|
32
|
+
throw new Error(`Failed to ${actionName}: ${result.stderr || stdout}`);
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!output) {
|
|
38
|
+
throw new Error(`td returned non-JSON output with exit code 0: ${stdout}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return output;
|
|
42
|
+
}
|
|
@@ -49,6 +49,15 @@ describe("createJulesSessionHandler", () => {
|
|
|
49
49
|
]);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
it("should create a session with repo parameter", async () => {
|
|
53
|
+
const result = await createJulesSessionHandler({ task: "do work", branch: "main", repo: "arii/portfolio" });
|
|
54
|
+
expect(result.id).toBe("1234567890");
|
|
55
|
+
|
|
56
|
+
expect(shell.runCommand).toHaveBeenCalledWith("td", [
|
|
57
|
+
"agent", "dispatch", "--repo", "arii/portfolio", "main", "do work"
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
52
61
|
it("should create a session with PR number", async () => {
|
|
53
62
|
vi.mocked(shell.runCommand).mockImplementation(async (cmd, args) => {
|
|
54
63
|
if (args[1] === "view") {
|
|
@@ -5,12 +5,17 @@ export const CreateJulesSessionInputSchema = z.object({
|
|
|
5
5
|
task: z.string(),
|
|
6
6
|
branch: z.string().optional(),
|
|
7
7
|
pr: z.number().optional(),
|
|
8
|
+
repo: z.string().optional(),
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
export async function createJulesSessionHandler(input: z.infer<typeof CreateJulesSessionInputSchema>) {
|
|
11
12
|
let branch = input.branch;
|
|
12
13
|
if (!branch && input.pr) {
|
|
13
|
-
const
|
|
14
|
+
const ghArgs = ["gh", "view", input.pr.toString()];
|
|
15
|
+
if (input.repo) {
|
|
16
|
+
ghArgs.push("--repo", input.repo);
|
|
17
|
+
}
|
|
18
|
+
const prData = await runTDCli(ghArgs);
|
|
14
19
|
branch = prData.pr.headRefName;
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -18,6 +23,12 @@ export async function createJulesSessionHandler(input: z.infer<typeof CreateJule
|
|
|
18
23
|
branch = process.env.DEFAULT_BASE_BRANCH || "main";
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const
|
|
26
|
+
const dispatchArgs = ["agent", "dispatch"];
|
|
27
|
+
if (input.repo) {
|
|
28
|
+
dispatchArgs.push("--repo", input.repo);
|
|
29
|
+
}
|
|
30
|
+
dispatchArgs.push(branch, input.task);
|
|
31
|
+
|
|
32
|
+
const output = await runTDCli(dispatchArgs);
|
|
22
33
|
return parseJulesSession(output.session || output);
|
|
23
34
|
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
|
|
4
4
|
export const GetJulesMessagesInputSchema = z.object({
|
|
5
5
|
sessionId: z.string(),
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
export async function getJulesMessagesHandler(input: z.infer<typeof GetJulesMessagesInputSchema>) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
if (result.exitCode !== 0) {
|
|
12
|
-
throw new Error(`Failed to get messages: ${result.stderr}`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const output = JSON.parse(result.stdout);
|
|
16
|
-
if (output.status === "error") {
|
|
17
|
-
throw new Error(`Failed to get messages: ${output.message}`);
|
|
18
|
-
}
|
|
9
|
+
const output = await runTDCli(["agent", "messages", input.sessionId], "get messages");
|
|
19
10
|
|
|
20
11
|
return {
|
|
21
12
|
id: input.sessionId.replace("sessions/", ""),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { JulesSession
|
|
3
|
-
import {
|
|
2
|
+
import { JulesSession } from "../types.js";
|
|
3
|
+
import { runTDCli } from "./cli.js";
|
|
4
|
+
import { parseJulesSession } from "./session.js";
|
|
4
5
|
|
|
5
6
|
export const ListJulesSessionsInputSchema = z.object({
|
|
6
7
|
pageSize: z.number().optional(),
|
|
@@ -8,43 +9,8 @@ export const ListJulesSessionsInputSchema = z.object({
|
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
export async function listJulesSessionsHandler(input: z.infer<typeof ListJulesSessionsInputSchema>): Promise<JulesSession[]> {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
if (result.exitCode !== 0) {
|
|
14
|
-
throw new Error(`Failed to list sessions: ${result.stderr}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const output = JSON.parse(result.stdout);
|
|
18
|
-
if (output.status === "error") {
|
|
19
|
-
throw new Error(`Failed to list sessions: ${output.message}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
12
|
+
const output = await runTDCli(["agent", "sync"], "list sessions");
|
|
22
13
|
const sessions = output.sessions || [];
|
|
23
14
|
|
|
24
|
-
return sessions.map((session: any) =>
|
|
25
|
-
const name = session.name || "";
|
|
26
|
-
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
27
|
-
|
|
28
|
-
let status: JulesStatus = "PENDING";
|
|
29
|
-
if (session.state === "SUCCEEDED" || session.state === "COMPLETED") status = "COMPLETED";
|
|
30
|
-
else if (session.state === "FAILED" || session.state === "CANCELLED" || session.state === "TERMINATED") status = "FAILED";
|
|
31
|
-
else if (session.state === "IN_PROGRESS") status = "IN_PROGRESS";
|
|
32
|
-
|
|
33
|
-
let pullRequestUrl: string | undefined;
|
|
34
|
-
if (session.outputs && Array.isArray(session.outputs)) {
|
|
35
|
-
for (const out of session.outputs) {
|
|
36
|
-
if (out.pullRequest && out.pullRequest.url) {
|
|
37
|
-
pullRequestUrl = out.pullRequest.url;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
id,
|
|
45
|
-
status,
|
|
46
|
-
createdAt: session.createTime ? new Date(session.createTime) : new Date(),
|
|
47
|
-
pullRequestUrl,
|
|
48
|
-
};
|
|
49
|
-
});
|
|
15
|
+
return sessions.map((session: any) => parseJulesSession(session));
|
|
50
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
|
|
4
4
|
export const SendJulesMessageInputSchema = z.object({
|
|
5
5
|
sessionId: z.union([z.string(), z.array(z.string())]),
|
|
@@ -8,16 +8,7 @@ export const SendJulesMessageInputSchema = z.object({
|
|
|
8
8
|
|
|
9
9
|
export async function sendJulesMessageHandler(input: z.infer<typeof SendJulesMessageInputSchema>) {
|
|
10
10
|
const sessionIds = Array.isArray(input.sessionId) ? input.sessionId.join(",") : input.sessionId;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (result.exitCode !== 0) {
|
|
14
|
-
throw new Error(`Failed to send message: ${result.stderr}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const output = JSON.parse(result.stdout);
|
|
18
|
-
if (output.status === "error") {
|
|
19
|
-
throw new Error(`Failed to send message: ${output.message}`);
|
|
20
|
-
}
|
|
11
|
+
await runTDCli(["agent", "send", sessionIds, input.message], "send message");
|
|
21
12
|
|
|
22
13
|
return {
|
|
23
14
|
id: Array.isArray(input.sessionId) ? input.sessionId.map(id => id.replace("sessions/", "")) : input.sessionId.replace("sessions/", ""),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { JulesSession, JulesStatus } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export function parseJulesSession(data: any): JulesSession {
|
|
4
|
+
const name = data.name || "";
|
|
5
|
+
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
6
|
+
|
|
7
|
+
let status: JulesStatus = "PENDING";
|
|
8
|
+
if (data.state === "SUCCEEDED" || data.state === "COMPLETED") status = "COMPLETED";
|
|
9
|
+
else if (data.state === "FAILED" || data.state === "CANCELLED" || data.state === "TERMINATED") status = "FAILED";
|
|
10
|
+
else if (data.state === "IN_PROGRESS") status = "IN_PROGRESS";
|
|
11
|
+
|
|
12
|
+
let pullRequestUrl: string | undefined;
|
|
13
|
+
if (data.outputs && Array.isArray(data.outputs)) {
|
|
14
|
+
for (const out of data.outputs) {
|
|
15
|
+
if (out.pullRequest && out.pullRequest.url) {
|
|
16
|
+
pullRequestUrl = out.pullRequest.url;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
status,
|
|
25
|
+
createdAt: data.createTime ? new Date(data.createTime) : new Date(),
|
|
26
|
+
pullRequestUrl,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -1,63 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface TDCliResponse {
|
|
5
|
-
status?: string;
|
|
6
|
-
message?: string;
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function tryParseJson(text: string): TDCliResponse | null {
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(text) as TDCliResponse;
|
|
13
|
-
} catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function runTDCli(args: string[]): Promise<TDCliResponse> {
|
|
19
|
-
const result = await runCommand("td", args);
|
|
20
|
-
const stdout = result.stdout.trim();
|
|
21
|
-
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
22
|
-
|
|
23
|
-
if (output?.status === "error") {
|
|
24
|
-
throw new Error(`td returned error: ${output.message ?? "Unknown error"}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (result.exitCode !== 0) {
|
|
28
|
-
throw new Error(`td command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!output) {
|
|
32
|
-
throw new Error(`td returned non-JSON output with exit code 0: ${stdout}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return output;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function parseJulesSession(data: any): JulesSession {
|
|
39
|
-
const name = data.name || "";
|
|
40
|
-
const id = name.startsWith("sessions/") ? name.substring(9) : name;
|
|
41
|
-
|
|
42
|
-
let status: JulesStatus = "PENDING";
|
|
43
|
-
if (data.state === "SUCCEEDED" || data.state === "COMPLETED") status = "COMPLETED";
|
|
44
|
-
else if (data.state === "FAILED" || data.state === "CANCELLED" || data.state === "TERMINATED") status = "FAILED";
|
|
45
|
-
else if (data.state === "IN_PROGRESS") status = "IN_PROGRESS";
|
|
46
|
-
|
|
47
|
-
let pullRequestUrl: string | undefined;
|
|
48
|
-
if (data.outputs && Array.isArray(data.outputs)) {
|
|
49
|
-
for (const out of data.outputs) {
|
|
50
|
-
if (out.pullRequest && out.pullRequest.url) {
|
|
51
|
-
pullRequestUrl = out.pullRequest.url;
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
id,
|
|
59
|
-
status,
|
|
60
|
-
createdAt: data.createTime ? new Date(data.createTime) : new Date(),
|
|
61
|
-
pullRequestUrl,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
1
|
+
export * from "./cli.js";
|
|
2
|
+
export * from "./session.js";
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { runTDCli } from "./cli.js";
|
|
3
3
|
|
|
4
4
|
export const TriggerJulesFeedbackInputSchema = z.object({
|
|
5
5
|
sessionId: z.string(),
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
export async function triggerJulesFeedbackHandler(input: z.infer<typeof TriggerJulesFeedbackInputSchema>) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (result.exitCode !== 0) {
|
|
12
|
-
throw new Error(`Failed to trigger feedback: ${result.stderr}`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const output = JSON.parse(result.stdout);
|
|
16
|
-
if (output.status === "error") {
|
|
17
|
-
throw new Error(`Failed to trigger feedback: ${output.message}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return output;
|
|
9
|
+
return await runTDCli(["agent", "trigger-feedback", input.sessionId], "trigger feedback");
|
|
21
10
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { checkJulesStatusHandler } from "./repo.check_jules_status.js";
|
|
3
|
+
|
|
4
|
+
describe("repo.check_jules_status MCP Tool Handler", () => {
|
|
5
|
+
const originalEnv = process.env;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.clearAllMocks();
|
|
9
|
+
process.env = { ...originalEnv };
|
|
10
|
+
delete process.env.TASK_ID;
|
|
11
|
+
delete process.env.JULES_API_KEY;
|
|
12
|
+
global.fetch = vi.fn();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
process.env = originalEnv;
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns NOT_FOUND status if no session ID or JULES_API_KEY is available", async () => {
|
|
21
|
+
const result = await checkJulesStatusHandler({});
|
|
22
|
+
expect(result.status).toBe("NOT_FOUND");
|
|
23
|
+
expect(result.completed).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("checks Jules session status and returns response object", async () => {
|
|
27
|
+
process.env.JULES_API_KEY = "test-api-key";
|
|
28
|
+
|
|
29
|
+
vi.mocked(global.fetch).mockResolvedValue({
|
|
30
|
+
ok: true,
|
|
31
|
+
json: vi.fn().mockResolvedValue({
|
|
32
|
+
name: "sessions/test-session-123",
|
|
33
|
+
state: "SUCCEEDED",
|
|
34
|
+
outputs: [
|
|
35
|
+
{
|
|
36
|
+
pullRequest: {
|
|
37
|
+
url: "https://github.com/org/repo/pull/100",
|
|
38
|
+
branch: "remediation-branch-123",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
} as any);
|
|
44
|
+
|
|
45
|
+
const result = await checkJulesStatusHandler({
|
|
46
|
+
sessionId: "test-session-123",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result.sessionId).toBe("sessions/test-session-123");
|
|
50
|
+
expect(result.status).toBe("COMPLETED");
|
|
51
|
+
expect(result.completed).toBe(true);
|
|
52
|
+
expect(result.remediationBranch).toBe("remediation-branch-123");
|
|
53
|
+
expect(result.pullRequestUrl).toBe("https://github.com/org/repo/pull/100");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as crypto from "crypto";
|
|
5
|
+
import { config } from "../config.js";
|
|
6
|
+
|
|
7
|
+
export interface JulesStatusResult {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
status: "COMPLETED" | "FAILED" | "IN_PROGRESS" | "TIMED_OUT" | "NOT_FOUND";
|
|
10
|
+
rawState?: string;
|
|
11
|
+
completed: boolean;
|
|
12
|
+
remediationBranch?: string;
|
|
13
|
+
pullRequestUrl?: string;
|
|
14
|
+
idempotencyHash?: string;
|
|
15
|
+
isDuplicate?: boolean;
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CheckJulesStatusInputSchema = z.object({
|
|
20
|
+
sessionId: z.string().optional().describe("The unique ID or task ID of the Jules session."),
|
|
21
|
+
commitSha: z.string().optional().describe("Commit SHA for idempotency key verification."),
|
|
22
|
+
failureLog: z.string().optional().describe("Failure log content for idempotency key calculation."),
|
|
23
|
+
poll: z.boolean().optional().describe("Whether to poll until session completion or timeout."),
|
|
24
|
+
timeoutMs: z.number().int().optional().describe("Timeout in milliseconds when polling."),
|
|
25
|
+
intervalMs: z.number().int().optional().describe("Polling interval in milliseconds."),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function calculateHash(commitSha: string, failureLog: string): string {
|
|
29
|
+
const hash = crypto.createHash("sha256");
|
|
30
|
+
return hash.update(`${commitSha}:${failureLog.trim()}`).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function checkJulesStatusHandler(input: z.infer<typeof CheckJulesStatusInputSchema>): Promise<JulesStatusResult> {
|
|
34
|
+
let resolvedTaskId = input.sessionId || process.env.TASK_ID;
|
|
35
|
+
|
|
36
|
+
if (!resolvedTaskId) {
|
|
37
|
+
return {
|
|
38
|
+
sessionId: "",
|
|
39
|
+
status: "NOT_FOUND",
|
|
40
|
+
completed: false,
|
|
41
|
+
message: "No session ID provided.",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sessionId = resolvedTaskId.startsWith("sessions/") ? resolvedTaskId : `sessions/${resolvedTaskId}`;
|
|
46
|
+
const apiKey = process.env.JULES_API_KEY;
|
|
47
|
+
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
return {
|
|
50
|
+
sessionId,
|
|
51
|
+
status: "NOT_FOUND",
|
|
52
|
+
completed: false,
|
|
53
|
+
message: "JULES_API_KEY environment variable not set.",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const commitSha = input.commitSha || process.env.GITHUB_SHA || process.env.COMMIT_SHA;
|
|
58
|
+
let idempotencyHash: string | undefined;
|
|
59
|
+
let isDuplicate = false;
|
|
60
|
+
|
|
61
|
+
if (commitSha && input.failureLog) {
|
|
62
|
+
idempotencyHash = calculateHash(commitSha, input.failureLog);
|
|
63
|
+
const artifactsDir = path.join(config.repoPath || process.cwd(), "artifacts");
|
|
64
|
+
const hashTrackingFile = path.join(artifactsDir, `.jules-dispatch-${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.hash`);
|
|
65
|
+
if (fs.existsSync(hashTrackingFile)) {
|
|
66
|
+
try {
|
|
67
|
+
const existingHash = fs.readFileSync(hashTrackingFile, "utf8").trim();
|
|
68
|
+
if (existingHash === idempotencyHash) {
|
|
69
|
+
isDuplicate = true;
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn("⚠️ Could not read previous Jules dispatch hash file during status check:", e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const url = `https://jules.googleapis.com/v1alpha/${sessionId}`;
|
|
78
|
+
const poll = input.poll ?? false;
|
|
79
|
+
const timeoutMs = input.timeoutMs ?? 60000;
|
|
80
|
+
const intervalMs = input.intervalMs ?? 5000;
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
|
|
83
|
+
const fetchSession = async () => {
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
"x-goog-api-key": apiKey,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return await response.json();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
while (true) {
|
|
98
|
+
let sessionData: any = null;
|
|
99
|
+
try {
|
|
100
|
+
sessionData = await fetchSession();
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.warn(`⚠️ Error fetching Jules session status for ${sessionId}:`, e);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!sessionData) {
|
|
106
|
+
return {
|
|
107
|
+
sessionId,
|
|
108
|
+
status: "NOT_FOUND",
|
|
109
|
+
completed: false,
|
|
110
|
+
idempotencyHash,
|
|
111
|
+
isDuplicate,
|
|
112
|
+
message: `Failed to retrieve session data for ${sessionId}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const rawState = sessionData.state || "UNKNOWN";
|
|
117
|
+
let status: JulesStatusResult["status"] = "IN_PROGRESS";
|
|
118
|
+
if (rawState === "SUCCEEDED" || rawState === "COMPLETED") {
|
|
119
|
+
status = "COMPLETED";
|
|
120
|
+
} else if (rawState === "FAILED" || rawState === "CANCELLED" || rawState === "TERMINATED") {
|
|
121
|
+
status = "FAILED";
|
|
122
|
+
} else if (rawState === "IN_PROGRESS" || rawState === "PENDING") {
|
|
123
|
+
status = "IN_PROGRESS";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let remediationBranch: string | undefined;
|
|
127
|
+
let pullRequestUrl: string | undefined;
|
|
128
|
+
|
|
129
|
+
if (sessionData.sourceContext?.githubRepoContext?.startingBranch) {
|
|
130
|
+
remediationBranch = sessionData.sourceContext.githubRepoContext.startingBranch;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(sessionData.outputs)) {
|
|
134
|
+
for (const output of sessionData.outputs) {
|
|
135
|
+
if (output?.pullRequest?.url) {
|
|
136
|
+
pullRequestUrl = output.pullRequest.url;
|
|
137
|
+
}
|
|
138
|
+
if (output?.pullRequest?.branch || output?.gitBranch) {
|
|
139
|
+
remediationBranch = output?.pullRequest?.branch || output?.gitBranch;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const completed = status === "COMPLETED";
|
|
145
|
+
|
|
146
|
+
if (!poll || status === "COMPLETED" || status === "FAILED") {
|
|
147
|
+
return {
|
|
148
|
+
sessionId,
|
|
149
|
+
status,
|
|
150
|
+
rawState,
|
|
151
|
+
completed,
|
|
152
|
+
remediationBranch,
|
|
153
|
+
pullRequestUrl,
|
|
154
|
+
idempotencyHash,
|
|
155
|
+
isDuplicate,
|
|
156
|
+
message: `Session state: ${rawState}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (Date.now() - startTime >= timeoutMs) {
|
|
161
|
+
return {
|
|
162
|
+
sessionId,
|
|
163
|
+
status: "TIMED_OUT",
|
|
164
|
+
rawState,
|
|
165
|
+
completed: false,
|
|
166
|
+
remediationBranch,
|
|
167
|
+
pullRequestUrl,
|
|
168
|
+
idempotencyHash,
|
|
169
|
+
isDuplicate,
|
|
170
|
+
message: `Polling timed out after ${timeoutMs}ms (current state: ${rawState})`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { multiExecHandler } from "./repo.multi_exec.js";
|
|
3
|
+
|
|
4
|
+
describe("MultiExec Tool", () => {
|
|
5
|
+
it("should execute a command sequentially across repositories", async () => {
|
|
6
|
+
const res = await multiExecHandler({
|
|
7
|
+
command: "echo test-vitest-exec",
|
|
8
|
+
repos: ["arii/boomtick"],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(res.status).toBe("success");
|
|
12
|
+
expect(res.total_repos).toBe(1);
|
|
13
|
+
expect(res.results[0].stdout).toContain("test-vitest-exec");
|
|
14
|
+
});
|
|
15
|
+
});
|