opencode-swarm-plugin 0.23.5 → 0.24.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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +34 -0
- package/README.md +155 -3
- package/bin/swarm.ts +497 -187
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +668 -91
- package/dist/plugin.js +596 -91
- package/dist/schemas/bead-events.d.ts +698 -0
- package/dist/schemas/bead-events.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/swarm-decompose.d.ts +74 -0
- package/dist/swarm-decompose.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm.d.ts +27 -0
- package/dist/swarm.d.ts.map +1 -1
- package/docs/testing/context-recovery-test.md +470 -0
- package/examples/commands/swarm.md +92 -20
- package/global-skills/swarm-coordination/SKILL.md +380 -10
- package/package.json +2 -2
- package/src/schemas/bead-events.test.ts +341 -0
- package/src/schemas/bead-events.ts +583 -0
- package/src/schemas/index.ts +51 -0
- package/src/skills.ts +10 -3
- package/src/swarm-decompose.ts +337 -0
- package/src/swarm-orchestrate.ts +72 -55
- package/src/swarm-prompts.ts +144 -42
- package/src/swarm.integration.test.ts +581 -31
package/src/swarm-decompose.ts
CHANGED
|
@@ -905,8 +905,345 @@ export class DecompositionError extends SwarmError {
|
|
|
905
905
|
}
|
|
906
906
|
}
|
|
907
907
|
|
|
908
|
+
/**
|
|
909
|
+
* Planning phase state machine for Socratic planning
|
|
910
|
+
*/
|
|
911
|
+
type PlanningPhase = "questioning" | "alternatives" | "recommendation" | "ready";
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Planning mode that determines interaction level
|
|
915
|
+
*/
|
|
916
|
+
type PlanningMode = "socratic" | "fast" | "auto" | "confirm-only";
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Socratic planning output structure
|
|
920
|
+
*/
|
|
921
|
+
interface SocraticPlanOutput {
|
|
922
|
+
mode: PlanningMode;
|
|
923
|
+
phase: PlanningPhase;
|
|
924
|
+
questions?: Array<{ question: string; options?: string[] }>;
|
|
925
|
+
alternatives?: Array<{
|
|
926
|
+
name: string;
|
|
927
|
+
description: string;
|
|
928
|
+
tradeoffs: string;
|
|
929
|
+
}>;
|
|
930
|
+
recommendation?: { approach: string; reasoning: string };
|
|
931
|
+
memory_context?: string;
|
|
932
|
+
codebase_context?: {
|
|
933
|
+
git_status?: string;
|
|
934
|
+
relevant_files?: string[];
|
|
935
|
+
};
|
|
936
|
+
ready_to_decompose: boolean;
|
|
937
|
+
next_action?: string;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Interactive planning tool with Socratic questioning
|
|
942
|
+
*
|
|
943
|
+
* Implements a planning phase BEFORE decomposition that:
|
|
944
|
+
* 1. Gathers context (git, files, semantic memory)
|
|
945
|
+
* 2. Asks clarifying questions (socratic mode)
|
|
946
|
+
* 3. Explores alternatives with tradeoffs
|
|
947
|
+
* 4. Recommends an approach with reasoning
|
|
948
|
+
* 5. Confirms before proceeding to decomposition
|
|
949
|
+
*
|
|
950
|
+
* Modes:
|
|
951
|
+
* - socratic: Full interactive planning with questions, alternatives, recommendations
|
|
952
|
+
* - fast: Skip brainstorming, go straight to decomposition with memory context
|
|
953
|
+
* - auto: Auto-select best approach based on task keywords, minimal interaction
|
|
954
|
+
* - confirm-only: Show decomposition, wait for yes/no confirmation
|
|
955
|
+
*
|
|
956
|
+
* Based on the Socratic Planner Pattern from obra/superpowers.
|
|
957
|
+
*
|
|
958
|
+
* @see docs/analysis-socratic-planner-pattern.md
|
|
959
|
+
*/
|
|
960
|
+
export const swarm_plan_interactive = tool({
|
|
961
|
+
description:
|
|
962
|
+
"Interactive planning phase with Socratic questioning before decomposition. Supports multiple modes from full interactive to auto-proceed.",
|
|
963
|
+
args: {
|
|
964
|
+
task: tool.schema.string().min(1).describe("The task to plan"),
|
|
965
|
+
mode: tool.schema
|
|
966
|
+
.enum(["socratic", "fast", "auto", "confirm-only"])
|
|
967
|
+
.default("socratic")
|
|
968
|
+
.describe("Planning mode: socratic (full), fast (skip questions), auto (minimal), confirm-only (single yes/no)"),
|
|
969
|
+
context: tool.schema
|
|
970
|
+
.string()
|
|
971
|
+
.optional()
|
|
972
|
+
.describe("Optional additional context about the task"),
|
|
973
|
+
user_response: tool.schema
|
|
974
|
+
.string()
|
|
975
|
+
.optional()
|
|
976
|
+
.describe("User's response to a previous question (for multi-turn socratic mode)"),
|
|
977
|
+
phase: tool.schema
|
|
978
|
+
.enum(["questioning", "alternatives", "recommendation", "ready"])
|
|
979
|
+
.optional()
|
|
980
|
+
.describe("Current planning phase (for resuming multi-turn interaction)"),
|
|
981
|
+
},
|
|
982
|
+
async execute(args): Promise<string> {
|
|
983
|
+
// Import needed modules
|
|
984
|
+
const { selectStrategy, formatStrategyGuidelines, STRATEGIES } =
|
|
985
|
+
await import("./swarm-strategies");
|
|
986
|
+
const { formatMemoryQueryForDecomposition } = await import("./learning");
|
|
987
|
+
|
|
988
|
+
// Determine current phase
|
|
989
|
+
const currentPhase: PlanningPhase = args.phase || "questioning";
|
|
990
|
+
const mode: PlanningMode = args.mode || "socratic";
|
|
991
|
+
|
|
992
|
+
// Gather context - always do this regardless of mode
|
|
993
|
+
let memoryContext = "";
|
|
994
|
+
let codebaseContext: { git_status?: string; relevant_files?: string[] } = {};
|
|
995
|
+
|
|
996
|
+
// Generate semantic memory query instruction
|
|
997
|
+
// Note: Semantic memory is accessed via OpenCode's global tools, not as a direct import
|
|
998
|
+
// The coordinator should call semantic-memory_find before calling this tool
|
|
999
|
+
// and pass results in the context parameter
|
|
1000
|
+
try {
|
|
1001
|
+
const memoryQuery = formatMemoryQueryForDecomposition(args.task, 3);
|
|
1002
|
+
memoryContext = `[Memory Query Instruction]\n${memoryQuery.instruction}\nQuery: "${memoryQuery.query}"\nLimit: ${memoryQuery.limit}`;
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
console.warn("[swarm_plan_interactive] Memory query formatting failed:", error);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Get git context for codebase awareness
|
|
1008
|
+
try {
|
|
1009
|
+
const gitResult = await Bun.$`git status --short`.quiet().nothrow();
|
|
1010
|
+
if (gitResult.exitCode === 0) {
|
|
1011
|
+
codebaseContext.git_status = gitResult.stdout.toString().trim();
|
|
1012
|
+
}
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
// Git not available or not in a git repo - continue without it
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Fast mode: Skip to recommendation
|
|
1018
|
+
if (mode === "fast") {
|
|
1019
|
+
const strategyResult = selectStrategy(args.task);
|
|
1020
|
+
const guidelines = formatStrategyGuidelines(strategyResult.strategy);
|
|
1021
|
+
|
|
1022
|
+
const output: SocraticPlanOutput = {
|
|
1023
|
+
mode: "fast",
|
|
1024
|
+
phase: "ready",
|
|
1025
|
+
recommendation: {
|
|
1026
|
+
approach: strategyResult.strategy,
|
|
1027
|
+
reasoning: `${strategyResult.reasoning}\n\n${guidelines}`,
|
|
1028
|
+
},
|
|
1029
|
+
memory_context: memoryContext || undefined,
|
|
1030
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1031
|
+
ready_to_decompose: true,
|
|
1032
|
+
next_action: "Proceed to swarm_decompose or swarm_delegate_planning",
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
return JSON.stringify(output, null, 2);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Auto mode: Auto-select and proceed
|
|
1039
|
+
if (mode === "auto") {
|
|
1040
|
+
const strategyResult = selectStrategy(args.task);
|
|
1041
|
+
|
|
1042
|
+
const output: SocraticPlanOutput = {
|
|
1043
|
+
mode: "auto",
|
|
1044
|
+
phase: "ready",
|
|
1045
|
+
recommendation: {
|
|
1046
|
+
approach: strategyResult.strategy,
|
|
1047
|
+
reasoning: `Auto-selected based on task keywords: ${strategyResult.reasoning}`,
|
|
1048
|
+
},
|
|
1049
|
+
memory_context: memoryContext || undefined,
|
|
1050
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1051
|
+
ready_to_decompose: true,
|
|
1052
|
+
next_action: "Auto-proceeding to decomposition",
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
return JSON.stringify(output, null, 2);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Confirm-only mode: Generate decomposition, show it, wait for yes/no
|
|
1059
|
+
if (mode === "confirm-only") {
|
|
1060
|
+
// This mode will be handled by calling swarm_delegate_planning
|
|
1061
|
+
// and then asking for confirmation on the result
|
|
1062
|
+
const output: SocraticPlanOutput = {
|
|
1063
|
+
mode: "confirm-only",
|
|
1064
|
+
phase: "ready",
|
|
1065
|
+
recommendation: {
|
|
1066
|
+
approach: "Will generate decomposition for your review",
|
|
1067
|
+
reasoning: "Use swarm_delegate_planning to generate the plan, then present it for yes/no confirmation",
|
|
1068
|
+
},
|
|
1069
|
+
memory_context: memoryContext || undefined,
|
|
1070
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1071
|
+
ready_to_decompose: false,
|
|
1072
|
+
next_action: "Call swarm_delegate_planning, then show result and ask for confirmation",
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
return JSON.stringify(output, null, 2);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Socratic mode: Full interactive planning
|
|
1079
|
+
// Phase 1: Questioning
|
|
1080
|
+
if (currentPhase === "questioning") {
|
|
1081
|
+
// Analyze task to identify what needs clarification
|
|
1082
|
+
const taskLower = args.task.toLowerCase();
|
|
1083
|
+
const questions: Array<{ question: string; options?: string[] }> = [];
|
|
1084
|
+
|
|
1085
|
+
// Check for vague task signals from skill
|
|
1086
|
+
const isVague = {
|
|
1087
|
+
noFiles: !taskLower.includes("src/") && !taskLower.includes("file"),
|
|
1088
|
+
vagueVerb:
|
|
1089
|
+
taskLower.includes("improve") ||
|
|
1090
|
+
taskLower.includes("fix") ||
|
|
1091
|
+
taskLower.includes("update") ||
|
|
1092
|
+
taskLower.includes("make better"),
|
|
1093
|
+
noSuccessCriteria: !taskLower.includes("test") && !taskLower.includes("verify"),
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
// Generate clarifying questions (one at a time)
|
|
1097
|
+
if (isVague.noFiles) {
|
|
1098
|
+
questions.push({
|
|
1099
|
+
question: "Which part of the codebase should this change affect?",
|
|
1100
|
+
options: [
|
|
1101
|
+
"Core functionality (src/)",
|
|
1102
|
+
"UI components (components/)",
|
|
1103
|
+
"API routes (app/api/)",
|
|
1104
|
+
"Configuration and tooling",
|
|
1105
|
+
"Tests",
|
|
1106
|
+
],
|
|
1107
|
+
});
|
|
1108
|
+
} else if (isVague.vagueVerb) {
|
|
1109
|
+
questions.push({
|
|
1110
|
+
question: "What specific change are you looking for?",
|
|
1111
|
+
options: [
|
|
1112
|
+
"Add new functionality",
|
|
1113
|
+
"Modify existing behavior",
|
|
1114
|
+
"Remove/deprecate something",
|
|
1115
|
+
"Refactor without behavior change",
|
|
1116
|
+
"Fix a bug",
|
|
1117
|
+
],
|
|
1118
|
+
});
|
|
1119
|
+
} else if (isVague.noSuccessCriteria) {
|
|
1120
|
+
questions.push({
|
|
1121
|
+
question: "How will we know this task is complete?",
|
|
1122
|
+
options: [
|
|
1123
|
+
"All tests pass",
|
|
1124
|
+
"Feature works as demonstrated",
|
|
1125
|
+
"Code review approved",
|
|
1126
|
+
"Documentation updated",
|
|
1127
|
+
"Performance target met",
|
|
1128
|
+
],
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// If task seems clear, move to alternatives phase
|
|
1133
|
+
if (questions.length === 0) {
|
|
1134
|
+
const output: SocraticPlanOutput = {
|
|
1135
|
+
mode: "socratic",
|
|
1136
|
+
phase: "alternatives",
|
|
1137
|
+
memory_context: memoryContext || undefined,
|
|
1138
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1139
|
+
ready_to_decompose: false,
|
|
1140
|
+
next_action: "Task is clear. Call again with phase=alternatives to explore approaches",
|
|
1141
|
+
};
|
|
1142
|
+
return JSON.stringify(output, null, 2);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Return first question only (Socratic principle: one at a time)
|
|
1146
|
+
const output: SocraticPlanOutput = {
|
|
1147
|
+
mode: "socratic",
|
|
1148
|
+
phase: "questioning",
|
|
1149
|
+
questions: [questions[0]],
|
|
1150
|
+
memory_context: memoryContext || undefined,
|
|
1151
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1152
|
+
ready_to_decompose: false,
|
|
1153
|
+
next_action: "User should answer this question, then call again with user_response",
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
return JSON.stringify(output, null, 2);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Phase 2: Alternatives
|
|
1160
|
+
if (currentPhase === "alternatives") {
|
|
1161
|
+
const strategyResult = selectStrategy(args.task);
|
|
1162
|
+
|
|
1163
|
+
// Build 2-3 alternative approaches
|
|
1164
|
+
const alternatives: Array<{
|
|
1165
|
+
name: string;
|
|
1166
|
+
description: string;
|
|
1167
|
+
tradeoffs: string;
|
|
1168
|
+
}> = [];
|
|
1169
|
+
|
|
1170
|
+
// Primary recommendation
|
|
1171
|
+
alternatives.push({
|
|
1172
|
+
name: strategyResult.strategy,
|
|
1173
|
+
description: strategyResult.reasoning,
|
|
1174
|
+
tradeoffs: `Confidence: ${(strategyResult.confidence * 100).toFixed(0)}%. ${STRATEGIES[strategyResult.strategy].description}`,
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
// Add top 2 alternatives
|
|
1178
|
+
for (let i = 0; i < Math.min(2, strategyResult.alternatives.length); i++) {
|
|
1179
|
+
const alt = strategyResult.alternatives[i];
|
|
1180
|
+
alternatives.push({
|
|
1181
|
+
name: alt.strategy,
|
|
1182
|
+
description: STRATEGIES[alt.strategy].description,
|
|
1183
|
+
tradeoffs: `Match score: ${alt.score}. ${STRATEGIES[alt.strategy].antiPatterns[0] || "Consider trade-offs carefully"}`,
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const output: SocraticPlanOutput = {
|
|
1188
|
+
mode: "socratic",
|
|
1189
|
+
phase: "alternatives",
|
|
1190
|
+
alternatives,
|
|
1191
|
+
memory_context: memoryContext || undefined,
|
|
1192
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1193
|
+
ready_to_decompose: false,
|
|
1194
|
+
next_action: "User should choose an approach, then call again with phase=recommendation",
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
return JSON.stringify(output, null, 2);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Phase 3: Recommendation
|
|
1201
|
+
if (currentPhase === "recommendation") {
|
|
1202
|
+
const strategyResult = selectStrategy(args.task);
|
|
1203
|
+
const guidelines = formatStrategyGuidelines(strategyResult.strategy);
|
|
1204
|
+
|
|
1205
|
+
const output: SocraticPlanOutput = {
|
|
1206
|
+
mode: "socratic",
|
|
1207
|
+
phase: "recommendation",
|
|
1208
|
+
recommendation: {
|
|
1209
|
+
approach: strategyResult.strategy,
|
|
1210
|
+
reasoning: `Based on your input and task analysis:\n\n${strategyResult.reasoning}\n\n${guidelines}`,
|
|
1211
|
+
},
|
|
1212
|
+
memory_context: memoryContext || undefined,
|
|
1213
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1214
|
+
ready_to_decompose: false,
|
|
1215
|
+
next_action: "User should confirm to proceed. Then call again with phase=ready",
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
return JSON.stringify(output, null, 2);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Phase 4: Ready
|
|
1222
|
+
if (currentPhase === "ready") {
|
|
1223
|
+
const output: SocraticPlanOutput = {
|
|
1224
|
+
mode: "socratic",
|
|
1225
|
+
phase: "ready",
|
|
1226
|
+
recommendation: {
|
|
1227
|
+
approach: "Confirmed by user",
|
|
1228
|
+
reasoning: "Ready to proceed with decomposition",
|
|
1229
|
+
},
|
|
1230
|
+
memory_context: memoryContext || undefined,
|
|
1231
|
+
codebase_context: Object.keys(codebaseContext).length > 0 ? codebaseContext : undefined,
|
|
1232
|
+
ready_to_decompose: true,
|
|
1233
|
+
next_action: "Proceed to swarm_decompose or swarm_delegate_planning",
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
return JSON.stringify(output, null, 2);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Should never reach here
|
|
1240
|
+
throw new Error(`Invalid planning phase: ${currentPhase}`);
|
|
1241
|
+
},
|
|
1242
|
+
});
|
|
1243
|
+
|
|
908
1244
|
export const decomposeTools = {
|
|
909
1245
|
swarm_decompose,
|
|
910
1246
|
swarm_validate_decomposition,
|
|
911
1247
|
swarm_delegate_planning,
|
|
1248
|
+
swarm_plan_interactive,
|
|
912
1249
|
};
|
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -871,30 +871,8 @@ export const swarm_progress = tool({
|
|
|
871
871
|
});
|
|
872
872
|
await appendEvent(event, args.project_key);
|
|
873
873
|
|
|
874
|
-
//
|
|
875
|
-
|
|
876
|
-
const db = await getDatabase(args.project_key);
|
|
877
|
-
const now = Date.now();
|
|
878
|
-
await db.query(
|
|
879
|
-
`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
|
|
880
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
881
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
882
|
-
files = EXCLUDED.files,
|
|
883
|
-
recovery = EXCLUDED.recovery,
|
|
884
|
-
updated_at = EXCLUDED.updated_at`,
|
|
885
|
-
[
|
|
886
|
-
args.bead_id,
|
|
887
|
-
epicId,
|
|
888
|
-
args.bead_id,
|
|
889
|
-
checkpoint.strategy,
|
|
890
|
-
JSON.stringify(checkpoint.files),
|
|
891
|
-
JSON.stringify(checkpoint.dependencies),
|
|
892
|
-
JSON.stringify(checkpoint.directives),
|
|
893
|
-
JSON.stringify(checkpoint.recovery),
|
|
894
|
-
now,
|
|
895
|
-
now,
|
|
896
|
-
],
|
|
897
|
-
);
|
|
874
|
+
// NOTE: The event handler (handleSwarmCheckpointed in store.ts) updates
|
|
875
|
+
// the swarm_contexts table. We follow event sourcing pattern here.
|
|
898
876
|
checkpointCreated = true;
|
|
899
877
|
} catch (error) {
|
|
900
878
|
// Non-fatal - log and continue
|
|
@@ -1218,8 +1196,26 @@ Continuing with completion, but this should be fixed for future subtasks.`;
|
|
|
1218
1196
|
.nothrow();
|
|
1219
1197
|
|
|
1220
1198
|
if (closeResult.exitCode !== 0) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1199
|
+
const stderrOutput = closeResult.stderr.toString().trim();
|
|
1200
|
+
return JSON.stringify(
|
|
1201
|
+
{
|
|
1202
|
+
success: false,
|
|
1203
|
+
error: "Failed to close bead",
|
|
1204
|
+
failed_step: "bd close",
|
|
1205
|
+
details: stderrOutput || "Unknown error from bd close command",
|
|
1206
|
+
bead_id: args.bead_id,
|
|
1207
|
+
recovery: {
|
|
1208
|
+
steps: [
|
|
1209
|
+
`1. Check bead exists: bd show ${args.bead_id}`,
|
|
1210
|
+
`2. Check bead status (might already be closed): beads_query()`,
|
|
1211
|
+
`3. If bead is blocked, unblock first: beads_update(id="${args.bead_id}", status="in_progress")`,
|
|
1212
|
+
`4. Try closing directly: beads_close(id="${args.bead_id}", reason="...")`,
|
|
1213
|
+
],
|
|
1214
|
+
hint: "If bead is in 'blocked' status, you must change it to 'in_progress' or 'open' before closing.",
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
null,
|
|
1218
|
+
2,
|
|
1223
1219
|
);
|
|
1224
1220
|
}
|
|
1225
1221
|
|
|
@@ -1479,6 +1475,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1479
1475
|
.join("\n");
|
|
1480
1476
|
|
|
1481
1477
|
// Send urgent notification to coordinator
|
|
1478
|
+
let notificationSent = false;
|
|
1482
1479
|
try {
|
|
1483
1480
|
await sendSwarmMessage({
|
|
1484
1481
|
projectPath: args.project_key,
|
|
@@ -1489,6 +1486,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1489
1486
|
threadId: epicId,
|
|
1490
1487
|
importance: "urgent",
|
|
1491
1488
|
});
|
|
1489
|
+
notificationSent = true;
|
|
1492
1490
|
} catch (mailError) {
|
|
1493
1491
|
// Even swarm mail failed - log to console as last resort
|
|
1494
1492
|
console.error(
|
|
@@ -1498,8 +1496,41 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1498
1496
|
console.error(`[swarm_complete] Original error:`, error);
|
|
1499
1497
|
}
|
|
1500
1498
|
|
|
1501
|
-
//
|
|
1502
|
-
|
|
1499
|
+
// Return structured error instead of throwing
|
|
1500
|
+
// This ensures the agent sees the actual error message
|
|
1501
|
+
return JSON.stringify(
|
|
1502
|
+
{
|
|
1503
|
+
success: false,
|
|
1504
|
+
error: errorMessage,
|
|
1505
|
+
failed_step: failedStep,
|
|
1506
|
+
bead_id: args.bead_id,
|
|
1507
|
+
agent_name: args.agent_name,
|
|
1508
|
+
coordinator_notified: notificationSent,
|
|
1509
|
+
stack_trace: errorStack?.slice(0, 500),
|
|
1510
|
+
context: {
|
|
1511
|
+
summary: args.summary,
|
|
1512
|
+
files_touched: args.files_touched || [],
|
|
1513
|
+
skip_ubs_scan: args.skip_ubs_scan ?? false,
|
|
1514
|
+
skip_verification: args.skip_verification ?? false,
|
|
1515
|
+
},
|
|
1516
|
+
recovery: {
|
|
1517
|
+
steps: [
|
|
1518
|
+
"1. Check the error message above for specific issue",
|
|
1519
|
+
`2. Review failed step: ${failedStep}`,
|
|
1520
|
+
"3. Fix underlying issue or use skip flags if appropriate",
|
|
1521
|
+
"4. Retry swarm_complete after fixing",
|
|
1522
|
+
],
|
|
1523
|
+
common_fixes: {
|
|
1524
|
+
"Verification Gate": "Use skip_verification=true to bypass (not recommended)",
|
|
1525
|
+
"UBS scan": "Use skip_ubs_scan=true to bypass",
|
|
1526
|
+
"Bead close": "Check bead status with beads_query(), may need beads_update() first",
|
|
1527
|
+
"Self-evaluation": "Check evaluation JSON format matches EvaluationSchema",
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
null,
|
|
1532
|
+
2,
|
|
1533
|
+
);
|
|
1503
1534
|
}
|
|
1504
1535
|
},
|
|
1505
1536
|
});
|
|
@@ -2061,31 +2092,11 @@ export const swarm_checkpoint = tool({
|
|
|
2061
2092
|
|
|
2062
2093
|
await appendEvent(event, args.project_key);
|
|
2063
2094
|
|
|
2064
|
-
//
|
|
2065
|
-
|
|
2066
|
-
|
|
2095
|
+
// NOTE: The event handler (handleSwarmCheckpointed in store.ts) updates
|
|
2096
|
+
// the swarm_contexts table. We don't write directly here to follow
|
|
2097
|
+
// event sourcing pattern - single source of truth is the event log.
|
|
2067
2098
|
|
|
2068
2099
|
const now = Date.now();
|
|
2069
|
-
await db.query(
|
|
2070
|
-
`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
|
|
2071
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
2072
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
2073
|
-
files = EXCLUDED.files,
|
|
2074
|
-
recovery = EXCLUDED.recovery,
|
|
2075
|
-
updated_at = EXCLUDED.updated_at`,
|
|
2076
|
-
[
|
|
2077
|
-
args.bead_id, // Use bead_id as unique ID
|
|
2078
|
-
args.epic_id,
|
|
2079
|
-
args.bead_id,
|
|
2080
|
-
checkpoint.strategy,
|
|
2081
|
-
JSON.stringify(checkpoint.files),
|
|
2082
|
-
JSON.stringify(checkpoint.dependencies),
|
|
2083
|
-
JSON.stringify(checkpoint.directives),
|
|
2084
|
-
JSON.stringify(checkpoint.recovery),
|
|
2085
|
-
now,
|
|
2086
|
-
now,
|
|
2087
|
-
],
|
|
2088
|
-
);
|
|
2089
2100
|
|
|
2090
2101
|
return JSON.stringify(
|
|
2091
2102
|
{
|
|
@@ -2174,15 +2185,21 @@ export const swarm_recover = tool({
|
|
|
2174
2185
|
}
|
|
2175
2186
|
|
|
2176
2187
|
const row = result.rows[0];
|
|
2188
|
+
// PGLite auto-parses JSON columns, so we need to handle both cases
|
|
2189
|
+
const parseIfString = <T>(val: unknown): T =>
|
|
2190
|
+
typeof val === "string" ? JSON.parse(val) : (val as T);
|
|
2191
|
+
|
|
2177
2192
|
const context: SwarmBeadContext = {
|
|
2178
2193
|
id: row.id,
|
|
2179
2194
|
epic_id: row.epic_id,
|
|
2180
2195
|
bead_id: row.bead_id,
|
|
2181
2196
|
strategy: row.strategy as SwarmBeadContext["strategy"],
|
|
2182
|
-
files:
|
|
2183
|
-
dependencies:
|
|
2184
|
-
directives:
|
|
2185
|
-
|
|
2197
|
+
files: parseIfString<string[]>(row.files),
|
|
2198
|
+
dependencies: parseIfString<string[]>(row.dependencies),
|
|
2199
|
+
directives: parseIfString<SwarmBeadContext["directives"]>(
|
|
2200
|
+
row.directives,
|
|
2201
|
+
),
|
|
2202
|
+
recovery: parseIfString<SwarmBeadContext["recovery"]>(row.recovery),
|
|
2186
2203
|
created_at: row.created_at,
|
|
2187
2204
|
updated_at: row.updated_at,
|
|
2188
2205
|
};
|