opencode-swarm-plugin 0.3.0 → 0.4.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.
@@ -16,6 +16,8 @@ import {
16
16
  swarm_complete,
17
17
  swarm_subtask_prompt,
18
18
  swarm_evaluation_prompt,
19
+ formatSubtaskPromptV2,
20
+ SUBTASK_PROMPT_V2,
19
21
  } from "./swarm";
20
22
  import { mcpCall, setState, clearState, AGENT_MAIL_URL } from "./agent-mail";
21
23
 
@@ -952,3 +954,127 @@ describe("Graceful Degradation", () => {
952
954
  expect(result).toContain("Report progress");
953
955
  });
954
956
  });
957
+
958
+ // ============================================================================
959
+ // Coordinator-Centric Swarm Tools (V2)
960
+ // ============================================================================
961
+
962
+ describe("Coordinator-Centric Swarm Tools", () => {
963
+ describe("formatSubtaskPromptV2", () => {
964
+ it("generates correct prompt with all fields", () => {
965
+ const result = formatSubtaskPromptV2({
966
+ subtask_title: "Add OAuth provider",
967
+ subtask_description: "Configure Google OAuth in the auth config",
968
+ files: ["src/auth/google.ts", "src/auth/config.ts"],
969
+ shared_context: "We are using NextAuth.js v5",
970
+ });
971
+
972
+ // Check title is included
973
+ expect(result).toContain("Add OAuth provider");
974
+
975
+ // Check description is included
976
+ expect(result).toContain("Configure Google OAuth in the auth config");
977
+
978
+ // Check files are formatted as list
979
+ expect(result).toContain("- `src/auth/google.ts`");
980
+ expect(result).toContain("- `src/auth/config.ts`");
981
+
982
+ // Check shared context is included
983
+ expect(result).toContain("We are using NextAuth.js v5");
984
+
985
+ // Check expected sections exist
986
+ expect(result).toContain("## Your Task");
987
+ expect(result).toContain("## Files to Modify");
988
+ expect(result).toContain("## Context");
989
+ expect(result).toContain("## Instructions");
990
+ expect(result).toContain("## When Complete");
991
+ });
992
+
993
+ it("handles missing optional fields", () => {
994
+ const result = formatSubtaskPromptV2({
995
+ subtask_title: "Simple task",
996
+ subtask_description: "",
997
+ files: [],
998
+ });
999
+
1000
+ // Check title is included
1001
+ expect(result).toContain("Simple task");
1002
+
1003
+ // Check fallback for empty description
1004
+ expect(result).toContain("(see title)");
1005
+
1006
+ // Check fallback for empty files
1007
+ expect(result).toContain(
1008
+ "(no specific files assigned - use your judgment)",
1009
+ );
1010
+
1011
+ // Check fallback for missing context
1012
+ expect(result).toContain("(none provided)");
1013
+ });
1014
+
1015
+ it("handles files with special characters", () => {
1016
+ const result = formatSubtaskPromptV2({
1017
+ subtask_title: "Handle paths",
1018
+ subtask_description: "Test file paths",
1019
+ files: [
1020
+ "src/components/[slug]/page.tsx",
1021
+ "src/api/users/[id]/route.ts",
1022
+ ],
1023
+ });
1024
+
1025
+ expect(result).toContain("- `src/components/[slug]/page.tsx`");
1026
+ expect(result).toContain("- `src/api/users/[id]/route.ts`");
1027
+ });
1028
+ });
1029
+
1030
+ describe("SUBTASK_PROMPT_V2", () => {
1031
+ it("contains expected sections", () => {
1032
+ // Check all main sections are present in the template
1033
+ expect(SUBTASK_PROMPT_V2).toContain("## Your Task");
1034
+ expect(SUBTASK_PROMPT_V2).toContain("{subtask_title}");
1035
+ expect(SUBTASK_PROMPT_V2).toContain("{subtask_description}");
1036
+
1037
+ expect(SUBTASK_PROMPT_V2).toContain("## Files to Modify");
1038
+ expect(SUBTASK_PROMPT_V2).toContain("{file_list}");
1039
+
1040
+ expect(SUBTASK_PROMPT_V2).toContain("## Context");
1041
+ expect(SUBTASK_PROMPT_V2).toContain("{shared_context}");
1042
+
1043
+ expect(SUBTASK_PROMPT_V2).toContain("## Instructions");
1044
+ expect(SUBTASK_PROMPT_V2).toContain("Read first");
1045
+ expect(SUBTASK_PROMPT_V2).toContain("Plan your approach");
1046
+ expect(SUBTASK_PROMPT_V2).toContain("Make the changes");
1047
+ expect(SUBTASK_PROMPT_V2).toContain("Verify");
1048
+
1049
+ expect(SUBTASK_PROMPT_V2).toContain("## When Complete");
1050
+ expect(SUBTASK_PROMPT_V2).toContain('"success"');
1051
+ expect(SUBTASK_PROMPT_V2).toContain('"summary"');
1052
+ expect(SUBTASK_PROMPT_V2).toContain('"files_modified"');
1053
+ });
1054
+
1055
+ it("does NOT contain Agent Mail instructions", () => {
1056
+ // V2 prompt is for coordinator-centric model where subagents don't use Agent Mail
1057
+ expect(SUBTASK_PROMPT_V2).not.toContain("Agent Mail");
1058
+ expect(SUBTASK_PROMPT_V2).not.toContain("agentmail_");
1059
+ expect(SUBTASK_PROMPT_V2).not.toContain("agent_name");
1060
+ expect(SUBTASK_PROMPT_V2).not.toContain("send_message");
1061
+ });
1062
+
1063
+ it("does NOT contain beads instructions", () => {
1064
+ // V2 prompt is for coordinator-centric model where subagents don't manage beads
1065
+ expect(SUBTASK_PROMPT_V2).not.toContain("bead_id");
1066
+ expect(SUBTASK_PROMPT_V2).not.toContain("epic_id");
1067
+ expect(SUBTASK_PROMPT_V2).not.toContain("bd update");
1068
+ expect(SUBTASK_PROMPT_V2).not.toContain("bd close");
1069
+ expect(SUBTASK_PROMPT_V2).not.toContain("swarm_progress");
1070
+ expect(SUBTASK_PROMPT_V2).not.toContain("swarm_complete");
1071
+ });
1072
+
1073
+ it("expects structured JSON response from subagent", () => {
1074
+ // The prompt should instruct agents to return structured JSON
1075
+ expect(SUBTASK_PROMPT_V2).toContain("```json");
1076
+ expect(SUBTASK_PROMPT_V2).toContain('"success"');
1077
+ expect(SUBTASK_PROMPT_V2).toContain('"blocker"');
1078
+ });
1079
+ });
1080
+ });
package/src/swarm.ts CHANGED
@@ -359,6 +359,92 @@ Before writing code:
359
359
 
360
360
  Begin work on your subtask now.`;
361
361
 
362
+ /**
363
+ * Simplified subtask prompt for Task subagents (V2 - coordinator-centric)
364
+ *
365
+ * This prompt is designed for agents that DON'T have access to Agent Mail or beads tools.
366
+ * The coordinator handles all coordination - subagents just do the work and return results.
367
+ *
368
+ * Key differences from V1:
369
+ * - No Agent Mail instructions (subagents can't use it)
370
+ * - No beads instructions (subagents can't use it)
371
+ * - Expects structured JSON response for coordinator to process
372
+ */
373
+ export const SUBTASK_PROMPT_V2 = `You are working on a subtask as part of a larger project.
374
+
375
+ ## Your Task
376
+ **Title**: {subtask_title}
377
+
378
+ {subtask_description}
379
+
380
+ ## Files to Modify
381
+ {file_list}
382
+
383
+ **IMPORTANT**: Only modify the files listed above. Do not create new files unless absolutely necessary for the task.
384
+
385
+ ## Context
386
+ {shared_context}
387
+
388
+ ## Instructions
389
+
390
+ 1. **Read first** - Understand the current state of the files before making changes
391
+ 2. **Plan your approach** - Think through what changes are needed
392
+ 3. **Make the changes** - Implement the required functionality
393
+ 4. **Verify** - Check that your changes work (run tests/typecheck if applicable)
394
+
395
+ ## When Complete
396
+
397
+ After finishing your work, provide a summary in this format:
398
+
399
+ \`\`\`json
400
+ {
401
+ "success": true,
402
+ "summary": "Brief description of what you accomplished",
403
+ "files_modified": ["list", "of", "files", "you", "changed"],
404
+ "files_created": ["any", "new", "files"],
405
+ "issues_found": ["any problems or concerns discovered"],
406
+ "tests_passed": true,
407
+ "notes": "Any additional context for the coordinator"
408
+ }
409
+ \`\`\`
410
+
411
+ If you encounter a blocker you cannot resolve, return:
412
+
413
+ \`\`\`json
414
+ {
415
+ "success": false,
416
+ "summary": "What you attempted",
417
+ "blocker": "Description of what's blocking you",
418
+ "files_modified": [],
419
+ "suggestions": ["possible", "solutions"]
420
+ }
421
+ \`\`\`
422
+
423
+ Begin work now.`;
424
+
425
+ /**
426
+ * Format the V2 subtask prompt for a specific agent
427
+ */
428
+ export function formatSubtaskPromptV2(params: {
429
+ subtask_title: string;
430
+ subtask_description: string;
431
+ files: string[];
432
+ shared_context?: string;
433
+ }): string {
434
+ const fileList =
435
+ params.files.length > 0
436
+ ? params.files.map((f) => `- \`${f}\``).join("\n")
437
+ : "(no specific files assigned - use your judgment)";
438
+
439
+ return SUBTASK_PROMPT_V2.replace("{subtask_title}", params.subtask_title)
440
+ .replace(
441
+ "{subtask_description}",
442
+ params.subtask_description || "(see title)",
443
+ )
444
+ .replace("{file_list}", fileList)
445
+ .replace("{shared_context}", params.shared_context || "(none provided)");
446
+ }
447
+
362
448
  /**
363
449
  * Prompt for self-evaluation before completing a subtask.
364
450
  *
@@ -1418,6 +1504,196 @@ export const swarm_subtask_prompt = tool({
1418
1504
  },
1419
1505
  });
1420
1506
 
1507
+ /**
1508
+ * Prepare a subtask for spawning with Task tool (V2 prompt)
1509
+ *
1510
+ * This is a simplified tool for coordinators that generates a prompt using
1511
+ * the V2 template (no Agent Mail/beads instructions - coordinator handles coordination).
1512
+ * Returns JSON that can be directly used with Task tool.
1513
+ */
1514
+ export const swarm_spawn_subtask = tool({
1515
+ description:
1516
+ "Prepare a subtask for spawning with Task tool. Returns prompt and metadata for coordinator to use.",
1517
+ args: {
1518
+ bead_id: tool.schema.string().describe("Subtask bead ID"),
1519
+ subtask_title: tool.schema.string().describe("Subtask title"),
1520
+ subtask_description: tool.schema
1521
+ .string()
1522
+ .optional()
1523
+ .describe("Detailed subtask instructions"),
1524
+ files: tool.schema
1525
+ .array(tool.schema.string())
1526
+ .describe("Files assigned to this subtask"),
1527
+ shared_context: tool.schema
1528
+ .string()
1529
+ .optional()
1530
+ .describe("Context shared across all agents"),
1531
+ },
1532
+ async execute(args) {
1533
+ const prompt = formatSubtaskPromptV2({
1534
+ subtask_title: args.subtask_title,
1535
+ subtask_description: args.subtask_description || "",
1536
+ files: args.files,
1537
+ shared_context: args.shared_context,
1538
+ });
1539
+
1540
+ return JSON.stringify(
1541
+ {
1542
+ prompt,
1543
+ bead_id: args.bead_id,
1544
+ files: args.files,
1545
+ },
1546
+ null,
1547
+ 2,
1548
+ );
1549
+ },
1550
+ });
1551
+
1552
+ /**
1553
+ * Schema for task agent result
1554
+ */
1555
+ const TaskResultSchema = z.object({
1556
+ success: z.boolean(),
1557
+ summary: z.string(),
1558
+ files_modified: z.array(z.string()).optional().default([]),
1559
+ files_created: z.array(z.string()).optional().default([]),
1560
+ issues_found: z.array(z.string()).optional().default([]),
1561
+ tests_passed: z.boolean().optional(),
1562
+ notes: z.string().optional(),
1563
+ blocker: z.string().optional(),
1564
+ suggestions: z.array(z.string()).optional(),
1565
+ });
1566
+
1567
+ type TaskResult = z.infer<typeof TaskResultSchema>;
1568
+
1569
+ /**
1570
+ * Handle subtask completion from a Task agent
1571
+ *
1572
+ * This tool is for coordinators to process the result after a Task subagent
1573
+ * returns. It parses the JSON result, closes the bead on success, and
1574
+ * creates new beads for any issues discovered.
1575
+ *
1576
+ * @example
1577
+ * // Task agent returns JSON:
1578
+ * // { "success": true, "summary": "Added auth", "files_modified": ["src/auth.ts"], "issues_found": ["Missing tests"] }
1579
+ * //
1580
+ * // Coordinator calls:
1581
+ * swarm_complete_subtask(bead_id="bd-123.1", task_result=<agent_response>)
1582
+ */
1583
+ export const swarm_complete_subtask = tool({
1584
+ description:
1585
+ "Handle subtask completion after Task agent returns. Parses result JSON, closes bead on success, creates new beads for issues found.",
1586
+ args: {
1587
+ bead_id: z.string().describe("Subtask bead ID to close"),
1588
+ task_result: z
1589
+ .string()
1590
+ .describe("JSON result from the Task agent (TaskResult schema)"),
1591
+ files_touched: z
1592
+ .array(z.string())
1593
+ .optional()
1594
+ .describe(
1595
+ "Override files touched (uses task_result.files_modified if not provided)",
1596
+ ),
1597
+ },
1598
+ async execute(args) {
1599
+ // Parse the task result JSON
1600
+ let result: TaskResult;
1601
+ try {
1602
+ const parsed = JSON.parse(args.task_result);
1603
+ result = TaskResultSchema.parse(parsed);
1604
+ } catch (error) {
1605
+ // Handle parse errors gracefully
1606
+ const errorMessage =
1607
+ error instanceof SyntaxError
1608
+ ? `Invalid JSON: ${error.message}`
1609
+ : error instanceof z.ZodError
1610
+ ? `Schema validation failed: ${error.issues.map((i) => i.message).join(", ")}`
1611
+ : String(error);
1612
+
1613
+ return JSON.stringify(
1614
+ {
1615
+ success: false,
1616
+ error: "Failed to parse task result",
1617
+ details: errorMessage,
1618
+ hint: "Task agent should return JSON matching TaskResult schema: { success, summary, files_modified?, issues_found?, ... }",
1619
+ },
1620
+ null,
1621
+ 2,
1622
+ );
1623
+ }
1624
+
1625
+ const filesTouched = args.files_touched ?? [
1626
+ ...result.files_modified,
1627
+ ...result.files_created,
1628
+ ];
1629
+ const issuesCreated: Array<{ title: string; id?: string }> = [];
1630
+
1631
+ // If task failed, don't close the bead - return info for coordinator to handle
1632
+ if (!result.success) {
1633
+ return JSON.stringify(
1634
+ {
1635
+ success: false,
1636
+ bead_id: args.bead_id,
1637
+ task_failed: true,
1638
+ summary: result.summary,
1639
+ blocker: result.blocker,
1640
+ suggestions: result.suggestions,
1641
+ files_touched: filesTouched,
1642
+ action_needed:
1643
+ "Task failed - review blocker and decide whether to retry or close as failed",
1644
+ },
1645
+ null,
1646
+ 2,
1647
+ );
1648
+ }
1649
+
1650
+ // Task succeeded - close the bead
1651
+ const closeReason = result.summary.slice(0, 200); // Truncate for safety
1652
+ await Bun.$`bd close ${args.bead_id} -r "${closeReason}"`.quiet().nothrow();
1653
+
1654
+ // Create new beads for each issue found
1655
+ if (result.issues_found.length > 0) {
1656
+ for (const issue of result.issues_found) {
1657
+ const issueTitle = issue.slice(0, 100); // Truncate long titles
1658
+ const createResult = await Bun.$`bd create "${issueTitle}" -t bug`
1659
+ .quiet()
1660
+ .nothrow();
1661
+
1662
+ if (createResult.exitCode === 0) {
1663
+ // Try to parse the bead ID from output
1664
+ const output = createResult.stdout.toString();
1665
+ const idMatch = output.match(/bd-[a-z0-9]+/);
1666
+ issuesCreated.push({
1667
+ title: issueTitle,
1668
+ id: idMatch?.[0],
1669
+ });
1670
+ } else {
1671
+ issuesCreated.push({
1672
+ title: issueTitle,
1673
+ id: undefined, // Failed to create
1674
+ });
1675
+ }
1676
+ }
1677
+ }
1678
+
1679
+ return JSON.stringify(
1680
+ {
1681
+ success: true,
1682
+ bead_id: args.bead_id,
1683
+ bead_closed: true,
1684
+ summary: result.summary,
1685
+ files_touched: filesTouched,
1686
+ tests_passed: result.tests_passed,
1687
+ notes: result.notes,
1688
+ issues_created: issuesCreated.length > 0 ? issuesCreated : undefined,
1689
+ issues_count: issuesCreated.length,
1690
+ },
1691
+ null,
1692
+ 2,
1693
+ );
1694
+ },
1695
+ });
1696
+
1421
1697
  /**
1422
1698
  * Generate self-evaluation prompt
1423
1699
  */
@@ -1560,5 +1836,7 @@ export const swarmTools = {
1560
1836
  swarm_complete: swarm_complete,
1561
1837
  swarm_record_outcome: swarm_record_outcome,
1562
1838
  swarm_subtask_prompt: swarm_subtask_prompt,
1839
+ swarm_spawn_subtask: swarm_spawn_subtask,
1840
+ swarm_complete_subtask: swarm_complete_subtask,
1563
1841
  swarm_evaluation_prompt: swarm_evaluation_prompt,
1564
1842
  };