Spaces:
No application file
No application file
| /** | |
| * Issueboard MCP - Manages issues and workflow during PBL generation. | |
| * | |
| * Migrated from PBL-Nano. Key changes: | |
| * - No Anthropic SDK dependency (initialize_question_agent removed) | |
| * - Question agent initialization is handled by generate-pbl.ts post-processing | |
| * - Operates directly on a shared PBLProjectConfig | |
| */ | |
| import type { PBLProjectConfig, PBLIssue, PBLToolResult } from '../types'; | |
| import { AgentMCP } from './agent-mcp'; | |
| import { getQuestionAgentPrompt, getJudgeAgentPrompt } from './agent-templates'; | |
| export class IssueboardMCP { | |
| private config: PBLProjectConfig; | |
| private agentMCP: AgentMCP; | |
| private language: string; | |
| private nextIssueId: number; | |
| constructor(config: PBLProjectConfig, agentMCP: AgentMCP, language: string = 'en-US') { | |
| this.config = config; | |
| this.agentMCP = agentMCP; | |
| this.language = language; | |
| this.nextIssueId = 1; | |
| } | |
| createIssueboard(): PBLToolResult { | |
| this.config.issueboard = { | |
| agent_ids: [], | |
| issues: [], | |
| current_issue_id: null, | |
| }; | |
| this.nextIssueId = 1; | |
| return { success: true, message: 'Issueboard created successfully.' }; | |
| } | |
| getIssueboard(): PBLToolResult { | |
| return { | |
| success: true, | |
| agent_ids: [...this.config.issueboard.agent_ids], | |
| issues: this.config.issueboard.issues.map((i) => ({ ...i })), | |
| }; | |
| } | |
| updateIssueboardAgents(agentIds: string[]): PBLToolResult { | |
| this.config.issueboard.agent_ids = [...agentIds]; | |
| return { | |
| success: true, | |
| message: 'Issueboard agents updated successfully.', | |
| }; | |
| } | |
| createIssue(params: { | |
| title: string; | |
| description: string; | |
| person_in_charge: string; | |
| participants?: string[]; | |
| notes?: string; | |
| parent_issue?: string | null; | |
| index?: number; | |
| }): PBLToolResult { | |
| const { | |
| title, | |
| description, | |
| person_in_charge, | |
| participants = [], | |
| notes = '', | |
| parent_issue = null, | |
| index = 0, | |
| } = params; | |
| if (!title?.trim()) { | |
| return { success: false, error: 'Title cannot be empty.' }; | |
| } | |
| if (!person_in_charge?.trim()) { | |
| return { success: false, error: 'Person in charge cannot be empty.' }; | |
| } | |
| if (parent_issue && !this.config.issueboard.issues.find((i) => i.id === parent_issue)) { | |
| return { | |
| success: false, | |
| error: `Parent issue "${parent_issue}" not found.`, | |
| }; | |
| } | |
| const issueId = `issue_${this.nextIssueId++}`; | |
| const questionAgentName = `Question Agent - ${issueId}`; | |
| const judgeAgentName = `Judge Agent - ${issueId}`; | |
| const newIssue: PBLIssue = { | |
| id: issueId, | |
| title, | |
| description, | |
| person_in_charge, | |
| participants: [...participants], | |
| notes, | |
| parent_issue, | |
| index, | |
| is_done: false, | |
| is_active: false, | |
| generated_questions: '', | |
| question_agent_name: questionAgentName, | |
| judge_agent_name: judgeAgentName, | |
| }; | |
| this.config.issueboard.issues.push(newIssue); | |
| // Auto-create question and judge agents | |
| this.agentMCP.createAgent({ | |
| name: questionAgentName, | |
| system_prompt: getQuestionAgentPrompt(this.language), | |
| default_mode: 'chat', | |
| actor_role: 'Question Assistant for Issue', | |
| role_division: 'development', | |
| is_system_agent: true, | |
| }); | |
| this.agentMCP.createAgent({ | |
| name: judgeAgentName, | |
| system_prompt: getJudgeAgentPrompt(this.language), | |
| default_mode: 'chat', | |
| actor_role: 'Judge for Issue Completion', | |
| role_division: 'management', | |
| is_system_agent: true, | |
| }); | |
| return { | |
| success: true, | |
| issue_id: issueId, | |
| message: 'Issue created with question and judge agents.', | |
| }; | |
| } | |
| listIssues(): PBLToolResult { | |
| return { | |
| success: true, | |
| issues: this.config.issueboard.issues.map((i) => ({ ...i })), | |
| }; | |
| } | |
| getIssue(issueId: string): PBLToolResult { | |
| const issue = this.config.issueboard.issues.find((i) => i.id === issueId); | |
| if (!issue) { | |
| return { success: false, error: `Issue "${issueId}" not found.` }; | |
| } | |
| return { success: true, issues: [{ ...issue }] }; | |
| } | |
| updateIssue(params: { | |
| issue_id: string; | |
| title?: string; | |
| description?: string; | |
| person_in_charge?: string; | |
| participants?: string[]; | |
| notes?: string; | |
| parent_issue?: string | null; | |
| index?: number; | |
| }): PBLToolResult { | |
| const issue = this.config.issueboard.issues.find((i) => i.id === params.issue_id); | |
| if (!issue) { | |
| return { success: false, error: `Issue "${params.issue_id}" not found.` }; | |
| } | |
| if ( | |
| params.parent_issue !== undefined && | |
| params.parent_issue !== null && | |
| !this.config.issueboard.issues.find((i) => i.id === params.parent_issue) | |
| ) { | |
| return { | |
| success: false, | |
| error: `Parent issue "${params.parent_issue}" not found.`, | |
| }; | |
| } | |
| if (params.title !== undefined) issue.title = params.title; | |
| if (params.description !== undefined) issue.description = params.description; | |
| if (params.person_in_charge !== undefined) issue.person_in_charge = params.person_in_charge; | |
| if (params.participants !== undefined) issue.participants = [...params.participants]; | |
| if (params.notes !== undefined) issue.notes = params.notes; | |
| if (params.parent_issue !== undefined) issue.parent_issue = params.parent_issue; | |
| if (params.index !== undefined) issue.index = params.index; | |
| return { success: true, message: 'Issue updated successfully.' }; | |
| } | |
| deleteIssue(issueId: string): PBLToolResult { | |
| const index = this.config.issueboard.issues.findIndex((i) => i.id === issueId); | |
| if (index === -1) { | |
| return { success: false, error: `Issue "${issueId}" not found.` }; | |
| } | |
| this.config.issueboard.issues.splice(index, 1); | |
| // Remove child issues | |
| this.config.issueboard.issues = this.config.issueboard.issues.filter( | |
| (i) => i.parent_issue !== issueId, | |
| ); | |
| return { success: true, message: 'Issue deleted successfully.' }; | |
| } | |
| reorderIssues(issueIds: string[]): PBLToolResult { | |
| for (const id of issueIds) { | |
| if (!this.config.issueboard.issues.find((i) => i.id === id)) { | |
| return { success: false, error: `Issue "${id}" not found.` }; | |
| } | |
| } | |
| const reordered: PBLIssue[] = []; | |
| for (let i = 0; i < issueIds.length; i++) { | |
| const issue = this.config.issueboard.issues.find((iss) => iss.id === issueIds[i])!; | |
| issue.index = i; | |
| reordered.push(issue); | |
| } | |
| // Append any issues not in the reorder list | |
| for (const issue of this.config.issueboard.issues) { | |
| if (!issueIds.includes(issue.id)) { | |
| reordered.push(issue); | |
| } | |
| } | |
| this.config.issueboard.issues = reordered; | |
| return { success: true, message: 'Issues reordered successfully.' }; | |
| } | |
| activateNextIssue(): PBLToolResult { | |
| // Deactivate current | |
| const current = this.config.issueboard.issues.find((i) => i.is_active); | |
| if (current) { | |
| current.is_active = false; | |
| this.config.issueboard.current_issue_id = null; | |
| } | |
| // Find next incomplete issue | |
| const next = this.config.issueboard.issues | |
| .filter((i) => !i.is_done) | |
| .sort((a, b) => a.index - b.index)[0]; | |
| if (!next) { | |
| return { success: false, error: 'No more issues to activate.' }; | |
| } | |
| next.is_active = true; | |
| this.config.issueboard.current_issue_id = next.id; | |
| return { | |
| success: true, | |
| issue_id: next.id, | |
| message: `Activated issue: ${next.title}`, | |
| }; | |
| } | |
| completeCurrentIssue(): PBLToolResult { | |
| const current = this.config.issueboard.issues.find((i) => i.is_active); | |
| if (!current) { | |
| return { success: false, error: 'No active issue to complete.' }; | |
| } | |
| current.is_done = true; | |
| current.is_active = false; | |
| this.config.issueboard.current_issue_id = null; | |
| return { | |
| success: true, | |
| message: `Issue "${current.id}" marked as complete.`, | |
| }; | |
| } | |
| } | |