opencode-swarm-plugin 0.9.0 → 0.10.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/README.md +184 -105
- package/bin/swarm.ts +89 -16
- package/dist/index.js +232 -26
- package/dist/plugin.js +232 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,21 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/opencode-swarm-plugin)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
```
|
|
7
|
+
███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗
|
|
8
|
+
██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║
|
|
9
|
+
███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║
|
|
10
|
+
╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║
|
|
11
|
+
███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║
|
|
12
|
+
╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
|
|
13
|
+
|
|
14
|
+
\ ` - ' /
|
|
15
|
+
- .(o o). -
|
|
16
|
+
( >.< ) Multi-agent coordination for OpenCode
|
|
17
|
+
/| |\ Break complex tasks into parallel subtasks,
|
|
18
|
+
(_| |_) spawn agents, coordinate via messaging.
|
|
19
|
+
bzzzz... The plugin learns from outcomes.
|
|
20
|
+
```
|
|
7
21
|
|
|
8
22
|
## Install
|
|
9
23
|
|
|
@@ -12,10 +26,10 @@ npm install -g opencode-swarm-plugin
|
|
|
12
26
|
swarm setup
|
|
13
27
|
```
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
The setup wizard handles everything:
|
|
16
30
|
|
|
17
31
|
```
|
|
18
|
-
┌ opencode-swarm-plugin v0.
|
|
32
|
+
┌ opencode-swarm-plugin v0.10.0
|
|
19
33
|
│
|
|
20
34
|
◇ Checking dependencies...
|
|
21
35
|
│
|
|
@@ -51,72 +65,138 @@ swarm init
|
|
|
51
65
|
swarm setup Interactive installer - checks and installs all dependencies
|
|
52
66
|
swarm doctor Health check - shows status of all dependencies
|
|
53
67
|
swarm init Initialize beads in current project
|
|
54
|
-
swarm
|
|
68
|
+
swarm config Show paths to generated config files
|
|
69
|
+
swarm version Show version and banner
|
|
55
70
|
swarm help Show help
|
|
56
71
|
```
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
In OpenCode:
|
|
59
76
|
|
|
60
77
|
```
|
|
61
|
-
|
|
62
|
-
│
|
|
63
|
-
◇ Required dependencies:
|
|
64
|
-
│
|
|
65
|
-
◆ OpenCode v1.0.134
|
|
66
|
-
◆ Beads v0.29.0
|
|
67
|
-
│
|
|
68
|
-
◇ Optional dependencies:
|
|
69
|
-
│
|
|
70
|
-
◆ Go v1.25.2 - Required for Agent Mail
|
|
71
|
-
▲ Agent Mail - not found
|
|
72
|
-
◆ Redis - Rate limiting
|
|
73
|
-
│
|
|
74
|
-
└ All required dependencies installed. 1 optional missing.
|
|
78
|
+
/swarm "Add user authentication with OAuth"
|
|
75
79
|
```
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
Or invoke the planner directly:
|
|
78
82
|
|
|
79
83
|
```
|
|
80
|
-
|
|
81
|
-
│
|
|
82
|
-
◇ Initializing beads...
|
|
83
|
-
◆ Created .beads/ directory
|
|
84
|
-
│
|
|
85
|
-
◆ Create your first bead?
|
|
86
|
-
│ ● Yes / ○ No
|
|
87
|
-
│
|
|
88
|
-
◇ Bead title: Implement user authentication
|
|
89
|
-
◇ Type: Feature
|
|
90
|
-
│
|
|
91
|
-
└ Project initialized!
|
|
84
|
+
@swarm-planner "Refactor all components to use hooks"
|
|
92
85
|
```
|
|
93
86
|
|
|
94
|
-
##
|
|
87
|
+
## Customization
|
|
95
88
|
|
|
96
|
-
|
|
89
|
+
Run `swarm config` to see your config file paths:
|
|
97
90
|
|
|
98
91
|
```
|
|
99
|
-
|
|
92
|
+
🔌 Plugin loader
|
|
93
|
+
~/.config/opencode/plugins/swarm.ts
|
|
94
|
+
|
|
95
|
+
📜 /swarm command prompt
|
|
96
|
+
~/.config/opencode/commands/swarm.md
|
|
97
|
+
|
|
98
|
+
🤖 @swarm-planner agent
|
|
99
|
+
~/.config/opencode/agents/swarm-planner.md
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
### /swarm Command
|
|
103
|
+
|
|
104
|
+
The `/swarm` command is defined in `~/.config/opencode/commands/swarm.md`:
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
---
|
|
108
|
+
description: Decompose task into parallel subtasks and coordinate agents
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
You are a swarm coordinator. Take a complex task, break it into beads,
|
|
112
|
+
and unleash parallel agents.
|
|
113
|
+
|
|
114
|
+
## Usage
|
|
115
|
+
|
|
116
|
+
/swarm <task description or bead-id>
|
|
117
|
+
|
|
118
|
+
## Workflow
|
|
103
119
|
|
|
120
|
+
1. **Initialize**: `agentmail_init` with project_path and task_description
|
|
121
|
+
2. **Decompose**: Use `swarm_select_strategy` then `swarm_plan_prompt`
|
|
122
|
+
3. **Create beads**: `beads_create_epic` with subtasks and file assignments
|
|
123
|
+
4. **Reserve files**: `agentmail_reserve` for each subtask's files
|
|
124
|
+
5. **Spawn agents**: Use Task tool with `swarm_spawn_subtask` prompts
|
|
125
|
+
6. **Monitor**: Check `agentmail_inbox` for progress
|
|
126
|
+
7. **Complete**: `swarm_complete` when done, then `beads_sync` to push
|
|
127
|
+
|
|
128
|
+
## Strategy Selection
|
|
129
|
+
|
|
130
|
+
| Strategy | Best For | Keywords |
|
|
131
|
+
| ------------- | ----------------------- | ------------------------------------- |
|
|
132
|
+
| file-based | Refactoring, migrations | refactor, migrate, rename, update all |
|
|
133
|
+
| feature-based | New features | add, implement, build, create |
|
|
134
|
+
| risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
|
|
135
|
+
|
|
136
|
+
Begin decomposition now.
|
|
104
137
|
```
|
|
105
|
-
|
|
138
|
+
|
|
139
|
+
### @swarm-planner Agent
|
|
140
|
+
|
|
141
|
+
The `@swarm-planner` agent is defined in `~/.config/opencode/agents/swarm-planner.md`:
|
|
142
|
+
|
|
143
|
+
````markdown
|
|
144
|
+
---
|
|
145
|
+
name: swarm-planner
|
|
146
|
+
description: Strategic task decomposition for swarm coordination
|
|
147
|
+
model: claude-sonnet-4-5
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
You are a swarm planner. Decompose tasks into optimal parallel subtasks.
|
|
151
|
+
|
|
152
|
+
## Workflow
|
|
153
|
+
|
|
154
|
+
1. Call `swarm_select_strategy` to analyze the task
|
|
155
|
+
2. Call `swarm_plan_prompt` to get strategy-specific guidance
|
|
156
|
+
3. Create a BeadTree following the guidelines
|
|
157
|
+
4. Return ONLY valid JSON - no markdown, no explanation
|
|
158
|
+
|
|
159
|
+
## Output Format
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"epic": { "title": "...", "description": "..." },
|
|
164
|
+
"subtasks": [
|
|
165
|
+
{
|
|
166
|
+
"title": "...",
|
|
167
|
+
"description": "...",
|
|
168
|
+
"files": ["src/..."],
|
|
169
|
+
"dependencies": [],
|
|
170
|
+
"estimated_complexity": 2
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
106
174
|
```
|
|
175
|
+
````
|
|
176
|
+
|
|
177
|
+
## Rules
|
|
178
|
+
|
|
179
|
+
- 2-7 subtasks (too few = not parallel, too many = overhead)
|
|
180
|
+
- No file overlap between subtasks
|
|
181
|
+
- Include tests with the code they test
|
|
182
|
+
- Order by dependency (if B needs A, A comes first)
|
|
183
|
+
|
|
184
|
+
````
|
|
185
|
+
|
|
186
|
+
Edit these files to customize behavior. Run `swarm setup` to regenerate defaults.
|
|
107
187
|
|
|
108
188
|
## Dependencies
|
|
109
189
|
|
|
110
|
-
| Dependency
|
|
111
|
-
|
|
112
|
-
| [OpenCode](https://opencode.ai)
|
|
113
|
-
| [Beads](https://github.com/steveyegge/beads)
|
|
114
|
-
| [Go](https://go.dev)
|
|
115
|
-
| [Agent Mail](https://github.com/joelhooks/agent-mail)
|
|
116
|
-
| [CASS](https://github.com/Dicklesworthstone/cass)
|
|
117
|
-
| [UBS](https://github.com/joelhooks/ubs)
|
|
118
|
-
| [semantic-memory](https://github.com/joelhooks/semantic-memory) | Learning persistence
|
|
119
|
-
| [Redis](https://redis.io)
|
|
190
|
+
| Dependency | Purpose | Required |
|
|
191
|
+
|------------|---------|----------|
|
|
192
|
+
| [OpenCode](https://opencode.ai) | Plugin host | Yes |
|
|
193
|
+
| [Beads](https://github.com/steveyegge/beads) | Git-backed issue tracking | Yes |
|
|
194
|
+
| [Go](https://go.dev) | Required for Agent Mail | No |
|
|
195
|
+
| [Agent Mail](https://github.com/joelhooks/agent-mail) | Multi-agent coordination, file reservations | No |
|
|
196
|
+
| [CASS](https://github.com/Dicklesworthstone/cass) | Historical context from past sessions | No |
|
|
197
|
+
| [UBS](https://github.com/joelhooks/ubs) | Pre-completion bug scanning | No |
|
|
198
|
+
| [semantic-memory](https://github.com/joelhooks/semantic-memory) | Learning persistence | No |
|
|
199
|
+
| [Redis](https://redis.io) | Rate limiting (SQLite fallback available) | No |
|
|
120
200
|
|
|
121
201
|
All dependencies are checked and can be installed via `swarm setup`.
|
|
122
202
|
|
|
@@ -124,58 +204,57 @@ All dependencies are checked and can be installed via `swarm setup`.
|
|
|
124
204
|
|
|
125
205
|
### Swarm
|
|
126
206
|
|
|
127
|
-
| Tool
|
|
128
|
-
|
|
129
|
-
| `swarm_init`
|
|
130
|
-
| `swarm_select_strategy`
|
|
131
|
-
| `swarm_plan_prompt`
|
|
132
|
-
| `swarm_decompose`
|
|
133
|
-
| `swarm_validate_decomposition` | Validate response, detect file conflicts
|
|
134
|
-
| `swarm_spawn_subtask`
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `swarm_record_outcome` | Record outcome for learning (duration, errors, retries) |
|
|
207
|
+
| Tool | Description |
|
|
208
|
+
|------|-------------|
|
|
209
|
+
| `swarm_init` | Initialize swarm session |
|
|
210
|
+
| `swarm_select_strategy` | Analyze task, recommend decomposition strategy (file/feature/risk-based) |
|
|
211
|
+
| `swarm_plan_prompt` | Generate strategy-specific planning prompt with CASS history |
|
|
212
|
+
| `swarm_decompose` | Generate decomposition prompt |
|
|
213
|
+
| `swarm_validate_decomposition` | Validate response, detect file conflicts |
|
|
214
|
+
| `swarm_spawn_subtask` | Generate worker agent prompt with Agent Mail/beads instructions |
|
|
215
|
+
| `swarm_status` | Get swarm progress by epic ID |
|
|
216
|
+
| `swarm_progress` | Report subtask progress to coordinator |
|
|
217
|
+
| `swarm_complete` | Complete subtask - runs UBS scan, releases reservations |
|
|
218
|
+
| `swarm_record_outcome` | Record outcome for learning (duration, errors, retries) |
|
|
140
219
|
|
|
141
220
|
### Beads
|
|
142
221
|
|
|
143
|
-
| Tool
|
|
144
|
-
|
|
145
|
-
| `beads_create`
|
|
146
|
-
| `beads_create_epic` | Create epic + subtasks atomically
|
|
147
|
-
| `beads_query`
|
|
148
|
-
| `beads_update`
|
|
149
|
-
| `beads_close`
|
|
150
|
-
| `beads_start`
|
|
151
|
-
| `beads_ready`
|
|
152
|
-
| `beads_sync`
|
|
153
|
-
| `beads_link_thread` | Link bead to Agent Mail thread
|
|
222
|
+
| Tool | Description |
|
|
223
|
+
|------|-------------|
|
|
224
|
+
| `beads_create` | Create bead with type-safe validation |
|
|
225
|
+
| `beads_create_epic` | Create epic + subtasks atomically |
|
|
226
|
+
| `beads_query` | Query beads with filters (status, type, ready) |
|
|
227
|
+
| `beads_update` | Update status/description/priority |
|
|
228
|
+
| `beads_close` | Close bead with reason |
|
|
229
|
+
| `beads_start` | Mark bead as in-progress |
|
|
230
|
+
| `beads_ready` | Get next unblocked bead |
|
|
231
|
+
| `beads_sync` | Sync to git and push |
|
|
232
|
+
| `beads_link_thread` | Link bead to Agent Mail thread |
|
|
154
233
|
|
|
155
234
|
### Agent Mail
|
|
156
235
|
|
|
157
|
-
| Tool
|
|
158
|
-
|
|
159
|
-
| `agentmail_init`
|
|
160
|
-
| `agentmail_send`
|
|
161
|
-
| `agentmail_inbox`
|
|
162
|
-
| `agentmail_read_message`
|
|
236
|
+
| Tool | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `agentmail_init` | Initialize session, register agent |
|
|
239
|
+
| `agentmail_send` | Send message to agents |
|
|
240
|
+
| `agentmail_inbox` | Fetch inbox (max 5, no bodies - context safe) |
|
|
241
|
+
| `agentmail_read_message` | Fetch single message body by ID |
|
|
163
242
|
| `agentmail_summarize_thread` | Summarize thread (preferred over fetching all) |
|
|
164
|
-
| `agentmail_reserve`
|
|
165
|
-
| `agentmail_release`
|
|
166
|
-
| `agentmail_ack`
|
|
167
|
-
| `agentmail_search`
|
|
168
|
-
| `agentmail_health`
|
|
243
|
+
| `agentmail_reserve` | Reserve file paths for exclusive editing |
|
|
244
|
+
| `agentmail_release` | Release file reservations |
|
|
245
|
+
| `agentmail_ack` | Acknowledge message |
|
|
246
|
+
| `agentmail_search` | Search messages by keyword |
|
|
247
|
+
| `agentmail_health` | Check if Agent Mail server is running |
|
|
169
248
|
|
|
170
249
|
### Structured Output
|
|
171
250
|
|
|
172
|
-
| Tool
|
|
173
|
-
|
|
174
|
-
| `structured_extract_json`
|
|
175
|
-
| `structured_validate`
|
|
176
|
-
| `structured_parse_evaluation`
|
|
177
|
-
| `structured_parse_decomposition` | Parse task decomposition response
|
|
178
|
-
| `structured_parse_bead_tree`
|
|
251
|
+
| Tool | Description |
|
|
252
|
+
|------|-------------|
|
|
253
|
+
| `structured_extract_json` | Extract JSON from markdown/text (multiple strategies) |
|
|
254
|
+
| `structured_validate` | Validate response against schema |
|
|
255
|
+
| `structured_parse_evaluation` | Parse self-evaluation response |
|
|
256
|
+
| `structured_parse_decomposition` | Parse task decomposition response |
|
|
257
|
+
| `structured_parse_bead_tree` | Parse bead tree for epic creation |
|
|
179
258
|
|
|
180
259
|
## Decomposition Strategies
|
|
181
260
|
|
|
@@ -213,32 +292,32 @@ Best for: bug fixes, security issues
|
|
|
213
292
|
|
|
214
293
|
The plugin learns from outcomes:
|
|
215
294
|
|
|
216
|
-
| Mechanism
|
|
217
|
-
|
|
218
|
-
| Confidence decay
|
|
219
|
-
| Implicit feedback | Fast + success = helpful signal, slow + errors = harmful
|
|
220
|
-
| Pattern maturity
|
|
221
|
-
| Anti-patterns
|
|
295
|
+
| Mechanism | How It Works |
|
|
296
|
+
|-----------|--------------|
|
|
297
|
+
| Confidence decay | Criteria weights fade unless revalidated (90-day half-life) |
|
|
298
|
+
| Implicit feedback | Fast + success = helpful signal, slow + errors = harmful |
|
|
299
|
+
| Pattern maturity | candidate → established → proven (or deprecated) |
|
|
300
|
+
| Anti-patterns | Patterns with >60% failure rate auto-invert |
|
|
222
301
|
|
|
223
302
|
## Context Preservation
|
|
224
303
|
|
|
225
304
|
Hard limits to prevent context exhaustion:
|
|
226
305
|
|
|
227
|
-
| Constraint
|
|
228
|
-
|
|
229
|
-
| Inbox limit
|
|
230
|
-
| Bodies excluded
|
|
231
|
-
| Summarize preferred | Yes
|
|
306
|
+
| Constraint | Default | Reason |
|
|
307
|
+
|------------|---------|--------|
|
|
308
|
+
| Inbox limit | 5 messages | Prevents token burn |
|
|
309
|
+
| Bodies excluded | Always | Fetch individually when needed |
|
|
310
|
+
| Summarize preferred | Yes | Key points, not raw dump |
|
|
232
311
|
|
|
233
312
|
## Rate Limiting
|
|
234
313
|
|
|
235
314
|
Client-side limits (Redis primary, SQLite fallback):
|
|
236
315
|
|
|
237
316
|
| Endpoint | Per Minute | Per Hour |
|
|
238
|
-
|
|
239
|
-
| send
|
|
240
|
-
| reserve
|
|
241
|
-
| inbox
|
|
317
|
+
|----------|------------|----------|
|
|
318
|
+
| send | 20 | 200 |
|
|
319
|
+
| reserve | 10 | 100 |
|
|
320
|
+
| inbox | 60 | 600 |
|
|
242
321
|
|
|
243
322
|
Configure via `OPENCODE_RATE_LIMIT_{ENDPOINT}_PER_MIN` env vars.
|
|
244
323
|
|
|
@@ -249,7 +328,7 @@ bun install
|
|
|
249
328
|
bun run typecheck
|
|
250
329
|
bun test
|
|
251
330
|
bun run build
|
|
252
|
-
|
|
331
|
+
````
|
|
253
332
|
|
|
254
333
|
## License
|
|
255
334
|
|
package/bin/swarm.ts
CHANGED
|
@@ -17,7 +17,34 @@ import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
|
17
17
|
import { homedir } from "os";
|
|
18
18
|
import { join } from "path";
|
|
19
19
|
|
|
20
|
-
const VERSION = "0.
|
|
20
|
+
const VERSION = "0.10.0";
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// ASCII Art & Branding
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
const BEE = `
|
|
27
|
+
\\ \` - ' /
|
|
28
|
+
- .(o o). -
|
|
29
|
+
( >.< )
|
|
30
|
+
/| |\\
|
|
31
|
+
(_| |_) bzzzz...
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const BANNER = `
|
|
35
|
+
███████╗██╗ ██╗ █████╗ ██████╗ ███╗ ███╗
|
|
36
|
+
██╔════╝██║ ██║██╔══██╗██╔══██╗████╗ ████║
|
|
37
|
+
███████╗██║ █╗ ██║███████║██████╔╝██╔████╔██║
|
|
38
|
+
╚════██║██║███╗██║██╔══██║██╔══██╗██║╚██╔╝██║
|
|
39
|
+
███████║╚███╔███╔╝██║ ██║██║ ██║██║ ╚═╝ ██║
|
|
40
|
+
╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const TAGLINE = "Multi-agent coordination for OpenCode";
|
|
44
|
+
|
|
45
|
+
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
46
|
+
const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
|
|
47
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
21
48
|
|
|
22
49
|
// ============================================================================
|
|
23
50
|
// Types
|
|
@@ -560,27 +587,70 @@ async function init() {
|
|
|
560
587
|
}
|
|
561
588
|
|
|
562
589
|
function version() {
|
|
563
|
-
console.log(
|
|
590
|
+
console.log(yellow(BANNER));
|
|
591
|
+
console.log(dim(" " + TAGLINE));
|
|
592
|
+
console.log();
|
|
593
|
+
console.log(" Version: " + VERSION);
|
|
594
|
+
console.log(" Docs: https://github.com/joelhooks/opencode-swarm-plugin");
|
|
595
|
+
console.log();
|
|
564
596
|
}
|
|
565
597
|
|
|
566
|
-
function
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
598
|
+
function config() {
|
|
599
|
+
const configDir = join(homedir(), ".config", "opencode");
|
|
600
|
+
const pluginPath = join(configDir, "plugins", "swarm.ts");
|
|
601
|
+
const commandPath = join(configDir, "commands", "swarm.md");
|
|
602
|
+
const agentPath = join(configDir, "agents", "swarm-planner.md");
|
|
603
|
+
|
|
604
|
+
console.log(yellow(BANNER));
|
|
605
|
+
console.log(dim(" " + TAGLINE + " v" + VERSION));
|
|
606
|
+
console.log();
|
|
607
|
+
console.log(cyan("Config Files:"));
|
|
608
|
+
console.log();
|
|
609
|
+
|
|
610
|
+
const files = [
|
|
611
|
+
{ path: pluginPath, desc: "Plugin loader", emoji: "🔌" },
|
|
612
|
+
{ path: commandPath, desc: "/swarm command prompt", emoji: "📜" },
|
|
613
|
+
{ path: agentPath, desc: "@swarm-planner agent", emoji: "🤖" },
|
|
614
|
+
];
|
|
615
|
+
|
|
616
|
+
for (const { path, desc, emoji } of files) {
|
|
617
|
+
const exists = existsSync(path);
|
|
618
|
+
const status = exists ? "✓" : "✗";
|
|
619
|
+
const color = exists ? "\x1b[32m" : "\x1b[31m";
|
|
620
|
+
console.log(` ${emoji} ${desc}`);
|
|
621
|
+
console.log(` ${color}${status}\x1b[0m ${dim(path)}`);
|
|
622
|
+
console.log();
|
|
623
|
+
}
|
|
571
624
|
|
|
572
|
-
|
|
573
|
-
swarm setup
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
swarm version Show version
|
|
577
|
-
swarm help Show this help
|
|
625
|
+
console.log(dim("Edit these files to customize swarm behavior."));
|
|
626
|
+
console.log(dim("Run 'swarm setup' to regenerate defaults."));
|
|
627
|
+
console.log();
|
|
628
|
+
}
|
|
578
629
|
|
|
579
|
-
|
|
630
|
+
function help() {
|
|
631
|
+
console.log(yellow(BANNER));
|
|
632
|
+
console.log(dim(" " + TAGLINE + " v" + VERSION));
|
|
633
|
+
console.log(cyan(BEE));
|
|
634
|
+
console.log(`
|
|
635
|
+
${cyan("Commands:")}
|
|
636
|
+
swarm setup Interactive installer - checks and installs dependencies
|
|
637
|
+
swarm doctor Health check - shows status of all dependencies
|
|
638
|
+
swarm init Initialize beads in current project
|
|
639
|
+
swarm config Show paths to generated config files
|
|
640
|
+
swarm version Show version and banner
|
|
641
|
+
swarm help Show this help
|
|
642
|
+
|
|
643
|
+
${cyan("Usage in OpenCode:")}
|
|
580
644
|
/swarm "Add user authentication with OAuth"
|
|
581
|
-
@swarm-planner "Refactor components to use hooks"
|
|
645
|
+
@swarm-planner "Refactor all components to use hooks"
|
|
582
646
|
|
|
583
|
-
|
|
647
|
+
${cyan("Customization:")}
|
|
648
|
+
Edit the generated files to customize behavior:
|
|
649
|
+
${dim("~/.config/opencode/commands/swarm.md")} - /swarm command prompt
|
|
650
|
+
${dim("~/.config/opencode/agents/swarm-planner.md")} - @swarm-planner agent
|
|
651
|
+
${dim("~/.config/opencode/plugins/swarm.ts")} - Plugin loader
|
|
652
|
+
|
|
653
|
+
${dim("Docs: https://github.com/joelhooks/opencode-swarm-plugin")}
|
|
584
654
|
`);
|
|
585
655
|
}
|
|
586
656
|
|
|
@@ -600,6 +670,9 @@ switch (command) {
|
|
|
600
670
|
case "init":
|
|
601
671
|
await init();
|
|
602
672
|
break;
|
|
673
|
+
case "config":
|
|
674
|
+
config();
|
|
675
|
+
break;
|
|
603
676
|
case "version":
|
|
604
677
|
case "--version":
|
|
605
678
|
case "-v":
|
package/dist/index.js
CHANGED
|
@@ -22593,6 +22593,18 @@ async function getRateLimiter() {
|
|
|
22593
22593
|
var AGENT_MAIL_URL = "http://127.0.0.1:8765";
|
|
22594
22594
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
22595
22595
|
var MAX_INBOX_LIMIT = 5;
|
|
22596
|
+
var RETRY_CONFIG = {
|
|
22597
|
+
maxRetries: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_RETRIES || "3"),
|
|
22598
|
+
baseDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_BASE_DELAY_MS || "100"),
|
|
22599
|
+
maxDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_DELAY_MS || "5000"),
|
|
22600
|
+
timeoutMs: parseInt(process.env.OPENCODE_AGENT_MAIL_TIMEOUT_MS || "10000"),
|
|
22601
|
+
jitterPercent: 20
|
|
22602
|
+
};
|
|
22603
|
+
var RECOVERY_CONFIG = {
|
|
22604
|
+
failureThreshold: 2,
|
|
22605
|
+
restartCooldownMs: 30000,
|
|
22606
|
+
enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
|
|
22607
|
+
};
|
|
22596
22608
|
var sessionStates = new Map;
|
|
22597
22609
|
|
|
22598
22610
|
class AgentMailError extends Error {
|
|
@@ -22639,6 +22651,151 @@ class RateLimitExceededError extends Error {
|
|
|
22639
22651
|
this.name = "RateLimitExceededError";
|
|
22640
22652
|
}
|
|
22641
22653
|
}
|
|
22654
|
+
var consecutiveFailures = 0;
|
|
22655
|
+
var lastRestartAttempt = 0;
|
|
22656
|
+
var isRestarting = false;
|
|
22657
|
+
async function isServerHealthy() {
|
|
22658
|
+
try {
|
|
22659
|
+
const controller = new AbortController;
|
|
22660
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
22661
|
+
const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`, {
|
|
22662
|
+
signal: controller.signal
|
|
22663
|
+
});
|
|
22664
|
+
clearTimeout(timeout);
|
|
22665
|
+
return response.ok;
|
|
22666
|
+
} catch {
|
|
22667
|
+
return false;
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
async function isServerFunctional() {
|
|
22671
|
+
try {
|
|
22672
|
+
const controller = new AbortController;
|
|
22673
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
22674
|
+
const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
|
|
22675
|
+
method: "POST",
|
|
22676
|
+
headers: { "Content-Type": "application/json" },
|
|
22677
|
+
body: JSON.stringify({
|
|
22678
|
+
jsonrpc: "2.0",
|
|
22679
|
+
id: "health-test",
|
|
22680
|
+
method: "tools/call",
|
|
22681
|
+
params: { name: "health_check", arguments: {} }
|
|
22682
|
+
}),
|
|
22683
|
+
signal: controller.signal
|
|
22684
|
+
});
|
|
22685
|
+
clearTimeout(timeout);
|
|
22686
|
+
if (!response.ok)
|
|
22687
|
+
return false;
|
|
22688
|
+
const json2 = await response.json();
|
|
22689
|
+
if (json2.result?.isError)
|
|
22690
|
+
return false;
|
|
22691
|
+
return true;
|
|
22692
|
+
} catch {
|
|
22693
|
+
return false;
|
|
22694
|
+
}
|
|
22695
|
+
}
|
|
22696
|
+
async function restartServer() {
|
|
22697
|
+
if (!RECOVERY_CONFIG.enabled) {
|
|
22698
|
+
console.warn("[agent-mail] Auto-restart disabled via OPENCODE_AGENT_MAIL_AUTO_RESTART=false");
|
|
22699
|
+
return false;
|
|
22700
|
+
}
|
|
22701
|
+
if (isRestarting) {
|
|
22702
|
+
console.warn("[agent-mail] Restart already in progress");
|
|
22703
|
+
return false;
|
|
22704
|
+
}
|
|
22705
|
+
const now = Date.now();
|
|
22706
|
+
if (now - lastRestartAttempt < RECOVERY_CONFIG.restartCooldownMs) {
|
|
22707
|
+
const waitSec = Math.ceil((RECOVERY_CONFIG.restartCooldownMs - (now - lastRestartAttempt)) / 1000);
|
|
22708
|
+
console.warn(`[agent-mail] Restart cooldown active, wait ${waitSec}s`);
|
|
22709
|
+
return false;
|
|
22710
|
+
}
|
|
22711
|
+
isRestarting = true;
|
|
22712
|
+
lastRestartAttempt = now;
|
|
22713
|
+
try {
|
|
22714
|
+
console.warn("[agent-mail] Attempting server restart...");
|
|
22715
|
+
const findProc = Bun.spawn(["lsof", "-i", ":8765", "-t"], {
|
|
22716
|
+
stdout: "pipe",
|
|
22717
|
+
stderr: "pipe"
|
|
22718
|
+
});
|
|
22719
|
+
const findOutput = await new Response(findProc.stdout).text();
|
|
22720
|
+
await findProc.exited;
|
|
22721
|
+
const pids = findOutput.trim().split(`
|
|
22722
|
+
`).filter(Boolean);
|
|
22723
|
+
if (pids.length > 0) {
|
|
22724
|
+
for (const pid of pids) {
|
|
22725
|
+
console.warn(`[agent-mail] Killing process ${pid}`);
|
|
22726
|
+
Bun.spawn(["kill", pid]);
|
|
22727
|
+
}
|
|
22728
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
22729
|
+
}
|
|
22730
|
+
const possiblePaths = [
|
|
22731
|
+
`${process.env.HOME}/Code/Dicklesworthstone/mcp_agent_mail`,
|
|
22732
|
+
`${process.env.HOME}/.local/share/agent-mail`,
|
|
22733
|
+
`${process.env.HOME}/mcp_agent_mail`
|
|
22734
|
+
];
|
|
22735
|
+
let serverDir = null;
|
|
22736
|
+
for (const path of possiblePaths) {
|
|
22737
|
+
try {
|
|
22738
|
+
const stat = await Bun.file(`${path}/pyproject.toml`).exists();
|
|
22739
|
+
if (stat) {
|
|
22740
|
+
serverDir = path;
|
|
22741
|
+
break;
|
|
22742
|
+
}
|
|
22743
|
+
} catch {
|
|
22744
|
+
continue;
|
|
22745
|
+
}
|
|
22746
|
+
}
|
|
22747
|
+
if (!serverDir) {
|
|
22748
|
+
console.error("[agent-mail] Could not find agent-mail installation directory");
|
|
22749
|
+
return false;
|
|
22750
|
+
}
|
|
22751
|
+
console.warn(`[agent-mail] Starting server from ${serverDir}`);
|
|
22752
|
+
Bun.spawn(["python", "-m", "mcp_agent_mail.cli", "serve-http"], {
|
|
22753
|
+
cwd: serverDir,
|
|
22754
|
+
stdout: "ignore",
|
|
22755
|
+
stderr: "ignore",
|
|
22756
|
+
detached: true
|
|
22757
|
+
});
|
|
22758
|
+
for (let i = 0;i < 10; i++) {
|
|
22759
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
22760
|
+
if (await isServerHealthy()) {
|
|
22761
|
+
console.warn("[agent-mail] Server restarted successfully");
|
|
22762
|
+
consecutiveFailures = 0;
|
|
22763
|
+
return true;
|
|
22764
|
+
}
|
|
22765
|
+
}
|
|
22766
|
+
console.error("[agent-mail] Server failed to start after restart");
|
|
22767
|
+
return false;
|
|
22768
|
+
} catch (error45) {
|
|
22769
|
+
console.error("[agent-mail] Restart failed:", error45);
|
|
22770
|
+
return false;
|
|
22771
|
+
} finally {
|
|
22772
|
+
isRestarting = false;
|
|
22773
|
+
}
|
|
22774
|
+
}
|
|
22775
|
+
function calculateBackoffDelay(attempt) {
|
|
22776
|
+
if (attempt === 0)
|
|
22777
|
+
return 0;
|
|
22778
|
+
const exponentialDelay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1);
|
|
22779
|
+
const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);
|
|
22780
|
+
const jitterRange = cappedDelay * (RETRY_CONFIG.jitterPercent / 100);
|
|
22781
|
+
const jitter = (Math.random() * 2 - 1) * jitterRange;
|
|
22782
|
+
return Math.round(cappedDelay + jitter);
|
|
22783
|
+
}
|
|
22784
|
+
function isRetryableError(error45) {
|
|
22785
|
+
if (error45 instanceof Error) {
|
|
22786
|
+
const message = error45.message.toLowerCase();
|
|
22787
|
+
if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket") || message.includes("network") || message.includes("timeout") || message.includes("aborted")) {
|
|
22788
|
+
return true;
|
|
22789
|
+
}
|
|
22790
|
+
if (error45 instanceof AgentMailError && error45.code) {
|
|
22791
|
+
return error45.code === 502 || error45.code === 503 || error45.code === 504;
|
|
22792
|
+
}
|
|
22793
|
+
if (message.includes("unexpected error")) {
|
|
22794
|
+
return true;
|
|
22795
|
+
}
|
|
22796
|
+
}
|
|
22797
|
+
return false;
|
|
22798
|
+
}
|
|
22642
22799
|
var agentMailAvailable = null;
|
|
22643
22800
|
async function checkAgentMailAvailable() {
|
|
22644
22801
|
if (agentMailAvailable !== null) {
|
|
@@ -22670,36 +22827,85 @@ async function recordRateLimitedRequest(agentName, endpoint) {
|
|
|
22670
22827
|
}
|
|
22671
22828
|
await rateLimiter.recordRequest(agentName, endpoint);
|
|
22672
22829
|
}
|
|
22673
|
-
async function
|
|
22674
|
-
const
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22830
|
+
async function mcpCallOnce(toolName, args) {
|
|
22831
|
+
const controller = new AbortController;
|
|
22832
|
+
const timeout = setTimeout(() => controller.abort(), RETRY_CONFIG.timeoutMs);
|
|
22833
|
+
try {
|
|
22834
|
+
const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
|
|
22835
|
+
method: "POST",
|
|
22836
|
+
headers: { "Content-Type": "application/json" },
|
|
22837
|
+
body: JSON.stringify({
|
|
22838
|
+
jsonrpc: "2.0",
|
|
22839
|
+
id: crypto.randomUUID(),
|
|
22840
|
+
method: "tools/call",
|
|
22841
|
+
params: { name: toolName, arguments: args }
|
|
22842
|
+
}),
|
|
22843
|
+
signal: controller.signal
|
|
22844
|
+
});
|
|
22845
|
+
clearTimeout(timeout);
|
|
22846
|
+
if (!response.ok) {
|
|
22847
|
+
throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName, response.status);
|
|
22848
|
+
}
|
|
22849
|
+
const json2 = await response.json();
|
|
22850
|
+
if (json2.error) {
|
|
22851
|
+
throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
|
|
22852
|
+
}
|
|
22853
|
+
const result = json2.result;
|
|
22854
|
+
if (result && typeof result === "object") {
|
|
22855
|
+
const wrapped = result;
|
|
22856
|
+
if (wrapped.isError) {
|
|
22857
|
+
const errorText = wrapped.content?.[0]?.text || "Unknown error";
|
|
22858
|
+
throw new AgentMailError(errorText, toolName);
|
|
22859
|
+
}
|
|
22860
|
+
if ("structuredContent" in wrapped) {
|
|
22861
|
+
return wrapped.structuredContent;
|
|
22862
|
+
}
|
|
22863
|
+
}
|
|
22864
|
+
return result;
|
|
22865
|
+
} catch (error45) {
|
|
22866
|
+
clearTimeout(timeout);
|
|
22867
|
+
throw error45;
|
|
22690
22868
|
}
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22869
|
+
}
|
|
22870
|
+
async function mcpCall(toolName, args) {
|
|
22871
|
+
let lastError = null;
|
|
22872
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
22873
|
+
if (attempt > 0) {
|
|
22874
|
+
const delay = calculateBackoffDelay(attempt);
|
|
22875
|
+
console.warn(`[agent-mail] Retry ${attempt}/${RETRY_CONFIG.maxRetries} for ${toolName} after ${delay}ms`);
|
|
22876
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
22697
22877
|
}
|
|
22698
|
-
|
|
22699
|
-
|
|
22878
|
+
try {
|
|
22879
|
+
const result = await mcpCallOnce(toolName, args);
|
|
22880
|
+
consecutiveFailures = 0;
|
|
22881
|
+
return result;
|
|
22882
|
+
} catch (error45) {
|
|
22883
|
+
lastError = error45 instanceof Error ? error45 : new Error(String(error45));
|
|
22884
|
+
consecutiveFailures++;
|
|
22885
|
+
if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
|
|
22886
|
+
console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
|
|
22887
|
+
const healthy = await isServerFunctional();
|
|
22888
|
+
if (!healthy) {
|
|
22889
|
+
console.warn("[agent-mail] Server unhealthy, attempting restart...");
|
|
22890
|
+
const restarted = await restartServer();
|
|
22891
|
+
if (restarted) {
|
|
22892
|
+
agentMailAvailable = null;
|
|
22893
|
+
attempt--;
|
|
22894
|
+
continue;
|
|
22895
|
+
}
|
|
22896
|
+
}
|
|
22897
|
+
}
|
|
22898
|
+
if (!isRetryableError(error45)) {
|
|
22899
|
+
console.warn(`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`);
|
|
22900
|
+
throw lastError;
|
|
22901
|
+
}
|
|
22902
|
+
if (attempt === RETRY_CONFIG.maxRetries) {
|
|
22903
|
+
console.error(`[agent-mail] All ${RETRY_CONFIG.maxRetries} retries exhausted for ${toolName}`);
|
|
22904
|
+
throw lastError;
|
|
22905
|
+
}
|
|
22700
22906
|
}
|
|
22701
22907
|
}
|
|
22702
|
-
|
|
22908
|
+
throw lastError || new Error("Unknown error in mcpCall");
|
|
22703
22909
|
}
|
|
22704
22910
|
function requireState(sessionID) {
|
|
22705
22911
|
const state = sessionStates.get(sessionID);
|
package/dist/plugin.js
CHANGED
|
@@ -22567,6 +22567,18 @@ async function getRateLimiter() {
|
|
|
22567
22567
|
var AGENT_MAIL_URL = "http://127.0.0.1:8765";
|
|
22568
22568
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
22569
22569
|
var MAX_INBOX_LIMIT = 5;
|
|
22570
|
+
var RETRY_CONFIG = {
|
|
22571
|
+
maxRetries: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_RETRIES || "3"),
|
|
22572
|
+
baseDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_BASE_DELAY_MS || "100"),
|
|
22573
|
+
maxDelayMs: parseInt(process.env.OPENCODE_AGENT_MAIL_MAX_DELAY_MS || "5000"),
|
|
22574
|
+
timeoutMs: parseInt(process.env.OPENCODE_AGENT_MAIL_TIMEOUT_MS || "10000"),
|
|
22575
|
+
jitterPercent: 20
|
|
22576
|
+
};
|
|
22577
|
+
var RECOVERY_CONFIG = {
|
|
22578
|
+
failureThreshold: 2,
|
|
22579
|
+
restartCooldownMs: 30000,
|
|
22580
|
+
enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false"
|
|
22581
|
+
};
|
|
22570
22582
|
var sessionStates = new Map;
|
|
22571
22583
|
|
|
22572
22584
|
class AgentMailError extends Error {
|
|
@@ -22613,6 +22625,151 @@ class RateLimitExceededError extends Error {
|
|
|
22613
22625
|
this.name = "RateLimitExceededError";
|
|
22614
22626
|
}
|
|
22615
22627
|
}
|
|
22628
|
+
var consecutiveFailures = 0;
|
|
22629
|
+
var lastRestartAttempt = 0;
|
|
22630
|
+
var isRestarting = false;
|
|
22631
|
+
async function isServerHealthy() {
|
|
22632
|
+
try {
|
|
22633
|
+
const controller = new AbortController;
|
|
22634
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
22635
|
+
const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`, {
|
|
22636
|
+
signal: controller.signal
|
|
22637
|
+
});
|
|
22638
|
+
clearTimeout(timeout);
|
|
22639
|
+
return response.ok;
|
|
22640
|
+
} catch {
|
|
22641
|
+
return false;
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
22644
|
+
async function isServerFunctional() {
|
|
22645
|
+
try {
|
|
22646
|
+
const controller = new AbortController;
|
|
22647
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
22648
|
+
const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
|
|
22649
|
+
method: "POST",
|
|
22650
|
+
headers: { "Content-Type": "application/json" },
|
|
22651
|
+
body: JSON.stringify({
|
|
22652
|
+
jsonrpc: "2.0",
|
|
22653
|
+
id: "health-test",
|
|
22654
|
+
method: "tools/call",
|
|
22655
|
+
params: { name: "health_check", arguments: {} }
|
|
22656
|
+
}),
|
|
22657
|
+
signal: controller.signal
|
|
22658
|
+
});
|
|
22659
|
+
clearTimeout(timeout);
|
|
22660
|
+
if (!response.ok)
|
|
22661
|
+
return false;
|
|
22662
|
+
const json2 = await response.json();
|
|
22663
|
+
if (json2.result?.isError)
|
|
22664
|
+
return false;
|
|
22665
|
+
return true;
|
|
22666
|
+
} catch {
|
|
22667
|
+
return false;
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
async function restartServer() {
|
|
22671
|
+
if (!RECOVERY_CONFIG.enabled) {
|
|
22672
|
+
console.warn("[agent-mail] Auto-restart disabled via OPENCODE_AGENT_MAIL_AUTO_RESTART=false");
|
|
22673
|
+
return false;
|
|
22674
|
+
}
|
|
22675
|
+
if (isRestarting) {
|
|
22676
|
+
console.warn("[agent-mail] Restart already in progress");
|
|
22677
|
+
return false;
|
|
22678
|
+
}
|
|
22679
|
+
const now = Date.now();
|
|
22680
|
+
if (now - lastRestartAttempt < RECOVERY_CONFIG.restartCooldownMs) {
|
|
22681
|
+
const waitSec = Math.ceil((RECOVERY_CONFIG.restartCooldownMs - (now - lastRestartAttempt)) / 1000);
|
|
22682
|
+
console.warn(`[agent-mail] Restart cooldown active, wait ${waitSec}s`);
|
|
22683
|
+
return false;
|
|
22684
|
+
}
|
|
22685
|
+
isRestarting = true;
|
|
22686
|
+
lastRestartAttempt = now;
|
|
22687
|
+
try {
|
|
22688
|
+
console.warn("[agent-mail] Attempting server restart...");
|
|
22689
|
+
const findProc = Bun.spawn(["lsof", "-i", ":8765", "-t"], {
|
|
22690
|
+
stdout: "pipe",
|
|
22691
|
+
stderr: "pipe"
|
|
22692
|
+
});
|
|
22693
|
+
const findOutput = await new Response(findProc.stdout).text();
|
|
22694
|
+
await findProc.exited;
|
|
22695
|
+
const pids = findOutput.trim().split(`
|
|
22696
|
+
`).filter(Boolean);
|
|
22697
|
+
if (pids.length > 0) {
|
|
22698
|
+
for (const pid of pids) {
|
|
22699
|
+
console.warn(`[agent-mail] Killing process ${pid}`);
|
|
22700
|
+
Bun.spawn(["kill", pid]);
|
|
22701
|
+
}
|
|
22702
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
22703
|
+
}
|
|
22704
|
+
const possiblePaths = [
|
|
22705
|
+
`${process.env.HOME}/Code/Dicklesworthstone/mcp_agent_mail`,
|
|
22706
|
+
`${process.env.HOME}/.local/share/agent-mail`,
|
|
22707
|
+
`${process.env.HOME}/mcp_agent_mail`
|
|
22708
|
+
];
|
|
22709
|
+
let serverDir = null;
|
|
22710
|
+
for (const path of possiblePaths) {
|
|
22711
|
+
try {
|
|
22712
|
+
const stat = await Bun.file(`${path}/pyproject.toml`).exists();
|
|
22713
|
+
if (stat) {
|
|
22714
|
+
serverDir = path;
|
|
22715
|
+
break;
|
|
22716
|
+
}
|
|
22717
|
+
} catch {
|
|
22718
|
+
continue;
|
|
22719
|
+
}
|
|
22720
|
+
}
|
|
22721
|
+
if (!serverDir) {
|
|
22722
|
+
console.error("[agent-mail] Could not find agent-mail installation directory");
|
|
22723
|
+
return false;
|
|
22724
|
+
}
|
|
22725
|
+
console.warn(`[agent-mail] Starting server from ${serverDir}`);
|
|
22726
|
+
Bun.spawn(["python", "-m", "mcp_agent_mail.cli", "serve-http"], {
|
|
22727
|
+
cwd: serverDir,
|
|
22728
|
+
stdout: "ignore",
|
|
22729
|
+
stderr: "ignore",
|
|
22730
|
+
detached: true
|
|
22731
|
+
});
|
|
22732
|
+
for (let i = 0;i < 10; i++) {
|
|
22733
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
22734
|
+
if (await isServerHealthy()) {
|
|
22735
|
+
console.warn("[agent-mail] Server restarted successfully");
|
|
22736
|
+
consecutiveFailures = 0;
|
|
22737
|
+
return true;
|
|
22738
|
+
}
|
|
22739
|
+
}
|
|
22740
|
+
console.error("[agent-mail] Server failed to start after restart");
|
|
22741
|
+
return false;
|
|
22742
|
+
} catch (error45) {
|
|
22743
|
+
console.error("[agent-mail] Restart failed:", error45);
|
|
22744
|
+
return false;
|
|
22745
|
+
} finally {
|
|
22746
|
+
isRestarting = false;
|
|
22747
|
+
}
|
|
22748
|
+
}
|
|
22749
|
+
function calculateBackoffDelay(attempt) {
|
|
22750
|
+
if (attempt === 0)
|
|
22751
|
+
return 0;
|
|
22752
|
+
const exponentialDelay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1);
|
|
22753
|
+
const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);
|
|
22754
|
+
const jitterRange = cappedDelay * (RETRY_CONFIG.jitterPercent / 100);
|
|
22755
|
+
const jitter = (Math.random() * 2 - 1) * jitterRange;
|
|
22756
|
+
return Math.round(cappedDelay + jitter);
|
|
22757
|
+
}
|
|
22758
|
+
function isRetryableError(error45) {
|
|
22759
|
+
if (error45 instanceof Error) {
|
|
22760
|
+
const message = error45.message.toLowerCase();
|
|
22761
|
+
if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket") || message.includes("network") || message.includes("timeout") || message.includes("aborted")) {
|
|
22762
|
+
return true;
|
|
22763
|
+
}
|
|
22764
|
+
if (error45 instanceof AgentMailError && error45.code) {
|
|
22765
|
+
return error45.code === 502 || error45.code === 503 || error45.code === 504;
|
|
22766
|
+
}
|
|
22767
|
+
if (message.includes("unexpected error")) {
|
|
22768
|
+
return true;
|
|
22769
|
+
}
|
|
22770
|
+
}
|
|
22771
|
+
return false;
|
|
22772
|
+
}
|
|
22616
22773
|
var agentMailAvailable = null;
|
|
22617
22774
|
async function checkAgentMailAvailable() {
|
|
22618
22775
|
if (agentMailAvailable !== null) {
|
|
@@ -22644,36 +22801,85 @@ async function recordRateLimitedRequest(agentName, endpoint) {
|
|
|
22644
22801
|
}
|
|
22645
22802
|
await rateLimiter.recordRequest(agentName, endpoint);
|
|
22646
22803
|
}
|
|
22647
|
-
async function
|
|
22648
|
-
const
|
|
22649
|
-
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22804
|
+
async function mcpCallOnce(toolName, args) {
|
|
22805
|
+
const controller = new AbortController;
|
|
22806
|
+
const timeout = setTimeout(() => controller.abort(), RETRY_CONFIG.timeoutMs);
|
|
22807
|
+
try {
|
|
22808
|
+
const response = await fetch(`${AGENT_MAIL_URL}/mcp/`, {
|
|
22809
|
+
method: "POST",
|
|
22810
|
+
headers: { "Content-Type": "application/json" },
|
|
22811
|
+
body: JSON.stringify({
|
|
22812
|
+
jsonrpc: "2.0",
|
|
22813
|
+
id: crypto.randomUUID(),
|
|
22814
|
+
method: "tools/call",
|
|
22815
|
+
params: { name: toolName, arguments: args }
|
|
22816
|
+
}),
|
|
22817
|
+
signal: controller.signal
|
|
22818
|
+
});
|
|
22819
|
+
clearTimeout(timeout);
|
|
22820
|
+
if (!response.ok) {
|
|
22821
|
+
throw new AgentMailError(`HTTP ${response.status}: ${response.statusText}`, toolName, response.status);
|
|
22822
|
+
}
|
|
22823
|
+
const json2 = await response.json();
|
|
22824
|
+
if (json2.error) {
|
|
22825
|
+
throw new AgentMailError(json2.error.message, toolName, json2.error.code, json2.error.data);
|
|
22826
|
+
}
|
|
22827
|
+
const result = json2.result;
|
|
22828
|
+
if (result && typeof result === "object") {
|
|
22829
|
+
const wrapped = result;
|
|
22830
|
+
if (wrapped.isError) {
|
|
22831
|
+
const errorText = wrapped.content?.[0]?.text || "Unknown error";
|
|
22832
|
+
throw new AgentMailError(errorText, toolName);
|
|
22833
|
+
}
|
|
22834
|
+
if ("structuredContent" in wrapped) {
|
|
22835
|
+
return wrapped.structuredContent;
|
|
22836
|
+
}
|
|
22837
|
+
}
|
|
22838
|
+
return result;
|
|
22839
|
+
} catch (error45) {
|
|
22840
|
+
clearTimeout(timeout);
|
|
22841
|
+
throw error45;
|
|
22664
22842
|
}
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22843
|
+
}
|
|
22844
|
+
async function mcpCall(toolName, args) {
|
|
22845
|
+
let lastError = null;
|
|
22846
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
22847
|
+
if (attempt > 0) {
|
|
22848
|
+
const delay = calculateBackoffDelay(attempt);
|
|
22849
|
+
console.warn(`[agent-mail] Retry ${attempt}/${RETRY_CONFIG.maxRetries} for ${toolName} after ${delay}ms`);
|
|
22850
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
22671
22851
|
}
|
|
22672
|
-
|
|
22673
|
-
|
|
22852
|
+
try {
|
|
22853
|
+
const result = await mcpCallOnce(toolName, args);
|
|
22854
|
+
consecutiveFailures = 0;
|
|
22855
|
+
return result;
|
|
22856
|
+
} catch (error45) {
|
|
22857
|
+
lastError = error45 instanceof Error ? error45 : new Error(String(error45));
|
|
22858
|
+
consecutiveFailures++;
|
|
22859
|
+
if (consecutiveFailures >= RECOVERY_CONFIG.failureThreshold && RECOVERY_CONFIG.enabled) {
|
|
22860
|
+
console.warn(`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`);
|
|
22861
|
+
const healthy = await isServerFunctional();
|
|
22862
|
+
if (!healthy) {
|
|
22863
|
+
console.warn("[agent-mail] Server unhealthy, attempting restart...");
|
|
22864
|
+
const restarted = await restartServer();
|
|
22865
|
+
if (restarted) {
|
|
22866
|
+
agentMailAvailable = null;
|
|
22867
|
+
attempt--;
|
|
22868
|
+
continue;
|
|
22869
|
+
}
|
|
22870
|
+
}
|
|
22871
|
+
}
|
|
22872
|
+
if (!isRetryableError(error45)) {
|
|
22873
|
+
console.warn(`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`);
|
|
22874
|
+
throw lastError;
|
|
22875
|
+
}
|
|
22876
|
+
if (attempt === RETRY_CONFIG.maxRetries) {
|
|
22877
|
+
console.error(`[agent-mail] All ${RETRY_CONFIG.maxRetries} retries exhausted for ${toolName}`);
|
|
22878
|
+
throw lastError;
|
|
22879
|
+
}
|
|
22674
22880
|
}
|
|
22675
22881
|
}
|
|
22676
|
-
|
|
22882
|
+
throw lastError || new Error("Unknown error in mcpCall");
|
|
22677
22883
|
}
|
|
22678
22884
|
function requireState(sessionID) {
|
|
22679
22885
|
const state = sessionStates.get(sessionID);
|
package/package.json
CHANGED