opencode-swarm-plugin 0.1.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/src/index.ts ADDED
@@ -0,0 +1,267 @@
1
+ /**
2
+ * OpenCode Swarm Plugin
3
+ *
4
+ * A type-safe plugin for multi-agent coordination with beads issue tracking
5
+ * and Agent Mail integration. Provides structured tools for swarm operations.
6
+ *
7
+ * @module opencode-swarm-plugin
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // In opencode.jsonc
12
+ * {
13
+ * "plugins": ["opencode-swarm-plugin"]
14
+ * }
15
+ * ```
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Programmatic usage
20
+ * import { beadsTools, agentMailTools } from "opencode-swarm-plugin"
21
+ * ```
22
+ */
23
+ import type { Plugin, PluginInput, Hooks } from "@opencode-ai/plugin";
24
+
25
+ import { beadsTools } from "./beads";
26
+ import {
27
+ agentMailTools,
28
+ type AgentMailState,
29
+ AGENT_MAIL_URL,
30
+ } from "./agent-mail";
31
+ import { structuredTools } from "./structured";
32
+ import { swarmTools } from "./swarm";
33
+
34
+ /**
35
+ * OpenCode Swarm Plugin
36
+ *
37
+ * Registers all swarm coordination tools:
38
+ * - beads:* - Type-safe beads issue tracker wrappers
39
+ * - agent-mail:* - Multi-agent coordination via Agent Mail MCP
40
+ * - structured:* - Structured output parsing and validation
41
+ * - swarm:* - Swarm orchestration and task decomposition
42
+ *
43
+ * @param input - Plugin context from OpenCode
44
+ * @returns Plugin hooks including tools, events, and tool execution hooks
45
+ */
46
+ export const SwarmPlugin: Plugin = async (
47
+ input: PluginInput,
48
+ ): Promise<Hooks> => {
49
+ const { $ } = input;
50
+
51
+ /** Track active sessions for cleanup */
52
+ let activeAgentMailState: AgentMailState | null = null;
53
+
54
+ /**
55
+ * Release all file reservations for the active agent
56
+ * Best-effort cleanup - errors are logged but not thrown
57
+ */
58
+ async function releaseReservations(): Promise<void> {
59
+ if (
60
+ !activeAgentMailState ||
61
+ activeAgentMailState.reservations.length === 0
62
+ ) {
63
+ return;
64
+ }
65
+
66
+ try {
67
+ const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json" },
70
+ body: JSON.stringify({
71
+ jsonrpc: "2.0",
72
+ id: crypto.randomUUID(),
73
+ method: "tools/call",
74
+ params: {
75
+ name: "release_file_reservations",
76
+ arguments: {
77
+ project_key: activeAgentMailState.projectKey,
78
+ agent_name: activeAgentMailState.agentName,
79
+ },
80
+ },
81
+ }),
82
+ });
83
+
84
+ if (response.ok) {
85
+ console.log(
86
+ `[swarm-plugin] Auto-released ${activeAgentMailState.reservations.length} file reservation(s)`,
87
+ );
88
+ activeAgentMailState.reservations = [];
89
+ }
90
+ } catch (error) {
91
+ // Agent Mail might not be running - that's ok
92
+ console.warn(
93
+ `[swarm-plugin] Could not auto-release reservations: ${error instanceof Error ? error.message : String(error)}`,
94
+ );
95
+ }
96
+ }
97
+
98
+ return {
99
+ /**
100
+ * Register all tools from modules
101
+ *
102
+ * Tools are namespaced by module:
103
+ * - beads:create, beads:query, beads:update, etc.
104
+ * - agent-mail:init, agent-mail:send, agent-mail:reserve, etc.
105
+ */
106
+ tool: {
107
+ ...beadsTools,
108
+ ...agentMailTools,
109
+ ...structuredTools,
110
+ ...swarmTools,
111
+ },
112
+
113
+ /**
114
+ * Event hook for session lifecycle
115
+ *
116
+ * Handles cleanup when session becomes idle:
117
+ * - Releases any held file reservations
118
+ */
119
+ event: async ({ event }) => {
120
+ // Auto-release reservations on session idle
121
+ if (event.type === "session.idle") {
122
+ await releaseReservations();
123
+ }
124
+ },
125
+
126
+ /**
127
+ * Hook after tool execution for automatic cleanup
128
+ *
129
+ * Auto-releases file reservations after swarm:complete or beads:close
130
+ * to prevent stale locks when tasks finish.
131
+ */
132
+ "tool.execute.after": async (input, output) => {
133
+ const toolName = input.tool;
134
+
135
+ // Track Agent Mail state for cleanup
136
+ if (toolName === "agentmail_init" && output.output) {
137
+ try {
138
+ const result = JSON.parse(output.output);
139
+ if (result.agent) {
140
+ activeAgentMailState = {
141
+ projectKey: result.project?.human_key || "",
142
+ agentName: result.agent.name,
143
+ reservations: [],
144
+ startedAt: new Date().toISOString(),
145
+ };
146
+ }
147
+ } catch {
148
+ // Parsing failed - ignore
149
+ }
150
+ }
151
+
152
+ // Track reservations from output
153
+ if (
154
+ toolName === "agentmail_reserve" &&
155
+ output.output &&
156
+ activeAgentMailState
157
+ ) {
158
+ // Extract reservation count from output if present
159
+ const match = output.output.match(/Reserved (\d+) path/);
160
+ if (match) {
161
+ // Track reservation for cleanup
162
+ activeAgentMailState.reservations.push(Date.now());
163
+ }
164
+ }
165
+
166
+ // Auto-release after swarm:complete
167
+ if (toolName === "swarm_complete" && activeAgentMailState) {
168
+ await releaseReservations();
169
+ console.log(
170
+ "[swarm-plugin] Auto-released reservations after swarm:complete",
171
+ );
172
+ }
173
+
174
+ // Auto-sync beads after closing
175
+ if (toolName === "beads_close") {
176
+ // Trigger async sync without blocking - fire and forget
177
+ void $`bd sync`
178
+ .quiet()
179
+ .nothrow()
180
+ .then(() => {
181
+ console.log("[swarm-plugin] Auto-synced beads after close");
182
+ });
183
+ }
184
+ },
185
+ };
186
+ };
187
+
188
+ /**
189
+ * Default export for OpenCode plugin loading
190
+ *
191
+ * OpenCode loads plugins by their default export, so this allows:
192
+ * ```json
193
+ * { "plugins": ["opencode-swarm-plugin"] }
194
+ * ```
195
+ */
196
+ export default SwarmPlugin;
197
+
198
+ // =============================================================================
199
+ // Re-exports for programmatic use
200
+ // =============================================================================
201
+
202
+ /**
203
+ * Re-export all schemas for type-safe usage
204
+ */
205
+ export * from "./schemas";
206
+
207
+ /**
208
+ * Re-export beads module
209
+ *
210
+ * Includes:
211
+ * - beadsTools - All bead tool definitions
212
+ * - Individual tool exports (beads_create, beads_query, etc.)
213
+ * - BeadError, BeadValidationError - Error classes
214
+ */
215
+ export * from "./beads";
216
+
217
+ /**
218
+ * Re-export agent-mail module
219
+ *
220
+ * Includes:
221
+ * - agentMailTools - All agent mail tool definitions
222
+ * - AgentMailError, FileReservationConflictError - Error classes
223
+ * - AgentMailState - Session state type
224
+ *
225
+ * NOTE: We selectively export to avoid exporting constants like AGENT_MAIL_URL
226
+ * which would confuse the plugin loader (it tries to call all exports as functions)
227
+ */
228
+ export {
229
+ agentMailTools,
230
+ AgentMailError,
231
+ AgentMailNotInitializedError,
232
+ FileReservationConflictError,
233
+ type AgentMailState,
234
+ } from "./agent-mail";
235
+
236
+ /**
237
+ * Re-export structured module
238
+ *
239
+ * Includes:
240
+ * - structuredTools - Structured output parsing tools
241
+ * - Utility functions for JSON extraction
242
+ */
243
+ export {
244
+ structuredTools,
245
+ extractJsonFromText,
246
+ formatZodErrors,
247
+ getSchemaByName,
248
+ } from "./structured";
249
+
250
+ /**
251
+ * Re-export swarm module
252
+ *
253
+ * Includes:
254
+ * - swarmTools - Swarm orchestration tools
255
+ * - SwarmError, DecompositionError - Error classes
256
+ * - formatSubtaskPrompt, formatEvaluationPrompt - Prompt helpers
257
+ *
258
+ * NOTE: Prompt template strings (DECOMPOSITION_PROMPT, etc.) are NOT exported
259
+ * to avoid confusing the plugin loader which tries to call all exports as functions
260
+ */
261
+ export {
262
+ swarmTools,
263
+ SwarmError,
264
+ DecompositionError,
265
+ formatSubtaskPrompt,
266
+ formatEvaluationPrompt,
267
+ } from "./swarm";