94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
import { type } from "arktype";
|
|
import { formatMcpToolRef } from "../external.ts";
|
|
import type { Mode } from "../modes.ts";
|
|
import type { ToolContext } from "./server.ts";
|
|
import { execute, tool } from "./shared.ts";
|
|
|
|
export const SelectModeParams = type({
|
|
mode: type.string.describe(
|
|
"the name of the mode to select (e.g., 'Build', 'Plan', 'Review', 'IncrementalReview', 'Fix', 'AddressReviews', 'Task', 'ResolveConflicts')"
|
|
),
|
|
"issue_number?": type("number").describe(
|
|
"optional issue number; when provided with Plan mode, used to look up an existing plan comment"
|
|
),
|
|
});
|
|
|
|
function resolveMode(modes: Mode[], modeName: string): Mode | null {
|
|
return modes.find((m) => m.name.toLowerCase() === modeName.toLowerCase()) ?? null;
|
|
}
|
|
|
|
function buildModeOverrides(t: (name: string) => string): Record<string, string> {
|
|
return {
|
|
PlanEdit: `### Checklist (editing existing plan)
|
|
|
|
An existing plan comment was found for this issue. Update that comment with the revised plan — do not create a new plan comment.
|
|
|
|
1. **task list**: create your task list for this run as your first action.
|
|
2. Use \`previousPlanBody\` from this response as the plan to revise; do not call \`get_issue\` or \`get_issue_comments\`.
|
|
3. Revise the plan based on the user's request.
|
|
4. Call \`${t("report_progress")}\` with the full revised plan text and \`{ target_plan_comment: true }\` so it updates the existing plan comment.
|
|
5. Then post a short note to the progress comment via \`${t("report_progress")}\` so it is not left as "Leaping...".`,
|
|
};
|
|
}
|
|
|
|
export function SelectModeTool(ctx: ToolContext) {
|
|
return tool({
|
|
name: "select_mode",
|
|
description:
|
|
"Select the operating mode for this run. Call this first to get the workflow for your task. " +
|
|
"Example: `select_mode({ mode: 'Review' })`.",
|
|
parameters: SelectModeParams,
|
|
execute: execute(async ({ mode, issue_number }) => {
|
|
const t = (toolName: string) => formatMcpToolRef(ctx.agentId, toolName);
|
|
const overrides = buildModeOverrides(t);
|
|
|
|
// find mode in available list
|
|
const foundMode = resolveMode(ctx.modes, mode);
|
|
if (!foundMode) {
|
|
const available = ctx.modes.map((m) => m.name).join(", ");
|
|
throw new Error(
|
|
`Unknown mode "${mode}". Available modes: ${available}`
|
|
);
|
|
}
|
|
|
|
ctx.toolState.selectedMode = foundMode.name;
|
|
|
|
const overrideGuidance = overrides[foundMode.name];
|
|
const hardcoded = overrideGuidance ?? foundMode.prompt ?? "";
|
|
const userInstructions = ctx.modeInstructions[foundMode.name] ?? "";
|
|
const guidance = [hardcoded, userInstructions].filter(Boolean).join("\n\n");
|
|
|
|
const response: Record<string, unknown> = {
|
|
modeName: foundMode.name,
|
|
description: foundMode.description,
|
|
orchestratorGuidance: guidance,
|
|
};
|
|
|
|
// For Plan mode with issue_number, look up existing plan comment
|
|
if (foundMode.name === "Plan" && issue_number !== undefined) {
|
|
try {
|
|
const commentsResp = await ctx.gitea.paginate(ctx.gitea.rest.issue.issueGetComments, {
|
|
owner: ctx.repo.owner,
|
|
repo: ctx.repo.name,
|
|
index: issue_number,
|
|
});
|
|
const comments = commentsResp;
|
|
// Look for a plan comment (one with our footer)
|
|
const planComment = comments.find((c) => c.body?.includes("<!-- shockbot-footer -->"));
|
|
if (planComment) {
|
|
if (planComment.id !== undefined) ctx.toolState.existingPlanCommentId = planComment.id;
|
|
ctx.toolState.previousPlanBody = planComment.body ?? "";
|
|
response.existingPlanCommentFound = true;
|
|
response.previousPlanBody = ctx.toolState.previousPlanBody;
|
|
response.orchestratorGuidance = (overrides["PlanEdit"] ?? guidance) + "\n\n" + (userInstructions ? `\n\n${userInstructions}` : "");
|
|
}
|
|
} catch {
|
|
// Best-effort — if we can't find the plan comment, proceed normally
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}),
|
|
});
|
|
}
|