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.
- package/.hive/issues.jsonl +12 -0
- package/.hive/memories.jsonl +255 -1
- package/.turbo/turbo-build.log +9 -10
- package/.turbo/turbo-test.log +343 -337
- package/CHANGELOG.md +358 -0
- package/README.md +152 -179
- package/bin/swarm.test.ts +303 -1
- package/bin/swarm.ts +473 -16
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/index.d.ts +112 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12380 -131
- package/dist/logger.d.ts +34 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/observability-tools.d.ts +116 -0
- package/dist/observability-tools.d.ts.map +1 -0
- package/dist/plugin.js +12254 -119
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts +105 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +113 -2
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-research.d.ts +127 -0
- package/dist/swarm-research.d.ts.map +1 -0
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm.d.ts +73 -1
- package/dist/swarm.d.ts.map +1 -1
- package/evals/compaction-resumption.eval.ts +289 -0
- package/evals/coordinator-behavior.eval.ts +307 -0
- package/evals/fixtures/compaction-cases.ts +350 -0
- package/evals/scorers/compaction-scorers.ts +305 -0
- package/evals/scorers/index.ts +12 -0
- package/examples/plugin-wrapper-template.ts +297 -8
- package/package.json +6 -2
- package/src/compaction-hook.test.ts +617 -1
- package/src/compaction-hook.ts +291 -18
- package/src/index.ts +54 -1
- package/src/logger.test.ts +189 -0
- package/src/logger.ts +135 -0
- package/src/observability-tools.test.ts +346 -0
- package/src/observability-tools.ts +594 -0
- package/src/skills.integration.test.ts +137 -1
- package/src/skills.test.ts +42 -1
- package/src/skills.ts +8 -4
- package/src/swarm-orchestrate.test.ts +123 -0
- package/src/swarm-orchestrate.ts +183 -0
- package/src/swarm-prompts.test.ts +553 -1
- package/src/swarm-prompts.ts +406 -4
- package/src/swarm-research.integration.test.ts +544 -0
- package/src/swarm-research.test.ts +698 -0
- package/src/swarm-research.ts +472 -0
- package/src/swarm-review.test.ts +177 -0
- package/src/swarm-review.ts +12 -47
- 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:
|
|
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 -
|
|
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
|
-
|
|
1207
|
-
output:
|
|
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 -
|
|
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.
|
|
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
|
-
"
|
|
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"
|