opencode-swarm-plugin 0.17.0 → 0.18.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/dist/index.js CHANGED
@@ -12810,6 +12810,11 @@ var init_events = __esm(() => {
12810
12810
  async function appendEvent(event, projectPath) {
12811
12811
  const db = await getDatabase(projectPath);
12812
12812
  const { type, project_key, timestamp, ...rest } = event;
12813
+ console.log("[SwarmMail] Appending event", {
12814
+ type,
12815
+ projectKey: project_key,
12816
+ timestamp
12817
+ });
12813
12818
  const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
12814
12819
  VALUES ($1, $2, $3, $4)
12815
12820
  RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
@@ -12818,6 +12823,13 @@ async function appendEvent(event, projectPath) {
12818
12823
  throw new Error("Failed to insert event - no row returned");
12819
12824
  }
12820
12825
  const { id, sequence } = row;
12826
+ console.log("[SwarmMail] Event appended", {
12827
+ type,
12828
+ id,
12829
+ sequence,
12830
+ projectKey: project_key
12831
+ });
12832
+ console.debug("[SwarmMail] Updating materialized views", { type, id });
12821
12833
  await updateMaterializedViews(db, { ...event, id, sequence });
12822
12834
  return { ...event, id, sequence };
12823
12835
  }
@@ -12841,9 +12853,13 @@ async function appendEvents(events, projectPath) {
12841
12853
  results.push(enrichedEvent);
12842
12854
  }
12843
12855
  await db.exec("COMMIT");
12844
- } catch (error45) {
12845
- await db.exec("ROLLBACK");
12846
- throw error45;
12856
+ } catch (e) {
12857
+ try {
12858
+ await db.exec("ROLLBACK");
12859
+ } catch (rollbackError) {
12860
+ console.error("[SwarmMail] ROLLBACK failed:", rollbackError);
12861
+ }
12862
+ throw e;
12847
12863
  }
12848
12864
  return results;
12849
12865
  }
@@ -12946,33 +12962,42 @@ async function replayEvents(options2 = {}, projectPath) {
12946
12962
  };
12947
12963
  }
12948
12964
  async function updateMaterializedViews(db, event) {
12949
- switch (event.type) {
12950
- case "agent_registered":
12951
- await handleAgentRegistered(db, event);
12952
- break;
12953
- case "agent_active":
12954
- await db.query(`UPDATE agents SET last_active_at = $1 WHERE project_key = $2 AND name = $3`, [event.timestamp, event.project_key, event.agent_name]);
12955
- break;
12956
- case "message_sent":
12957
- await handleMessageSent(db, event);
12958
- break;
12959
- case "message_read":
12960
- await db.query(`UPDATE message_recipients SET read_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
12961
- break;
12962
- case "message_acked":
12963
- await db.query(`UPDATE message_recipients SET acked_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
12964
- break;
12965
- case "file_reserved":
12966
- await handleFileReserved(db, event);
12967
- break;
12968
- case "file_released":
12969
- await handleFileReleased(db, event);
12970
- break;
12971
- case "task_started":
12972
- case "task_progress":
12973
- case "task_completed":
12974
- case "task_blocked":
12975
- break;
12965
+ try {
12966
+ switch (event.type) {
12967
+ case "agent_registered":
12968
+ await handleAgentRegistered(db, event);
12969
+ break;
12970
+ case "agent_active":
12971
+ await db.query(`UPDATE agents SET last_active_at = $1 WHERE project_key = $2 AND name = $3`, [event.timestamp, event.project_key, event.agent_name]);
12972
+ break;
12973
+ case "message_sent":
12974
+ await handleMessageSent(db, event);
12975
+ break;
12976
+ case "message_read":
12977
+ await db.query(`UPDATE message_recipients SET read_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
12978
+ break;
12979
+ case "message_acked":
12980
+ await db.query(`UPDATE message_recipients SET acked_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
12981
+ break;
12982
+ case "file_reserved":
12983
+ await handleFileReserved(db, event);
12984
+ break;
12985
+ case "file_released":
12986
+ await handleFileReleased(db, event);
12987
+ break;
12988
+ case "task_started":
12989
+ case "task_progress":
12990
+ case "task_completed":
12991
+ case "task_blocked":
12992
+ break;
12993
+ }
12994
+ } catch (error45) {
12995
+ console.error("[SwarmMail] Failed to update materialized views", {
12996
+ eventType: event.type,
12997
+ eventId: event.id,
12998
+ error: error45
12999
+ });
13000
+ throw error45;
12976
13001
  }
12977
13002
  }
12978
13003
  async function handleAgentRegistered(db, event) {
@@ -12992,6 +13017,12 @@ async function handleAgentRegistered(db, event) {
12992
13017
  ]);
12993
13018
  }
12994
13019
  async function handleMessageSent(db, event) {
13020
+ console.log("[SwarmMail] Handling message sent event", {
13021
+ from: event.from_agent,
13022
+ to: event.to_agents,
13023
+ subject: event.subject,
13024
+ projectKey: event.project_key
13025
+ });
12995
13026
  const result = await db.query(`INSERT INTO messages (project_key, from_agent, subject, body, thread_id, importance, ack_required, created_at)
12996
13027
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
12997
13028
  RETURNING id`, [
@@ -13009,24 +13040,42 @@ async function handleMessageSent(db, event) {
13009
13040
  throw new Error("Failed to insert message - no row returned");
13010
13041
  }
13011
13042
  const messageId = msgRow.id;
13012
- for (const agent of event.to_agents) {
13043
+ if (event.to_agents.length > 0) {
13044
+ const values = event.to_agents.map((_, i) => `($1, $${i + 2})`).join(", ");
13045
+ const params = [messageId, ...event.to_agents];
13013
13046
  await db.query(`INSERT INTO message_recipients (message_id, agent_name)
13014
- VALUES ($1, $2)
13015
- ON CONFLICT DO NOTHING`, [messageId, agent]);
13047
+ VALUES ${values}
13048
+ ON CONFLICT DO NOTHING`, params);
13049
+ console.log("[SwarmMail] Message recipients inserted", {
13050
+ messageId,
13051
+ recipientCount: event.to_agents.length
13052
+ });
13016
13053
  }
13017
13054
  }
13018
13055
  async function handleFileReserved(db, event) {
13019
- for (const path of event.paths) {
13020
- await db.query(`INSERT INTO reservations (project_key, agent_name, path_pattern, exclusive, reason, created_at, expires_at)
13021
- VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
13056
+ console.log("[SwarmMail] Handling file reservation event", {
13057
+ agent: event.agent_name,
13058
+ paths: event.paths,
13059
+ exclusive: event.exclusive,
13060
+ projectKey: event.project_key
13061
+ });
13062
+ if (event.paths.length > 0) {
13063
+ const values = event.paths.map((_, i) => `($1, $2, $${i + 3}, $${event.paths.length + 3}, $${event.paths.length + 4}, $${event.paths.length + 5}, $${event.paths.length + 6})`).join(", ");
13064
+ const params = [
13022
13065
  event.project_key,
13023
13066
  event.agent_name,
13024
- path,
13067
+ ...event.paths,
13025
13068
  event.exclusive,
13026
13069
  event.reason || null,
13027
13070
  event.timestamp,
13028
13071
  event.expires_at
13029
- ]);
13072
+ ];
13073
+ await db.query(`INSERT INTO reservations (project_key, agent_name, path_pattern, exclusive, reason, created_at, expires_at)
13074
+ VALUES ${values}`, params);
13075
+ console.log("[SwarmMail] File reservations inserted", {
13076
+ agent: event.agent_name,
13077
+ reservationCount: event.paths.length
13078
+ });
13030
13079
  }
13031
13080
  }
13032
13081
  async function handleFileReleased(db, event) {
@@ -14592,6 +14641,12 @@ async function checkConflicts(projectKey, agentName, paths, projectPath) {
14592
14641
  }
14593
14642
  for (const path2 of paths) {
14594
14643
  if (pathMatches(path2, reservation.path_pattern)) {
14644
+ console.warn("[SwarmMail] Conflict detected", {
14645
+ path: path2,
14646
+ holder: reservation.agent_name,
14647
+ pattern: reservation.path_pattern,
14648
+ requestedBy: agentName
14649
+ });
14595
14650
  conflicts.push({
14596
14651
  path: path2,
14597
14652
  holder: reservation.agent_name,
@@ -14601,6 +14656,13 @@ async function checkConflicts(projectKey, agentName, paths, projectPath) {
14601
14656
  }
14602
14657
  }
14603
14658
  }
14659
+ if (conflicts.length > 0) {
14660
+ console.warn("[SwarmMail] Total conflicts detected", {
14661
+ count: conflicts.length,
14662
+ requestedBy: agentName,
14663
+ paths
14664
+ });
14665
+ }
14604
14666
  return conflicts;
14605
14667
  }
14606
14668
  function pathMatches(path2, pattern) {
@@ -15271,6 +15333,7 @@ var init_migrations = __esm(() => {
15271
15333
  // src/streams/index.ts
15272
15334
  var exports_streams = {};
15273
15335
  __export(exports_streams, {
15336
+ withTimeout: () => withTimeout,
15274
15337
  sendSwarmMessage: () => sendSwarmMessage,
15275
15338
  sendMessage: () => sendMessage,
15276
15339
  sendAgentMessage: () => sendAgentMessage,
@@ -15343,6 +15406,10 @@ import { PGlite } from "@electric-sql/pglite";
15343
15406
  import { existsSync, mkdirSync, appendFileSync } from "node:fs";
15344
15407
  import { join } from "node:path";
15345
15408
  import { homedir } from "node:os";
15409
+ async function withTimeout(promise2, ms, operation) {
15410
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms));
15411
+ return Promise.race([promise2, timeout]);
15412
+ }
15346
15413
  function debugLog(message, data) {
15347
15414
  const timestamp = new Date().toISOString();
15348
15415
  const logLine = data ? `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
@@ -30697,14 +30764,35 @@ class SqliteRateLimiter {
30697
30764
  }
30698
30765
  return { allowed, remaining, resetAt };
30699
30766
  }
30767
+ cleanup() {
30768
+ const BATCH_SIZE = 1000;
30769
+ const MAX_BATCHES = 10;
30770
+ const cutoff = Date.now() - 7200000;
30771
+ let totalDeleted = 0;
30772
+ for (let i = 0;i < MAX_BATCHES; i++) {
30773
+ const result = this.db.run(`DELETE FROM rate_limits
30774
+ WHERE rowid IN (
30775
+ SELECT rowid FROM rate_limits
30776
+ WHERE timestamp < ?
30777
+ LIMIT ?
30778
+ )`, [cutoff, BATCH_SIZE]);
30779
+ totalDeleted += result.changes;
30780
+ if (result.changes < BATCH_SIZE)
30781
+ break;
30782
+ }
30783
+ if (totalDeleted > 0) {
30784
+ console.log("[RateLimiter] Cleanup completed:", {
30785
+ deletedRows: totalDeleted
30786
+ });
30787
+ }
30788
+ }
30700
30789
  async recordRequest(agentName, endpoint) {
30701
30790
  const now = Date.now();
30702
30791
  const stmt = this.db.prepare(`INSERT INTO rate_limits (agent_name, endpoint, window, timestamp) VALUES (?, ?, ?, ?)`);
30703
30792
  stmt.run(agentName, endpoint, "minute", now);
30704
30793
  stmt.run(agentName, endpoint, "hour", now);
30705
30794
  if (Math.random() < 0.01) {
30706
- const cutoff = Date.now() - 7200000;
30707
- this.db.run(`DELETE FROM rate_limits WHERE timestamp < ?`, [cutoff]);
30795
+ this.cleanup();
30708
30796
  }
30709
30797
  }
30710
30798
  async close() {
@@ -32754,6 +32842,36 @@ class ErrorAccumulator {
32754
32842
  };
32755
32843
  }
32756
32844
  }
32845
+ function formatMemoryStoreOnSuccess(beadId, summary, filesTouched, strategy) {
32846
+ const strategyInfo = strategy ? ` using ${strategy} strategy` : "";
32847
+ return {
32848
+ information: `Task "${beadId}" completed successfully${strategyInfo}.
32849
+ Key insight: ${summary}
32850
+ Files touched: ${filesTouched.join(", ") || "none"}`,
32851
+ metadata: `swarm, success, ${beadId}, ${strategy || "completion"}`,
32852
+ instruction: "Store this successful completion in semantic-memory for future reference"
32853
+ };
32854
+ }
32855
+ function formatMemoryStoreOn3Strike(beadId, failures) {
32856
+ const failuresList = failures.map((f, i) => `${i + 1}. ${f.attempt} - Failed: ${f.reason}`).join(`
32857
+ `);
32858
+ return {
32859
+ information: `Architecture problem detected in ${beadId}: Task failed after 3 attempts.
32860
+ Attempts:
32861
+ ${failuresList}
32862
+
32863
+ This indicates a structural issue requiring human decision, not another fix attempt.`,
32864
+ metadata: `architecture, 3-strike, ${beadId}, failure`,
32865
+ instruction: "Store this architectural problem in semantic-memory to avoid similar patterns in future"
32866
+ };
32867
+ }
32868
+ function formatMemoryQueryForDecomposition(task, limit = 3) {
32869
+ return {
32870
+ query: task,
32871
+ limit,
32872
+ instruction: "Query semantic-memory for relevant past learnings about similar tasks before decomposition"
32873
+ };
32874
+ }
32757
32875
 
32758
32876
  // src/swarm.ts
32759
32877
  init_skills();
@@ -33681,7 +33799,136 @@ ${args.context}` : `## Additional Context
33681
33799
  },
33682
33800
  validation_note: "Parse agent response as JSON and validate with swarm_validate_decomposition",
33683
33801
  cass_history: cassResultInfo,
33684
- skills: skillsInfo
33802
+ skills: skillsInfo,
33803
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3)
33804
+ }, null, 2);
33805
+ }
33806
+ });
33807
+ var swarm_delegate_planning = tool({
33808
+ description: "Delegate task decomposition to a swarm/planner subagent. Returns a prompt to spawn the planner. Use this to keep coordinator context lean - all planning reasoning happens in the subagent.",
33809
+ args: {
33810
+ task: tool.schema.string().min(1).describe("The task to decompose"),
33811
+ context: tool.schema.string().optional().describe("Additional context to include"),
33812
+ max_subtasks: tool.schema.number().int().min(2).max(10).optional().default(5).describe("Maximum number of subtasks (default: 5)"),
33813
+ strategy: tool.schema.enum(["auto", "file-based", "feature-based", "risk-based"]).optional().default("auto").describe("Decomposition strategy (default: auto-detect)"),
33814
+ query_cass: tool.schema.boolean().optional().default(true).describe("Query CASS for similar past tasks (default: true)")
33815
+ },
33816
+ async execute(args) {
33817
+ let selectedStrategy;
33818
+ let strategyReasoning;
33819
+ if (args.strategy && args.strategy !== "auto") {
33820
+ selectedStrategy = args.strategy;
33821
+ strategyReasoning = `User-specified strategy: ${selectedStrategy}`;
33822
+ } else {
33823
+ const selection = selectStrategy(args.task);
33824
+ selectedStrategy = selection.strategy;
33825
+ strategyReasoning = selection.reasoning;
33826
+ }
33827
+ let cassContext = "";
33828
+ let cassResultInfo;
33829
+ if (args.query_cass !== false) {
33830
+ const cassResult = await queryCassHistory(args.task, 3);
33831
+ if (cassResult.status === "success") {
33832
+ cassContext = formatCassHistoryForPrompt(cassResult.data);
33833
+ cassResultInfo = {
33834
+ queried: true,
33835
+ results_found: cassResult.data.results.length,
33836
+ included_in_context: true
33837
+ };
33838
+ } else {
33839
+ cassResultInfo = {
33840
+ queried: true,
33841
+ results_found: 0,
33842
+ included_in_context: false,
33843
+ reason: cassResult.status
33844
+ };
33845
+ }
33846
+ } else {
33847
+ cassResultInfo = { queried: false, reason: "disabled" };
33848
+ }
33849
+ let skillsContext = "";
33850
+ let skillsInfo = {
33851
+ included: false
33852
+ };
33853
+ const allSkills = await listSkills();
33854
+ if (allSkills.length > 0) {
33855
+ skillsContext = await getSkillsContextForSwarm();
33856
+ const relevantSkills = await findRelevantSkills(args.task);
33857
+ skillsInfo = {
33858
+ included: true,
33859
+ count: allSkills.length,
33860
+ relevant: relevantSkills
33861
+ };
33862
+ if (relevantSkills.length > 0) {
33863
+ skillsContext += `
33864
+
33865
+ **Suggested skills for this task**: ${relevantSkills.join(", ")}`;
33866
+ }
33867
+ }
33868
+ const strategyGuidelines = formatStrategyGuidelines(selectedStrategy);
33869
+ const contextSection = args.context ? `## Additional Context
33870
+ ${args.context}` : `## Additional Context
33871
+ (none provided)`;
33872
+ const planningPrompt = STRATEGY_DECOMPOSITION_PROMPT.replace("{task}", args.task).replace("{strategy_guidelines}", strategyGuidelines).replace("{context_section}", contextSection).replace("{cass_history}", cassContext || "").replace("{skills_context}", skillsContext || "").replace("{max_subtasks}", (args.max_subtasks ?? 5).toString());
33873
+ const subagentInstructions = `
33874
+ ## CRITICAL: Output Format
33875
+
33876
+ You are a planner subagent. Your ONLY output must be valid JSON matching the BeadTree schema.
33877
+
33878
+ DO NOT include:
33879
+ - Explanatory text before or after the JSON
33880
+ - Markdown code fences (\`\`\`json)
33881
+ - Commentary or reasoning
33882
+
33883
+ OUTPUT ONLY the raw JSON object.
33884
+
33885
+ ## Example Output
33886
+
33887
+ {
33888
+ "epic": {
33889
+ "title": "Add user authentication",
33890
+ "description": "Implement OAuth-based authentication system"
33891
+ },
33892
+ "subtasks": [
33893
+ {
33894
+ "title": "Set up OAuth provider",
33895
+ "description": "Configure OAuth client credentials and redirect URLs",
33896
+ "files": ["src/auth/oauth.ts", "src/config/auth.ts"],
33897
+ "dependencies": [],
33898
+ "estimated_complexity": 2
33899
+ },
33900
+ {
33901
+ "title": "Create auth routes",
33902
+ "description": "Implement login, logout, and callback routes",
33903
+ "files": ["src/app/api/auth/[...nextauth]/route.ts"],
33904
+ "dependencies": [0],
33905
+ "estimated_complexity": 3
33906
+ }
33907
+ ]
33908
+ }
33909
+
33910
+ Now generate the BeadTree for the given task.`;
33911
+ const fullPrompt = `${planningPrompt}
33912
+
33913
+ ${subagentInstructions}`;
33914
+ return JSON.stringify({
33915
+ prompt: fullPrompt,
33916
+ subagent_type: "swarm/planner",
33917
+ description: "Task decomposition planning",
33918
+ strategy: {
33919
+ selected: selectedStrategy,
33920
+ reasoning: strategyReasoning
33921
+ },
33922
+ expected_output: "BeadTree JSON (raw JSON, no markdown)",
33923
+ next_steps: [
33924
+ "1. Spawn subagent with Task tool using returned prompt",
33925
+ "2. Parse subagent response as JSON",
33926
+ "3. Validate with swarm_validate_decomposition",
33927
+ "4. Create beads with beads_create_epic"
33928
+ ],
33929
+ cass_history: cassResultInfo,
33930
+ skills: skillsInfo,
33931
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3)
33685
33932
  }, null, 2);
33686
33933
  }
33687
33934
  });
@@ -33737,7 +33984,8 @@ var swarm_decompose = tool({
33737
33984
  ]
33738
33985
  },
33739
33986
  validation_note: "Parse agent response as JSON and validate with BeadTreeSchema from schemas/bead.ts",
33740
- cass_history: cassResultInfo
33987
+ cass_history: cassResultInfo,
33988
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3)
33741
33989
  }, null, 2);
33742
33990
  }
33743
33991
  });
@@ -34254,7 +34502,7 @@ var swarm_complete = tool({
34254
34502
  threadId: epicId,
34255
34503
  importance: "normal"
34256
34504
  });
34257
- return JSON.stringify({
34505
+ const response = {
34258
34506
  success: true,
34259
34507
  bead_id: args.bead_id,
34260
34508
  closed: true,
@@ -34290,8 +34538,10 @@ Did you learn anything reusable during this subtask? Consider:
34290
34538
 
34291
34539
  If you discovered something valuable, use \`swarm_learn\` or \`skills_create\` to preserve it as a skill for future swarms.
34292
34540
 
34293
- Files touched: ${args.files_touched?.join(", ") || "none recorded"}`
34294
- }, null, 2);
34541
+ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
34542
+ memory_store: formatMemoryStoreOnSuccess(args.bead_id, args.summary, args.files_touched || [])
34543
+ };
34544
+ return JSON.stringify(response, null, 2);
34295
34545
  }
34296
34546
  });
34297
34547
  function classifyFailure(error45) {
@@ -34827,6 +35077,7 @@ var swarmTools = {
34827
35077
  swarm_init,
34828
35078
  swarm_select_strategy,
34829
35079
  swarm_plan_prompt,
35080
+ swarm_delegate_planning,
34830
35081
  swarm_decompose,
34831
35082
  swarm_validate_decomposition,
34832
35083
  swarm_status,
@@ -34873,14 +35124,18 @@ var swarm_check_strikes = tool({
34873
35124
  }
34874
35125
  const record2 = await addStrike(args.bead_id, args.attempt, args.reason, globalStrikeStorage);
34875
35126
  const strikedOut = record2.strike_count >= 3;
34876
- return JSON.stringify({
35127
+ const response = {
34877
35128
  bead_id: args.bead_id,
34878
35129
  strike_count: record2.strike_count,
34879
35130
  is_striked_out: strikedOut,
34880
35131
  failures: record2.failures,
34881
35132
  message: strikedOut ? "⚠️ STRUCK OUT: 3 strikes reached. STOP and question the architecture." : `Strike ${record2.strike_count} recorded. ${3 - record2.strike_count} remaining.`,
34882
35133
  warning: strikedOut ? "DO NOT attempt Fix #4. Call with action=get_prompt for architecture review." : undefined
34883
- }, null, 2);
35134
+ };
35135
+ if (strikedOut) {
35136
+ response.memory_store = formatMemoryStoreOn3Strike(args.bead_id, record2.failures);
35137
+ }
35138
+ return JSON.stringify(response, null, 2);
34884
35139
  }
34885
35140
  case "clear": {
34886
35141
  await clearStrikes(args.bead_id, globalStrikeStorage);