opencode-swarm-plugin 0.12.4 → 0.12.7
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/.beads/issues.jsonl +224 -0
- package/README.md +94 -106
- package/bin/swarm.ts +6 -7
- package/dist/index.js +409 -71
- package/dist/plugin.js +376 -58
- package/examples/commands/swarm.md +51 -216
- package/package.json +1 -1
- package/src/agent-mail.ts +183 -37
- package/src/beads.ts +75 -20
- package/src/learning.ts +277 -0
- package/src/rate-limiter.ts +12 -6
- package/src/schemas/bead.ts +4 -4
- package/src/schemas/evaluation.ts +2 -2
- package/src/schemas/task.ts +3 -3
- package/src/storage.ts +37 -15
- package/src/swarm.ts +189 -1
- package/examples/agents/swarm-planner.md +0 -138
|
@@ -2,282 +2,117 @@
|
|
|
2
2
|
description: Decompose task into parallel subtasks and coordinate agents
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
You are a swarm coordinator.
|
|
5
|
+
You are a swarm coordinator. Decompose the task into beads and spawn parallel agents.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Task
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
/swarm <task description or bead-id>
|
|
11
|
-
/swarm --to-main <task> # Skip PR, push directly to main (use sparingly)
|
|
12
|
-
/swarm --no-sync <task> # Skip mid-task context sync (for simple independent tasks)
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
**Default behavior: Feature branch + PR with context sync.** All swarm work goes to a feature branch, agents share context mid-task, and creates a PR for review.
|
|
16
|
-
|
|
17
|
-
## Step 1: Initialize Session
|
|
18
|
-
|
|
19
|
-
Use the plugin's agent-mail tools to register:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
agentmail_init with project_path=$PWD, task_description="Swarm coordinator: <task>"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
This returns your agent name and session state. Remember it.
|
|
26
|
-
|
|
27
|
-
## Step 2: Create Feature Branch
|
|
28
|
-
|
|
29
|
-
**CRITICAL: Never push directly to main.**
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
# Create branch from bead ID or task name
|
|
33
|
-
git checkout -b swarm/<bead-id> # e.g., swarm/trt-buddy-d7d
|
|
34
|
-
# Or for ad-hoc tasks:
|
|
35
|
-
git checkout -b swarm/<short-description> # e.g., swarm/contextual-checkins
|
|
36
|
-
|
|
37
|
-
git push -u origin HEAD
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Step 3: Understand the Task
|
|
41
|
-
|
|
42
|
-
If given a bead-id:
|
|
9
|
+
$ARGUMENTS
|
|
43
10
|
|
|
44
|
-
|
|
45
|
-
beads_query with id=<bead-id>
|
|
46
|
-
```
|
|
11
|
+
## Flags (parse from task above)
|
|
47
12
|
|
|
48
|
-
|
|
13
|
+
- `--to-main` - Push directly to main, skip PR
|
|
14
|
+
- `--no-sync` - Skip mid-task context sharing
|
|
49
15
|
|
|
50
|
-
|
|
16
|
+
**Default: Feature branch + PR with context sync.**
|
|
51
17
|
|
|
52
|
-
|
|
18
|
+
## Workflow
|
|
53
19
|
|
|
54
|
-
|
|
20
|
+
### 1. Initialize
|
|
55
21
|
|
|
56
22
|
```
|
|
57
|
-
|
|
58
|
-
subagent_type="general",
|
|
59
|
-
description="Plan swarm decomposition",
|
|
60
|
-
prompt="You are @swarm-planner. Decompose this task: <task description>. Use swarm_select_strategy and swarm_plan_prompt to guide your decomposition. Return ONLY valid BeadTree JSON."
|
|
61
|
-
)
|
|
23
|
+
agentmail_init(project_path="$PWD", task_description="Swarm: <task summary>")
|
|
62
24
|
```
|
|
63
25
|
|
|
64
|
-
###
|
|
65
|
-
|
|
66
|
-
1. **Select strategy**:
|
|
26
|
+
### 2. Create Feature Branch (unless --to-main)
|
|
67
27
|
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
2. **Get planning prompt**:
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
swarm_plan_prompt with task="<task description>", strategy="<selected or auto>"
|
|
28
|
+
```bash
|
|
29
|
+
git checkout -b swarm/<short-task-name>
|
|
30
|
+
git push -u origin HEAD
|
|
76
31
|
```
|
|
77
32
|
|
|
78
|
-
3.
|
|
33
|
+
### 3. Decompose Task
|
|
79
34
|
|
|
80
|
-
|
|
35
|
+
Use strategy selection and planning:
|
|
81
36
|
|
|
82
37
|
```
|
|
83
|
-
|
|
38
|
+
swarm_select_strategy(task="<the task>")
|
|
39
|
+
swarm_plan_prompt(task="<the task>", strategy="<auto or selected>")
|
|
84
40
|
```
|
|
85
41
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Once you have a valid BeadTree:
|
|
42
|
+
Follow the prompt to create a BeadTree, then validate:
|
|
89
43
|
|
|
90
44
|
```
|
|
91
|
-
|
|
45
|
+
swarm_validate_decomposition(response="<your BeadTree JSON>")
|
|
92
46
|
```
|
|
93
47
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- Each bead should be completable by one agent
|
|
97
|
-
- Beads should be independent (parallelizable) where possible
|
|
98
|
-
- If there are dependencies, order them in the subtasks array
|
|
99
|
-
- Aim for 3-7 beads per swarm (too few = not parallel, too many = coordination overhead)
|
|
100
|
-
|
|
101
|
-
## Step 5: Reserve Files
|
|
102
|
-
|
|
103
|
-
For each subtask, reserve the files it will touch:
|
|
48
|
+
### 4. Create Beads
|
|
104
49
|
|
|
105
50
|
```
|
|
106
|
-
|
|
51
|
+
beads_create_epic(epic_title="<task>", subtasks=[{title, files, priority}...])
|
|
107
52
|
```
|
|
108
53
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- No two agents should edit the same file
|
|
112
|
-
- If overlap exists, merge beads or sequence them
|
|
113
|
-
|
|
114
|
-
## Step 6: Spawn the Swarm
|
|
115
|
-
|
|
116
|
-
**CRITICAL: Spawn ALL agents in a SINGLE message with multiple Task calls.**
|
|
117
|
-
|
|
118
|
-
Use the prompt generator for each subtask:
|
|
54
|
+
Rules:
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
56
|
+
- Each bead completable by one agent
|
|
57
|
+
- Independent where possible (parallelizable)
|
|
58
|
+
- 3-7 beads per swarm
|
|
123
59
|
|
|
124
|
-
|
|
60
|
+
### 5. Reserve Files
|
|
125
61
|
|
|
126
62
|
```
|
|
127
|
-
|
|
128
|
-
subagent_type="general",
|
|
129
|
-
description="Swarm worker: <bead-title>",
|
|
130
|
-
prompt="<output from swarm_spawn_subtask>"
|
|
131
|
-
)
|
|
63
|
+
agentmail_reserve(paths=[<files>], reason="<bead-id>: <description>")
|
|
132
64
|
```
|
|
133
65
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
## Step 7: Monitor Progress (unless --no-sync)
|
|
66
|
+
No two agents should edit the same file.
|
|
137
67
|
|
|
138
|
-
|
|
68
|
+
### 6. Spawn Agents
|
|
139
69
|
|
|
140
|
-
|
|
141
|
-
swarm_status with epic_id="<parent-bead-id>"
|
|
142
|
-
```
|
|
70
|
+
**CRITICAL: Spawn ALL in a SINGLE message with multiple Task calls.**
|
|
143
71
|
|
|
144
|
-
|
|
72
|
+
For each subtask:
|
|
145
73
|
|
|
146
74
|
```
|
|
147
|
-
|
|
75
|
+
swarm_spawn_subtask(bead_id="<id>", epic_id="<epic>", subtask_title="<title>", files=[...])
|
|
148
76
|
```
|
|
149
77
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
1. **Review decisions made** - Are agents making compatible choices?
|
|
153
|
-
2. **Check for pattern conflicts** - Different approaches to the same problem?
|
|
154
|
-
3. **Identify shared concerns** - Common blockers or discoveries?
|
|
155
|
-
|
|
156
|
-
**If you spot incompatibilities, broadcast shared context:**
|
|
78
|
+
Then spawn:
|
|
157
79
|
|
|
158
80
|
```
|
|
159
|
-
|
|
81
|
+
Task(subagent_type="swarm-worker", description="<bead-title>", prompt="<from swarm_spawn_subtask>")
|
|
160
82
|
```
|
|
161
83
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
When agents complete, they send completion messages. Summarize the thread:
|
|
84
|
+
### 7. Monitor (unless --no-sync)
|
|
165
85
|
|
|
166
86
|
```
|
|
167
|
-
|
|
87
|
+
swarm_status(epic_id="<epic-id>")
|
|
88
|
+
agentmail_inbox()
|
|
168
89
|
```
|
|
169
90
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Use the swarm completion tool:
|
|
91
|
+
If incompatibilities spotted, broadcast:
|
|
173
92
|
|
|
174
93
|
```
|
|
175
|
-
|
|
94
|
+
agentmail_send(to=["*"], subject="Coordinator Update", body="<guidance>", importance="high")
|
|
176
95
|
```
|
|
177
96
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- Runs [UBS (Ultimate Bug Scanner)](https://github.com/Dicklesworthstone/ultimate_bug_scanner) on touched files to detect bugs before completion
|
|
181
|
-
- Releases file reservations
|
|
182
|
-
- Closes the bead
|
|
183
|
-
- Records outcome for learning
|
|
184
|
-
|
|
185
|
-
> **Note:** UBS is optional but recommended. If not installed, swarm completion proceeds with a warning that manual review is advised. Install via:
|
|
186
|
-
> ```bash
|
|
187
|
-
> curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh" | bash
|
|
188
|
-
> ```
|
|
189
|
-
> See the [UBS repo](https://github.com/Dicklesworthstone/ultimate_bug_scanner) for more options (Docker, Nix, etc.).
|
|
190
|
-
|
|
191
|
-
Then sync beads:
|
|
97
|
+
### 8. Complete
|
|
192
98
|
|
|
193
99
|
```
|
|
194
|
-
|
|
100
|
+
swarm_complete(project_key="$PWD", agent_name="<your-name>", bead_id="<epic-id>", summary="<done>", files_touched=[...])
|
|
101
|
+
beads_sync()
|
|
195
102
|
```
|
|
196
103
|
|
|
197
|
-
|
|
104
|
+
### 9. Create PR (unless --to-main)
|
|
198
105
|
|
|
199
106
|
```bash
|
|
200
|
-
gh pr create --title "feat: <epic title>" --body "
|
|
201
|
-
## Summary
|
|
202
|
-
<1-3 bullet points from swarm results>
|
|
203
|
-
|
|
204
|
-
## Beads Completed
|
|
205
|
-
- <bead-id>: <summary>
|
|
206
|
-
- <bead-id>: <summary>
|
|
207
|
-
|
|
208
|
-
## Files Changed
|
|
209
|
-
<aggregate list>
|
|
210
|
-
|
|
211
|
-
## Testing
|
|
212
|
-
- [ ] Type check passes
|
|
213
|
-
- [ ] Tests pass (if applicable)
|
|
214
|
-
EOF
|
|
215
|
-
)"
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Report summary:
|
|
219
|
-
|
|
220
|
-
```markdown
|
|
221
|
-
## Swarm Complete: <task>
|
|
222
|
-
|
|
223
|
-
### PR: #<number>
|
|
224
|
-
|
|
225
|
-
### Agents Spawned: N
|
|
226
|
-
|
|
227
|
-
### Beads Closed: N
|
|
228
|
-
|
|
229
|
-
### Work Completed
|
|
230
|
-
|
|
231
|
-
- [bead-id]: [summary]
|
|
232
|
-
|
|
233
|
-
### Files Changed
|
|
234
|
-
|
|
235
|
-
- [aggregate list]
|
|
107
|
+
gh pr create --title "feat: <epic title>" --body "## Summary\n<bullets>\n\n## Beads\n<list>"
|
|
236
108
|
```
|
|
237
109
|
|
|
238
|
-
## Failure Handling
|
|
239
|
-
|
|
240
|
-
If an agent fails:
|
|
241
|
-
|
|
242
|
-
- Check its messages: `agentmail_inbox`
|
|
243
|
-
- The bead remains in-progress
|
|
244
|
-
- Manually investigate or re-spawn
|
|
245
|
-
|
|
246
|
-
If file conflicts occur:
|
|
247
|
-
|
|
248
|
-
- Agent Mail reservations should prevent this
|
|
249
|
-
- If it happens, one agent needs to wait
|
|
250
|
-
|
|
251
|
-
## Direct-to-Main Mode (--to-main)
|
|
252
|
-
|
|
253
|
-
Only use when explicitly requested. Skips branch/PR:
|
|
254
|
-
|
|
255
|
-
- Trivial fixes across many files
|
|
256
|
-
- Automated migrations with high confidence
|
|
257
|
-
- User explicitly says "push to main"
|
|
258
|
-
|
|
259
|
-
## No-Sync Mode (--no-sync)
|
|
260
|
-
|
|
261
|
-
Skip mid-task context sharing when tasks are truly independent:
|
|
262
|
-
|
|
263
|
-
- Simple mechanical changes (find/replace, formatting, lint fixes)
|
|
264
|
-
- Tasks with zero integration points
|
|
265
|
-
- Completely separate feature areas with no shared types
|
|
266
|
-
|
|
267
|
-
In this mode:
|
|
268
|
-
|
|
269
|
-
- Agents skip the mid-task progress message
|
|
270
|
-
- Coordinator skips Step 7 (monitoring)
|
|
271
|
-
- Faster execution, less coordination overhead
|
|
272
|
-
|
|
273
|
-
**Default is sync ON** - prefer sharing context. Use `--no-sync` deliberately.
|
|
274
|
-
|
|
275
110
|
## Strategy Reference
|
|
276
111
|
|
|
277
|
-
| Strategy
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
|
281
|
-
|
|
|
112
|
+
| Strategy | Best For | Keywords |
|
|
113
|
+
| ------------- | ----------------------- | ------------------------------------- |
|
|
114
|
+
| file-based | Refactoring, migrations | refactor, migrate, rename, update all |
|
|
115
|
+
| feature-based | New features | add, implement, build, create, new |
|
|
116
|
+
| risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
|
|
282
117
|
|
|
283
|
-
|
|
118
|
+
Begin decomposition now.
|
package/package.json
CHANGED
package/src/agent-mail.ts
CHANGED
|
@@ -38,10 +38,10 @@ const RETRY_CONFIG = {
|
|
|
38
38
|
|
|
39
39
|
// Server recovery configuration
|
|
40
40
|
const RECOVERY_CONFIG = {
|
|
41
|
-
/** Max consecutive failures before attempting restart */
|
|
42
|
-
failureThreshold:
|
|
43
|
-
/** Cooldown between restart attempts (ms) */
|
|
44
|
-
restartCooldownMs:
|
|
41
|
+
/** Max consecutive failures before attempting restart (1 = restart on first "unexpected error") */
|
|
42
|
+
failureThreshold: 1,
|
|
43
|
+
/** Cooldown between restart attempts (ms) - 10 seconds */
|
|
44
|
+
restartCooldownMs: 10000,
|
|
45
45
|
/** Whether auto-restart is enabled */
|
|
46
46
|
enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false",
|
|
47
47
|
};
|
|
@@ -107,8 +107,10 @@ function loadSessionState(sessionID: string): AgentMailState | null {
|
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* Save session state to disk
|
|
110
|
+
*
|
|
111
|
+
* @returns true if save succeeded, false if failed
|
|
110
112
|
*/
|
|
111
|
-
function saveSessionState(sessionID: string, state: AgentMailState):
|
|
113
|
+
function saveSessionState(sessionID: string, state: AgentMailState): boolean {
|
|
112
114
|
try {
|
|
113
115
|
// Ensure directory exists
|
|
114
116
|
if (!existsSync(SESSION_STATE_DIR)) {
|
|
@@ -116,9 +118,16 @@ function saveSessionState(sessionID: string, state: AgentMailState): void {
|
|
|
116
118
|
}
|
|
117
119
|
const path = getSessionStatePath(sessionID);
|
|
118
120
|
writeFileSync(path, JSON.stringify(state, null, 2));
|
|
121
|
+
return true;
|
|
119
122
|
} catch (error) {
|
|
120
123
|
// Non-fatal - state just won't persist
|
|
121
|
-
console.
|
|
124
|
+
console.error(
|
|
125
|
+
`[agent-mail] CRITICAL: Could not save session state: ${error}`,
|
|
126
|
+
);
|
|
127
|
+
console.error(
|
|
128
|
+
`[agent-mail] Session state will not persist across CLI invocations!`,
|
|
129
|
+
);
|
|
130
|
+
return false;
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
@@ -718,6 +727,9 @@ export async function mcpCall<T>(
|
|
|
718
727
|
// Track consecutive failures
|
|
719
728
|
consecutiveFailures++;
|
|
720
729
|
|
|
730
|
+
// Check if error is retryable FIRST
|
|
731
|
+
const retryable = isRetryableError(error);
|
|
732
|
+
|
|
721
733
|
// Check if we should attempt server restart
|
|
722
734
|
if (
|
|
723
735
|
consecutiveFailures >= RECOVERY_CONFIG.failureThreshold &&
|
|
@@ -734,15 +746,18 @@ export async function mcpCall<T>(
|
|
|
734
746
|
if (restarted) {
|
|
735
747
|
// Reset availability cache since server restarted
|
|
736
748
|
agentMailAvailable = null;
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
749
|
+
// Only retry if the error was retryable in the first place
|
|
750
|
+
if (retryable) {
|
|
751
|
+
// Don't count this attempt against retries - try again
|
|
752
|
+
attempt--;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
740
755
|
}
|
|
741
756
|
}
|
|
742
757
|
}
|
|
743
758
|
|
|
744
|
-
//
|
|
745
|
-
if (!
|
|
759
|
+
// If error is not retryable, throw immediately
|
|
760
|
+
if (!retryable) {
|
|
746
761
|
console.warn(
|
|
747
762
|
`[agent-mail] Non-retryable error for ${toolName}: ${lastError.message}`,
|
|
748
763
|
);
|
|
@@ -865,30 +880,88 @@ export const agentmail_init = tool({
|
|
|
865
880
|
);
|
|
866
881
|
}
|
|
867
882
|
|
|
868
|
-
//
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
});
|
|
883
|
+
// Retry loop with restart on failure
|
|
884
|
+
const MAX_INIT_RETRIES = 3;
|
|
885
|
+
let lastError: Error | null = null;
|
|
872
886
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
887
|
+
for (let attempt = 1; attempt <= MAX_INIT_RETRIES; attempt++) {
|
|
888
|
+
try {
|
|
889
|
+
// 1. Ensure project exists
|
|
890
|
+
const project = await mcpCall<ProjectInfo>("ensure_project", {
|
|
891
|
+
human_key: args.project_path,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// 2. Register agent
|
|
895
|
+
const agent = await mcpCall<AgentInfo>("register_agent", {
|
|
896
|
+
project_key: args.project_path,
|
|
897
|
+
program: "opencode",
|
|
898
|
+
model: "claude-opus-4",
|
|
899
|
+
name: args.agent_name, // undefined = auto-generate
|
|
900
|
+
task_description: args.task_description || "",
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// 3. Store state using sessionID
|
|
904
|
+
const state: AgentMailState = {
|
|
905
|
+
projectKey: args.project_path,
|
|
906
|
+
agentName: agent.name,
|
|
907
|
+
reservations: [],
|
|
908
|
+
startedAt: new Date().toISOString(),
|
|
909
|
+
};
|
|
910
|
+
setState(ctx.sessionID, state);
|
|
911
|
+
|
|
912
|
+
// Success - if we retried, log it
|
|
913
|
+
if (attempt > 1) {
|
|
914
|
+
console.warn(
|
|
915
|
+
`[agent-mail] Init succeeded on attempt ${attempt} after restart`,
|
|
916
|
+
);
|
|
917
|
+
}
|
|
881
918
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
919
|
+
return JSON.stringify({ project, agent, available: true }, null, 2);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
922
|
+
const isUnexpectedError = lastError.message
|
|
923
|
+
.toLowerCase()
|
|
924
|
+
.includes("unexpected error");
|
|
925
|
+
|
|
926
|
+
console.warn(
|
|
927
|
+
`[agent-mail] Init attempt ${attempt}/${MAX_INIT_RETRIES} failed: ${lastError.message}`,
|
|
928
|
+
);
|
|
890
929
|
|
|
891
|
-
|
|
930
|
+
// If it's an "unexpected error" and we have retries left, restart and retry
|
|
931
|
+
if (isUnexpectedError && attempt < MAX_INIT_RETRIES) {
|
|
932
|
+
console.warn(
|
|
933
|
+
"[agent-mail] Detected 'unexpected error', restarting server...",
|
|
934
|
+
);
|
|
935
|
+
const restarted = await restartServer();
|
|
936
|
+
if (restarted) {
|
|
937
|
+
// Clear cache and retry
|
|
938
|
+
agentMailAvailable = null;
|
|
939
|
+
consecutiveFailures = 0;
|
|
940
|
+
// Small delay to let server stabilize
|
|
941
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// For non-unexpected errors or if restart failed, don't retry
|
|
947
|
+
if (!isUnexpectedError) {
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// All retries exhausted
|
|
954
|
+
return JSON.stringify(
|
|
955
|
+
{
|
|
956
|
+
error: `Agent Mail init failed after ${MAX_INIT_RETRIES} attempts`,
|
|
957
|
+
available: false,
|
|
958
|
+
lastError: lastError?.message,
|
|
959
|
+
hint: "Manually restart Agent Mail: pkill -f agent-mail && agent-mail serve",
|
|
960
|
+
fallback: "Swarm will continue without multi-agent coordination.",
|
|
961
|
+
},
|
|
962
|
+
null,
|
|
963
|
+
2,
|
|
964
|
+
);
|
|
892
965
|
},
|
|
893
966
|
});
|
|
894
967
|
|
|
@@ -1006,18 +1079,18 @@ export const agentmail_read_message = tool({
|
|
|
1006
1079
|
message_id: args.message_id,
|
|
1007
1080
|
});
|
|
1008
1081
|
|
|
1009
|
-
// Fetch with body -
|
|
1010
|
-
// Since there's no get_message, we
|
|
1082
|
+
// Fetch with body - fetch more messages to find the requested one
|
|
1083
|
+
// Since there's no get_message endpoint, we need to fetch a reasonable batch
|
|
1011
1084
|
const messages = await mcpCall<MessageHeader[]>("fetch_inbox", {
|
|
1012
1085
|
project_key: state.projectKey,
|
|
1013
1086
|
agent_name: state.agentName,
|
|
1014
|
-
limit:
|
|
1087
|
+
limit: 50, // Fetch more messages to increase chance of finding the target
|
|
1015
1088
|
include_bodies: true, // Only for single message fetch
|
|
1016
1089
|
});
|
|
1017
1090
|
|
|
1018
1091
|
const message = messages.find((m) => m.id === args.message_id);
|
|
1019
1092
|
if (!message) {
|
|
1020
|
-
return `Message ${args.message_id} not found
|
|
1093
|
+
return `Message ${args.message_id} not found in recent 50 messages. Try using agentmail_search to locate it.`;
|
|
1021
1094
|
}
|
|
1022
1095
|
|
|
1023
1096
|
// Record successful request
|
|
@@ -1255,7 +1328,12 @@ export const agentmail_health = tool({
|
|
|
1255
1328
|
try {
|
|
1256
1329
|
const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`);
|
|
1257
1330
|
if (response.ok) {
|
|
1258
|
-
|
|
1331
|
+
// Also check if MCP is functional
|
|
1332
|
+
const functional = await isServerFunctional();
|
|
1333
|
+
if (functional) {
|
|
1334
|
+
return "Agent Mail is running and functional";
|
|
1335
|
+
}
|
|
1336
|
+
return "Agent Mail health OK but MCP not responding - consider restart";
|
|
1259
1337
|
}
|
|
1260
1338
|
return `Agent Mail returned status ${response.status}`;
|
|
1261
1339
|
} catch (error) {
|
|
@@ -1264,6 +1342,73 @@ export const agentmail_health = tool({
|
|
|
1264
1342
|
},
|
|
1265
1343
|
});
|
|
1266
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* Manually restart Agent Mail server
|
|
1347
|
+
*
|
|
1348
|
+
* Use when server is in bad state (health OK but MCP failing).
|
|
1349
|
+
* This kills the existing process and starts a fresh one.
|
|
1350
|
+
*/
|
|
1351
|
+
export const agentmail_restart = tool({
|
|
1352
|
+
description:
|
|
1353
|
+
"Manually restart Agent Mail server (use when getting 'unexpected error')",
|
|
1354
|
+
args: {
|
|
1355
|
+
force: tool.schema
|
|
1356
|
+
.boolean()
|
|
1357
|
+
.optional()
|
|
1358
|
+
.describe(
|
|
1359
|
+
"Force restart even if server appears healthy (default: false)",
|
|
1360
|
+
),
|
|
1361
|
+
},
|
|
1362
|
+
async execute(args) {
|
|
1363
|
+
// Check if restart is needed
|
|
1364
|
+
if (!args.force) {
|
|
1365
|
+
const functional = await isServerFunctional();
|
|
1366
|
+
if (functional) {
|
|
1367
|
+
return JSON.stringify(
|
|
1368
|
+
{
|
|
1369
|
+
restarted: false,
|
|
1370
|
+
reason: "Server is functional, no restart needed",
|
|
1371
|
+
hint: "Use force=true to restart anyway",
|
|
1372
|
+
},
|
|
1373
|
+
null,
|
|
1374
|
+
2,
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Attempt restart
|
|
1380
|
+
console.warn("[agent-mail] Manual restart requested...");
|
|
1381
|
+
const success = await restartServer();
|
|
1382
|
+
|
|
1383
|
+
// Clear caches
|
|
1384
|
+
agentMailAvailable = null;
|
|
1385
|
+
consecutiveFailures = 0;
|
|
1386
|
+
|
|
1387
|
+
if (success) {
|
|
1388
|
+
return JSON.stringify(
|
|
1389
|
+
{
|
|
1390
|
+
restarted: true,
|
|
1391
|
+
success: true,
|
|
1392
|
+
message: "Agent Mail server restarted successfully",
|
|
1393
|
+
},
|
|
1394
|
+
null,
|
|
1395
|
+
2,
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return JSON.stringify(
|
|
1400
|
+
{
|
|
1401
|
+
restarted: true,
|
|
1402
|
+
success: false,
|
|
1403
|
+
error: "Restart attempted but server did not come back up",
|
|
1404
|
+
hint: "Check server logs or manually start: agent-mail serve",
|
|
1405
|
+
},
|
|
1406
|
+
null,
|
|
1407
|
+
2,
|
|
1408
|
+
);
|
|
1409
|
+
},
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1267
1412
|
// ============================================================================
|
|
1268
1413
|
// Export all tools
|
|
1269
1414
|
// ============================================================================
|
|
@@ -1279,6 +1424,7 @@ export const agentMailTools = {
|
|
|
1279
1424
|
agentmail_ack: agentmail_ack,
|
|
1280
1425
|
agentmail_search: agentmail_search,
|
|
1281
1426
|
agentmail_health: agentmail_health,
|
|
1427
|
+
agentmail_restart: agentmail_restart,
|
|
1282
1428
|
};
|
|
1283
1429
|
|
|
1284
1430
|
// ============================================================================
|