opencode-swarm-plugin 0.32.0 → 0.34.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.
Files changed (55) hide show
  1. package/.hive/issues.jsonl +12 -0
  2. package/.hive/memories.jsonl +255 -1
  3. package/.turbo/turbo-build.log +9 -10
  4. package/.turbo/turbo-test.log +343 -337
  5. package/CHANGELOG.md +358 -0
  6. package/README.md +152 -179
  7. package/bin/swarm.test.ts +303 -1
  8. package/bin/swarm.ts +473 -16
  9. package/dist/compaction-hook.d.ts +1 -1
  10. package/dist/compaction-hook.d.ts.map +1 -1
  11. package/dist/index.d.ts +112 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +12380 -131
  14. package/dist/logger.d.ts +34 -0
  15. package/dist/logger.d.ts.map +1 -0
  16. package/dist/observability-tools.d.ts +116 -0
  17. package/dist/observability-tools.d.ts.map +1 -0
  18. package/dist/plugin.js +12254 -119
  19. package/dist/skills.d.ts.map +1 -1
  20. package/dist/swarm-orchestrate.d.ts +105 -0
  21. package/dist/swarm-orchestrate.d.ts.map +1 -1
  22. package/dist/swarm-prompts.d.ts +113 -2
  23. package/dist/swarm-prompts.d.ts.map +1 -1
  24. package/dist/swarm-research.d.ts +127 -0
  25. package/dist/swarm-research.d.ts.map +1 -0
  26. package/dist/swarm-review.d.ts.map +1 -1
  27. package/dist/swarm.d.ts +73 -1
  28. package/dist/swarm.d.ts.map +1 -1
  29. package/evals/compaction-resumption.eval.ts +289 -0
  30. package/evals/coordinator-behavior.eval.ts +307 -0
  31. package/evals/fixtures/compaction-cases.ts +350 -0
  32. package/evals/scorers/compaction-scorers.ts +305 -0
  33. package/evals/scorers/index.ts +12 -0
  34. package/examples/plugin-wrapper-template.ts +297 -8
  35. package/package.json +6 -2
  36. package/src/compaction-hook.test.ts +617 -1
  37. package/src/compaction-hook.ts +291 -18
  38. package/src/index.ts +54 -1
  39. package/src/logger.test.ts +189 -0
  40. package/src/logger.ts +135 -0
  41. package/src/observability-tools.test.ts +346 -0
  42. package/src/observability-tools.ts +594 -0
  43. package/src/skills.integration.test.ts +137 -1
  44. package/src/skills.test.ts +42 -1
  45. package/src/skills.ts +8 -4
  46. package/src/swarm-orchestrate.test.ts +123 -0
  47. package/src/swarm-orchestrate.ts +183 -0
  48. package/src/swarm-prompts.test.ts +553 -1
  49. package/src/swarm-prompts.ts +406 -4
  50. package/src/swarm-research.integration.test.ts +544 -0
  51. package/src/swarm-research.test.ts +698 -0
  52. package/src/swarm-research.ts +472 -0
  53. package/src/swarm-review.test.ts +177 -0
  54. package/src/swarm-review.ts +12 -47
  55. package/src/swarm.ts +6 -3
@@ -905,6 +905,252 @@ interface SwarmDetection {
905
905
  reasons: string[];
906
906
  }
907
907
 
908
+ /**
909
+ * Structured state snapshot for LLM-powered compaction
910
+ *
911
+ * This is passed to the lite model to generate a continuation prompt
912
+ * with concrete data instead of just instructions.
913
+ */
914
+ interface SwarmStateSnapshot {
915
+ sessionID: string;
916
+ detection: {
917
+ confidence: "high" | "medium" | "low" | "none";
918
+ reasons: string[];
919
+ };
920
+ epic?: {
921
+ id: string;
922
+ title: string;
923
+ status: string;
924
+ subtasks: Array<{
925
+ id: string;
926
+ title: string;
927
+ status: "open" | "in_progress" | "blocked" | "closed";
928
+ files: string[];
929
+ assignedTo?: string;
930
+ }>;
931
+ };
932
+ messages: Array<{
933
+ from: string;
934
+ to: string[];
935
+ subject: string;
936
+ body: string;
937
+ timestamp: number;
938
+ importance?: string;
939
+ }>;
940
+ reservations: Array<{
941
+ agent: string;
942
+ paths: string[];
943
+ exclusive: boolean;
944
+ expiresAt: number;
945
+ }>;
946
+ }
947
+
948
+ /**
949
+ * Query actual swarm state using spawn (like detectSwarm does)
950
+ *
951
+ * Returns structured snapshot of current state for LLM compaction.
952
+ * Shells out to swarm CLI to get real data.
953
+ */
954
+ async function querySwarmState(sessionID: string): Promise<SwarmStateSnapshot> {
955
+ try {
956
+ // Query cells via swarm CLI
957
+ const cellsResult = await new Promise<{ exitCode: number; stdout: string }>(
958
+ (resolve) => {
959
+ const proc = spawn(SWARM_CLI, ["tool", "hive_query"], {
960
+ cwd: projectDirectory,
961
+ stdio: ["ignore", "pipe", "pipe"],
962
+ });
963
+ let stdout = "";
964
+ proc.stdout.on("data", (d) => {
965
+ stdout += d;
966
+ });
967
+ proc.on("close", (exitCode) =>
968
+ resolve({ exitCode: exitCode ?? 1, stdout }),
969
+ );
970
+ },
971
+ );
972
+
973
+ const cells =
974
+ cellsResult.exitCode === 0 ? JSON.parse(cellsResult.stdout) : [];
975
+
976
+ // Find active epic (first unclosed epic with subtasks)
977
+ const openEpics = cells.filter(
978
+ (c: { type?: string; status: string }) =>
979
+ c.type === "epic" && c.status !== "closed",
980
+ );
981
+ const epic = openEpics[0];
982
+
983
+ // Get subtasks if we have an epic
984
+ const subtasks =
985
+ epic && epic.id
986
+ ? cells.filter(
987
+ (c: { parent_id?: string }) => c.parent_id === epic.id,
988
+ )
989
+ : [];
990
+
991
+ // TODO: Query swarm mail for messages and reservations
992
+ // For MVP, use empty arrays - the fallback chain handles this
993
+ const messages: SwarmStateSnapshot["messages"] = [];
994
+ const reservations: SwarmStateSnapshot["reservations"] = [];
995
+
996
+ // Run detection for confidence
997
+ const detection = await detectSwarm();
998
+
999
+ return {
1000
+ sessionID,
1001
+ detection: {
1002
+ confidence: detection.confidence,
1003
+ reasons: detection.reasons,
1004
+ },
1005
+ epic: epic
1006
+ ? {
1007
+ id: epic.id,
1008
+ title: epic.title,
1009
+ status: epic.status,
1010
+ subtasks: subtasks.map((s: {
1011
+ id: string;
1012
+ title: string;
1013
+ status: string;
1014
+ files?: string[];
1015
+ }) => ({
1016
+ id: s.id,
1017
+ title: s.title,
1018
+ status: s.status as "open" | "in_progress" | "blocked" | "closed",
1019
+ files: s.files || [],
1020
+ })),
1021
+ }
1022
+ : undefined,
1023
+ messages,
1024
+ reservations,
1025
+ };
1026
+ } catch (err) {
1027
+ // If query fails, return minimal snapshot
1028
+ const detection = await detectSwarm();
1029
+ return {
1030
+ sessionID,
1031
+ detection: {
1032
+ confidence: detection.confidence,
1033
+ reasons: detection.reasons,
1034
+ },
1035
+ messages: [],
1036
+ reservations: [],
1037
+ };
1038
+ }
1039
+ }
1040
+
1041
+ /**
1042
+ * Generate compaction prompt using LLM
1043
+ *
1044
+ * Shells out to `opencode run -m <liteModel>` with structured state.
1045
+ * Returns markdown continuation prompt or null on failure.
1046
+ *
1047
+ * Timeout: 30 seconds
1048
+ */
1049
+ async function generateCompactionPrompt(
1050
+ snapshot: SwarmStateSnapshot,
1051
+ ): Promise<string | null> {
1052
+ try {
1053
+ const liteModel =
1054
+ process.env.OPENCODE_LITE_MODEL || "claude-3-5-haiku-20241022";
1055
+
1056
+ const promptText = `You are generating a continuation prompt for a compacted swarm coordination session.
1057
+
1058
+ Analyze this swarm state and generate a structured markdown prompt that will be given to the resumed session:
1059
+
1060
+ ${JSON.stringify(snapshot, null, 2)}
1061
+
1062
+ Generate a prompt following this structure:
1063
+
1064
+ # 🐝 Swarm Continuation - [Epic Title or "Unknown"]
1065
+
1066
+ You are resuming coordination of an active swarm that was interrupted by context compaction.
1067
+
1068
+ ## Epic State
1069
+
1070
+ **ID:** [epic ID or "Unknown"]
1071
+ **Title:** [epic title or "No active epic"]
1072
+ **Status:** [X/Y subtasks complete]
1073
+ **Project:** ${projectDirectory}
1074
+
1075
+ ## Subtask Status
1076
+
1077
+ ### ✅ Completed (N)
1078
+ [List completed subtasks with IDs]
1079
+
1080
+ ### 🚧 In Progress (N)
1081
+ [List in-progress subtasks with IDs, files, agents if known]
1082
+
1083
+ ### 🚫 Blocked (N)
1084
+ [List blocked subtasks]
1085
+
1086
+ ### ⏳ Pending (N)
1087
+ [List pending subtasks]
1088
+
1089
+ ## Next Actions (IMMEDIATE)
1090
+
1091
+ [List 3-5 concrete actions with actual commands, using real IDs from the state]
1092
+
1093
+ ## Coordinator Reminders
1094
+
1095
+ - **You are the coordinator** - Don't wait for instructions, orchestrate
1096
+ - **Monitor actively** - Check messages every ~10 minutes
1097
+ - **Unblock aggressively** - Resolve dependencies immediately
1098
+ - **Review thoroughly** - 3-strike rule enforced
1099
+ - **Ship it** - When all subtasks done, close the epic
1100
+
1101
+ Keep the prompt concise but actionable. Use actual data from the snapshot, not placeholders.`;
1102
+
1103
+ const result = await new Promise<{ exitCode: number; stdout: string; stderr: string }>(
1104
+ (resolve, reject) => {
1105
+ const proc = spawn("opencode", ["run", "-m", liteModel, "--", promptText], {
1106
+ cwd: projectDirectory,
1107
+ stdio: ["ignore", "pipe", "pipe"],
1108
+ timeout: 30000, // 30 second timeout
1109
+ });
1110
+
1111
+ let stdout = "";
1112
+ let stderr = "";
1113
+
1114
+ proc.stdout.on("data", (d) => {
1115
+ stdout += d;
1116
+ });
1117
+ proc.stderr.on("data", (d) => {
1118
+ stderr += d;
1119
+ });
1120
+
1121
+ proc.on("close", (exitCode) => {
1122
+ resolve({ exitCode: exitCode ?? 1, stdout, stderr });
1123
+ });
1124
+
1125
+ proc.on("error", (err) => {
1126
+ reject(err);
1127
+ });
1128
+
1129
+ // Timeout handling
1130
+ setTimeout(() => {
1131
+ proc.kill("SIGTERM");
1132
+ reject(new Error("LLM compaction timeout (30s)"));
1133
+ }, 30000);
1134
+ },
1135
+ );
1136
+
1137
+ if (result.exitCode !== 0) {
1138
+ console.error(
1139
+ "[Swarm Compaction] opencode run failed:",
1140
+ result.stderr,
1141
+ );
1142
+ return null;
1143
+ }
1144
+
1145
+ // Extract the prompt from stdout (LLM may wrap in markdown)
1146
+ const prompt = result.stdout.trim();
1147
+ return prompt.length > 0 ? prompt : null;
1148
+ } catch (err) {
1149
+ console.error("[Swarm Compaction] LLM generation failed:", err);
1150
+ return null;
1151
+ }
1152
+ }
1153
+
908
1154
  /**
909
1155
  * Check for swarm sign - evidence a swarm passed through
910
1156
  *
@@ -1124,11 +1370,16 @@ Include this in your summary:
1124
1370
  "This is an active swarm. Check swarm_status and swarmmail_inbox immediately."
1125
1371
  `;
1126
1372
 
1127
- // Extended hooks type to include experimental compaction hook
1373
+ // Extended hooks type to include experimental compaction hook with new prompt API
1374
+ type CompactionOutput = {
1375
+ context: string[];
1376
+ prompt?: string; // NEW API from OpenCode PR #5907
1377
+ };
1378
+
1128
1379
  type ExtendedHooks = Hooks & {
1129
1380
  "experimental.session.compacting"?: (
1130
1381
  input: { sessionID: string },
1131
- output: { context: string[] },
1382
+ output: CompactionOutput,
1132
1383
  ) => Promise<void>;
1133
1384
  };
1134
1385
 
@@ -1201,23 +1452,61 @@ export const SwarmPlugin: Plugin = async (
1201
1452
  skills_execute,
1202
1453
  },
1203
1454
 
1204
- // Swarm-aware compaction hook - injects context based on detection confidence
1455
+ // Swarm-aware compaction hook with LLM-powered continuation prompts
1456
+ // Three-level fallback chain: LLM → static context → detection fallback → none
1205
1457
  "experimental.session.compacting": async (
1206
- _input: { sessionID: string },
1207
- output: { context: string[] },
1458
+ input: { sessionID: string },
1459
+ output: CompactionOutput,
1208
1460
  ) => {
1209
1461
  const detection = await detectSwarm();
1210
1462
 
1211
1463
  if (detection.confidence === "high" || detection.confidence === "medium") {
1212
- // Definite or probable swarm - inject full context
1464
+ // Definite or probable swarm - try LLM-powered compaction
1465
+ try {
1466
+ // Level 1: Query actual state
1467
+ const snapshot = await querySwarmState(input.sessionID);
1468
+
1469
+ // Level 2: Generate prompt with LLM
1470
+ const llmPrompt = await generateCompactionPrompt(snapshot);
1471
+
1472
+ if (llmPrompt) {
1473
+ // SUCCESS: Use LLM-generated prompt
1474
+ const header = `[Swarm compaction: LLM-generated, ${detection.reasons.join(", ")}]\n\n`;
1475
+
1476
+ // Progressive enhancement: use new API if available
1477
+ if ("prompt" in output) {
1478
+ output.prompt = header + llmPrompt;
1479
+ } else {
1480
+ output.context.push(header + llmPrompt);
1481
+ }
1482
+
1483
+ console.log(
1484
+ "[Swarm Compaction] Using LLM-generated continuation prompt",
1485
+ );
1486
+ return;
1487
+ }
1488
+
1489
+ // LLM failed, fall through to static prompt
1490
+ console.log(
1491
+ "[Swarm Compaction] LLM generation returned null, using static prompt",
1492
+ );
1493
+ } catch (err) {
1494
+ // LLM failed, fall through to static prompt
1495
+ console.error(
1496
+ "[Swarm Compaction] LLM generation failed, using static prompt:",
1497
+ err,
1498
+ );
1499
+ }
1500
+
1501
+ // Level 3: Fall back to static context
1213
1502
  const header = `[Swarm detected: ${detection.reasons.join(", ")}]\n\n`;
1214
1503
  output.context.push(header + SWARM_COMPACTION_CONTEXT);
1215
1504
  } else if (detection.confidence === "low") {
1216
- // Possible swarm - inject fallback detection prompt
1505
+ // Level 4: Possible swarm - inject fallback detection prompt
1217
1506
  const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
1218
1507
  output.context.push(header + SWARM_DETECTION_FALLBACK);
1219
1508
  }
1220
- // confidence === "none" - no injection, probably not a swarm
1509
+ // Level 5: confidence === "none" - no injection, probably not a swarm
1221
1510
  },
1222
1511
  };
1223
1512
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm-plugin",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Multi-agent swarm coordination for OpenCode with learning capabilities, beads integration, and Agent Mail",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,10 @@
39
39
  "gray-matter": "^4.0.3",
40
40
  "ioredis": "^5.4.1",
41
41
  "minimatch": "^10.1.1",
42
- "swarm-mail": "1.3.0",
42
+ "pino": "^9.6.0",
43
+ "pino-roll": "^1.3.0",
44
+ "swarm-mail": "1.5.0",
45
+ "yaml": "^2.8.2",
43
46
  "zod": "4.1.8"
44
47
  },
45
48
  "devDependencies": {
@@ -48,6 +51,7 @@
48
51
  "ai": "6.0.0-beta.150",
49
52
  "bun-types": "^1.3.4",
50
53
  "evalite": "^1.0.0-beta.10",
54
+ "pino-pretty": "^13.1.3",
51
55
  "turbo": "^2.6.3",
52
56
  "typescript": "^5.7.0",
53
57
  "vitest": "^4.0.15"