internaltool-mcp 1.6.25 → 1.6.26

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.
Files changed (2) hide show
  1. package/index.js +120 -1
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1537,6 +1537,125 @@ The log is visible in InternalTool under the task's Session tab — gives the hu
1537
1537
  return text({ logged: true, type, name, summary })
1538
1538
  }
1539
1539
  )
1540
+
1541
+ // ── get_parallel_kickoffs ─────────────────────────────────────────────────────
1542
+ server.tool(
1543
+ 'get_parallel_kickoffs',
1544
+ `Read the decomposition plan and return ready-to-paste Cursor Agent prompts for the next group of parallel subtasks.
1545
+
1546
+ COORDINATOR WORKFLOW:
1547
+ 1. Call decompose_task to create the plan
1548
+ 2. Call get_parallel_kickoffs to get one paste-ready prompt per parallel subtask
1549
+ 3. Tell the user exactly how many new Cursor Agent windows to open
1550
+ 4. Give them each prompt to paste — one window per subtask
1551
+
1552
+ Cursor cannot spawn agent windows automatically. This tool generates the prompts;
1553
+ the human opens the windows. Each builder agent then runs independently.`,
1554
+ {
1555
+ taskId: z.string().describe("Task's MongoDB ObjectId"),
1556
+ },
1557
+ async ({ taskId }) => {
1558
+ trackTaskActivity(taskId, 'get_parallel_kickoffs')
1559
+ const taskRes = await apiWithRetry(() => api.get(`/api/tasks/${taskId}`))
1560
+ if (!taskRes?.success) return errorText('Task not found')
1561
+ const task = taskRes.data.task
1562
+
1563
+ if (!task.decomposition?.trim()) {
1564
+ return text({
1565
+ error: true,
1566
+ message: 'No decomposition plan found. Call decompose_task first to break the task into subtasks.',
1567
+ })
1568
+ }
1569
+
1570
+ let dec
1571
+ try { dec = JSON.parse(task.decomposition) } catch {
1572
+ return errorText('Decomposition JSON is malformed — call decompose_task again.')
1573
+ }
1574
+
1575
+ const subtasks = dec.subtasks || []
1576
+ const execOrder = dec.executionOrder || []
1577
+
1578
+ // Determine which group to run next: first group that has at least one
1579
+ // subtask not yet reflected in the board subtasks as done.
1580
+ const boardSubtasks = task.subtasks || []
1581
+ // Board subtask titles may carry a role prefix like "[BUILDER] Auth hardening..."
1582
+ // Match on bare title OR prefixed title
1583
+ const isDone = (title) => boardSubtasks.some(s => s.done && (s.title === title || s.title.endsWith(title)))
1584
+
1585
+ let nextGroup = null
1586
+ let nextGroupIndex = -1
1587
+ for (let i = 0; i < execOrder.length; i++) {
1588
+ const group = execOrder[i]
1589
+ const allDone = group.every(t => isDone(t))
1590
+ if (!allDone) {
1591
+ // Also check all prior groups are fully done (respects dependsOn)
1592
+ const priorAllDone = execOrder.slice(0, i).every(g => g.every(t => isDone(t)))
1593
+ if (priorAllDone) {
1594
+ nextGroup = group
1595
+ nextGroupIndex = i + 1
1596
+ break
1597
+ }
1598
+ }
1599
+ }
1600
+
1601
+ if (!nextGroup) {
1602
+ return text({
1603
+ allDone: true,
1604
+ message: 'All subtask groups are complete. Move to the final review or run coverage tests.',
1605
+ })
1606
+ }
1607
+
1608
+ // Only kick off subtasks that aren't done yet
1609
+ const pendingInGroup = nextGroup.filter(t => !isDone(t))
1610
+ const parallelCount = pendingInGroup.length
1611
+ const isParallel = parallelCount > 1
1612
+
1613
+ if (parallelCount === 0) {
1614
+ return text({
1615
+ group: nextGroupIndex,
1616
+ allDone: true,
1617
+ message: `All subtasks in Group ${nextGroupIndex} are already done. Call get_parallel_kickoffs again to get the next group.`,
1618
+ })
1619
+ }
1620
+
1621
+ // Build one ready-to-paste prompt per pending subtask in this group
1622
+ const kickoffs = pendingInGroup.map((subtaskTitle, i) => {
1623
+ const st = subtasks.find(s => s.title === subtaskTitle) || { title: subtaskTitle, role: 'builder', files: [], description: '' }
1624
+ const fileList = (st.files || []).map(f => ` - ${f}`).join('\n')
1625
+ const prompt = [
1626
+ `You are a ${st.role || 'builder'} agent for TASK-011 (${task.key}).`,
1627
+ ``,
1628
+ `## Your subtask (${i + 1} of ${parallelCount} running in parallel)`,
1629
+ `**Title:** ${st.title}`,
1630
+ `**Description:** ${st.description || ''}`,
1631
+ `**Your files:**`,
1632
+ fileList || ' (to be determined)',
1633
+ ``,
1634
+ `## Steps`,
1635
+ `1. Call \`get_agent_context\` with taskId \`${taskId}\``,
1636
+ `2. Call \`claim_files\` with taskId \`${taskId}\` and files: [${(st.files || []).map(f => `"${f}"`).join(', ')}]`,
1637
+ `3. Implement the subtask — only modify your claimed files`,
1638
+ `4. Run tests to verify nothing is broken`,
1639
+ `5. Commit your changes with a clear message referencing ${task.key}`,
1640
+ `6. Mark the subtask done in InternalTool by calling \`update_task\` to tick off "${st.title}" in the subtasks list`,
1641
+ ``,
1642
+ `Do NOT touch files owned by other parallel agents.`,
1643
+ ].join('\n')
1644
+ return { subtask: st.title, role: st.role, files: st.files, prompt }
1645
+ })
1646
+
1647
+ return text({
1648
+ group: nextGroupIndex,
1649
+ totalGroups: execOrder.length,
1650
+ isParallel,
1651
+ parallelCount,
1652
+ instruction: isParallel
1653
+ ? `⚡ GROUP ${nextGroupIndex} runs ${parallelCount} subtasks IN PARALLEL.\n\nTell the user:\n"Please open ${parallelCount} new Cursor Agent windows (Cmd+Shift+P → New Agent Session or open a new Composer tab set to Agent mode).\nPaste prompt 1 in window 1, prompt 2 in window 2. They will run independently."`
1654
+ : `GROUP ${nextGroupIndex} has 1 sequential subtask. Paste the prompt below in a new Agent window (or handle it yourself).`,
1655
+ kickoffs,
1656
+ })
1657
+ }
1658
+ )
1540
1659
  }
1541
1660
 
1542
1661
  // ── Standup activity formatter ────────────────────────────────────────────────
@@ -2967,7 +3086,7 @@ function writeCursorWorkspace(task, projectAgentConfig, startPath) {
2967
3086
  const BUILT_IN = {
2968
3087
  scout: `# Scout Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n\n## Constraints\n- READ ONLY — do not write or modify any file\n- You CAN read files, run tests, and use MCP tools\n\n## Workflow\n1. Call \`get_agent_context\` with taskId \`${taskId}\`\n2. Read every source file systematically\n3. Call \`scout_task\` with confirmed=false, then confirmed=true + your report\n`,
2969
3088
  builder: `# Builder Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n\n**Claimed files:**\n${fileLine}\n\n${scoutLine}\n${decLine}\n\n## Constraints\n- Only modify your claimed files\n- Every change needs a test update\n- Run tests before marking done\n\n## Workflow\n1. Call \`get_agent_context\` with taskId \`${taskId}\`\n2. Implement the feature\n3. Use the \`run-tests\` skill to verify\n4. Call \`commit_helper\` then \`raise_pr\`\n`,
2970
- coordinator: `# Coordinator Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n\n## Constraints\n- Do NOT write code — plan and delegate only\n- Each subtask must have exclusive file ownership\n\n## Workflow\n1. Call \`get_agent_context\` with taskId \`${taskId}\`\n2. Break work into subtasks with role + files + description\n3. Call \`decompose_task\` with taskId \`${taskId}\` to save the plan\n`,
3089
+ coordinator: `# Coordinator Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n\n## Constraints\n- Do NOT write code — plan and delegate only\n- Each subtask must have exclusive file ownership\n- Cursor CANNOT auto-spawn agents — you generate prompts, the human opens windows\n\n## Workflow\n1. Call \`get_agent_context\` with taskId \`${taskId}\` to read the scout report\n2. Call \`decompose_task\` with taskId \`${taskId}\` to break work into parallel groups\n3. Call \`get_parallel_kickoffs\` with taskId \`${taskId}\` — this returns one ready-to-paste prompt per parallel subtask\n4. Tell the user exactly:\n - How many new Cursor Agent windows to open\n - Which prompt to paste in each window (copy verbatim from the kickoffs output)\n5. After all groups in a parallel batch are done, call \`get_parallel_kickoffs\` again for the next group\n\n## Important\nParallel = multiple Cursor Agent windows open at the same time.\nEach window is an independent builder — it claims its own files, implements, commits.\nYou (coordinator) do not implement anything — only plan and hand off.\n`,
2971
3090
  reviewer: `# Reviewer Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n\n## Constraints\n- Read the full diff before approving\n- Post specific, file-level comments\n\n## Workflow\n1. Call \`get_agent_context\` with taskId \`${taskId}\`\n2. Call \`review_pr\` to fetch the diff\n3. Call \`post_pr_review\` with your verdict\n`,
2972
3091
  }
2973
3092
  agentBody = BUILT_IN[role] || `# ${role} Agent — ${taskKey}\n\n**Task ID:** \`${taskId}\`\n**Task:** ${taskTitle}\n`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internaltool-mcp",
3
- "version": "1.6.25",
3
+ "version": "1.6.26",
4
4
  "description": "MCP server for InternalTool — connect AI assistants (Claude Code, Cursor) to your project and task management platform",
5
5
  "type": "module",
6
6
  "main": "index.js",