swarm-code 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/LICENSE +21 -0
- package/README.md +384 -0
- package/bin/swarm.mjs +45 -0
- package/dist/agents/aider.d.ts +12 -0
- package/dist/agents/aider.js +182 -0
- package/dist/agents/claude-code.d.ts +9 -0
- package/dist/agents/claude-code.js +216 -0
- package/dist/agents/codex.d.ts +14 -0
- package/dist/agents/codex.js +193 -0
- package/dist/agents/direct-llm.d.ts +9 -0
- package/dist/agents/direct-llm.js +78 -0
- package/dist/agents/mock.d.ts +9 -0
- package/dist/agents/mock.js +77 -0
- package/dist/agents/opencode.d.ts +23 -0
- package/dist/agents/opencode.js +571 -0
- package/dist/agents/provider.d.ts +11 -0
- package/dist/agents/provider.js +31 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +285 -0
- package/dist/compression/compressor.d.ts +28 -0
- package/dist/compression/compressor.js +265 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +170 -0
- package/dist/core/repl.d.ts +69 -0
- package/dist/core/repl.js +336 -0
- package/dist/core/rlm.d.ts +63 -0
- package/dist/core/rlm.js +409 -0
- package/dist/core/runtime.py +335 -0
- package/dist/core/types.d.ts +131 -0
- package/dist/core/types.js +19 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +75 -0
- package/dist/interactive-swarm.d.ts +20 -0
- package/dist/interactive-swarm.js +1041 -0
- package/dist/interactive.d.ts +10 -0
- package/dist/interactive.js +1765 -0
- package/dist/main.d.ts +15 -0
- package/dist/main.js +242 -0
- package/dist/mcp/server.d.ts +15 -0
- package/dist/mcp/server.js +72 -0
- package/dist/mcp/session.d.ts +73 -0
- package/dist/mcp/session.js +184 -0
- package/dist/mcp/tools.d.ts +15 -0
- package/dist/mcp/tools.js +377 -0
- package/dist/memory/episodic.d.ts +132 -0
- package/dist/memory/episodic.js +390 -0
- package/dist/prompts/orchestrator.d.ts +5 -0
- package/dist/prompts/orchestrator.js +191 -0
- package/dist/routing/model-router.d.ts +130 -0
- package/dist/routing/model-router.js +515 -0
- package/dist/swarm.d.ts +14 -0
- package/dist/swarm.js +557 -0
- package/dist/threads/cache.d.ts +58 -0
- package/dist/threads/cache.js +198 -0
- package/dist/threads/manager.d.ts +85 -0
- package/dist/threads/manager.js +659 -0
- package/dist/ui/banner.d.ts +14 -0
- package/dist/ui/banner.js +42 -0
- package/dist/ui/dashboard.d.ts +33 -0
- package/dist/ui/dashboard.js +151 -0
- package/dist/ui/index.d.ts +10 -0
- package/dist/ui/index.js +11 -0
- package/dist/ui/log.d.ts +39 -0
- package/dist/ui/log.js +126 -0
- package/dist/ui/onboarding.d.ts +14 -0
- package/dist/ui/onboarding.js +518 -0
- package/dist/ui/spinner.d.ts +25 -0
- package/dist/ui/spinner.js +113 -0
- package/dist/ui/summary.d.ts +18 -0
- package/dist/ui/summary.js +113 -0
- package/dist/ui/theme.d.ts +63 -0
- package/dist/ui/theme.js +97 -0
- package/dist/viewer.d.ts +12 -0
- package/dist/viewer.js +1284 -0
- package/dist/worktree/manager.d.ts +45 -0
- package/dist/worktree/manager.js +266 -0
- package/dist/worktree/merge.d.ts +28 -0
- package/dist/worktree/merge.js +138 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Vipul Maheshwari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# swarm-code
|
|
2
|
+
|
|
3
|
+
Open-source swarm-native coding agent orchestrator. Spawns parallel coding agents in isolated git worktrees, orchestrated by a Recursive Language Model (based on [arXiv:2512.24601](https://arxiv.org/abs/2512.24601)).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g swarm-code
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires **Node.js >= 20** and **Python 3**.
|
|
12
|
+
|
|
13
|
+
### Supported Providers
|
|
14
|
+
|
|
15
|
+
| Provider | Env Variable | Default Model |
|
|
16
|
+
|----------|-------------|---------------|
|
|
17
|
+
| **Anthropic** | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
|
|
18
|
+
| **OpenAI** | `OPENAI_API_KEY` | `gpt-4o` |
|
|
19
|
+
| **Google** | `GEMINI_API_KEY` | `gemini-2.5-flash` |
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### From Source
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/kingjulio8238/swarm-code.git
|
|
29
|
+
cd swarm-code
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
npm link
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Swarm Mode — Parallel Coding Agents
|
|
38
|
+
|
|
39
|
+
Point swarm at a repo with a task. It scans the codebase, decomposes the work, and spawns coding agents in isolated git worktrees:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
swarm --dir ./my-project "add error handling to all API routes"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The orchestrator LLM writes Python code that calls `thread()` to spawn agents, `asyncio.gather()` for parallelism, and `merge_threads()` to integrate changes:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# With auto model routing (picks best agent+model per task)
|
|
49
|
+
swarm --dir ./project --auto-route "migrate from Express to Fastify"
|
|
50
|
+
|
|
51
|
+
# Dry run — plan without executing
|
|
52
|
+
swarm --dir ./project --dry-run "refactor auth module"
|
|
53
|
+
|
|
54
|
+
# Budget cap
|
|
55
|
+
swarm --dir ./project --max-budget 5.00 "add comprehensive tests"
|
|
56
|
+
|
|
57
|
+
# Specific agent backend
|
|
58
|
+
swarm --dir ./project --agent claude-code "review and fix security issues"
|
|
59
|
+
|
|
60
|
+
# Verbose — see routing decisions and memory hints
|
|
61
|
+
swarm --dir ./project --verbose --auto-route "optimize database queries"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Agent Backends
|
|
65
|
+
|
|
66
|
+
| Agent | Description | Best for |
|
|
67
|
+
|-------|------------|----------|
|
|
68
|
+
| `opencode` (default) | Open-source, multi-provider, tool-capable | General coding, testing |
|
|
69
|
+
| `claude-code` | Anthropic's Claude Code CLI | Deep analysis, refactoring |
|
|
70
|
+
| `codex` | OpenAI's Codex CLI | Shell commands, OpenAI models |
|
|
71
|
+
| `aider` | Git-aware AI coding assistant | Targeted edits, minimal changes |
|
|
72
|
+
| `direct-llm` | Bare LLM call, no agent wrapper | Analysis, planning, classification |
|
|
73
|
+
|
|
74
|
+
### RLM Text Mode (inherited)
|
|
75
|
+
|
|
76
|
+
The original RLM text-processing mode is preserved:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
swarm run --file large-document.txt "summarize the key findings"
|
|
80
|
+
swarm run --url https://example.com/data.txt "extract all dates"
|
|
81
|
+
cat data.txt | swarm run --stdin "count the errors"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Interactive Mode
|
|
85
|
+
|
|
86
|
+
Run with `--dir` but no task to enter interactive mode — a persistent REPL with live thread monitoring:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
swarm --dir ./my-project
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Commands:
|
|
93
|
+
|
|
94
|
+
| Command | Description |
|
|
95
|
+
|---------|-------------|
|
|
96
|
+
| `/threads` | List all threads with status, cost, duration |
|
|
97
|
+
| `/thread <id>` | Show thread detail (files changed, diff, result) |
|
|
98
|
+
| `/merge` | Merge all completed thread branches |
|
|
99
|
+
| `/reject <id>` | Reject a thread's changes |
|
|
100
|
+
| `/dag` | Show thread DAG with timing bars |
|
|
101
|
+
| `/budget` | Show budget breakdown |
|
|
102
|
+
| `/status` | Show session stats |
|
|
103
|
+
| `/help` | List commands |
|
|
104
|
+
| `/quit` | Cleanup and exit |
|
|
105
|
+
|
|
106
|
+
Ctrl+C once cancels the current task. Ctrl+C twice exits.
|
|
107
|
+
|
|
108
|
+
### GitHub Action
|
|
109
|
+
|
|
110
|
+
Trigger swarm from issue comments. Add `.github/workflows/swarm.yml` to your repo (a template is provided in this repo):
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
name: Swarm Agent
|
|
114
|
+
on:
|
|
115
|
+
issue_comment:
|
|
116
|
+
types: [created]
|
|
117
|
+
workflow_dispatch:
|
|
118
|
+
inputs:
|
|
119
|
+
task:
|
|
120
|
+
description: 'Task for swarm to execute'
|
|
121
|
+
required: true
|
|
122
|
+
type: string
|
|
123
|
+
|
|
124
|
+
jobs:
|
|
125
|
+
swarm:
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
timeout-minutes: 30
|
|
128
|
+
if: >
|
|
129
|
+
github.event_name == 'workflow_dispatch' ||
|
|
130
|
+
(github.event_name == 'issue_comment' &&
|
|
131
|
+
contains(github.event.comment.body, '@swarm'))
|
|
132
|
+
permissions:
|
|
133
|
+
contents: write
|
|
134
|
+
pull-requests: write
|
|
135
|
+
issues: write
|
|
136
|
+
steps:
|
|
137
|
+
- uses: actions/checkout@v4
|
|
138
|
+
- uses: kingjulio8238/swarm-code@main
|
|
139
|
+
with:
|
|
140
|
+
task: ${{ github.event.inputs.task || '' }}
|
|
141
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
142
|
+
max_budget: '5.00'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then comment on any issue:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
@swarm fix the auth bug in src/auth.ts
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Swarm runs, creates a PR with the changes, and posts a summary back on the issue.
|
|
152
|
+
|
|
153
|
+
**Security**: Only OWNER/MEMBER/COLLABORATOR can trigger. Fork PRs are rejected. Budget hard cap of $50. API keys are masked in logs.
|
|
154
|
+
|
|
155
|
+
**Action inputs**: `task`, `anthropic_api_key`, `openai_api_key`, `gemini_api_key`, `agent`, `model`, `max_budget`
|
|
156
|
+
|
|
157
|
+
**Action outputs**: `success`, `pr_url`, `cost_usd`, `threads_completed`, `threads_failed`, `elapsed_s`, `answer`, `skipped`, `skip_reason`
|
|
158
|
+
|
|
159
|
+
### MCP Server
|
|
160
|
+
|
|
161
|
+
Expose swarm as tools for Claude Code, Cursor, or any MCP-compatible agent:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
swarm mcp # Start MCP server (stdio)
|
|
165
|
+
swarm mcp --dir ./my-project # Start with default directory
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Tools exposed:**
|
|
169
|
+
|
|
170
|
+
| Tool | Description |
|
|
171
|
+
|------|-------------|
|
|
172
|
+
| `swarm_run` | Full swarm orchestration — decompose, spawn threads, merge, return result |
|
|
173
|
+
| `swarm_thread` | Spawn a single coding agent in an isolated worktree |
|
|
174
|
+
| `swarm_status` | Get session status — threads, budget, costs |
|
|
175
|
+
| `swarm_merge` | Merge completed thread branches back to main |
|
|
176
|
+
| `swarm_cancel` | Cancel running thread(s) |
|
|
177
|
+
| `swarm_cleanup` | Destroy session and clean up worktrees |
|
|
178
|
+
|
|
179
|
+
**Claude Code setup:**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
claude mcp add swarm-code -- npx swarm-code mcp
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or add to your project's `.mcp.json`:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"mcpServers": {
|
|
190
|
+
"swarm-code": {
|
|
191
|
+
"command": "npx",
|
|
192
|
+
"args": ["swarm-code", "mcp", "--dir", "."],
|
|
193
|
+
"env": {
|
|
194
|
+
"ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY}"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Cursor setup:** Add to `.cursor/mcp.json` with the same format.
|
|
202
|
+
|
|
203
|
+
Once configured, Claude Code or Cursor can call `swarm_thread` to spawn coding agents in worktrees, check progress with `swarm_status`, and merge with `swarm_merge` — giving the host agent full orchestration control.
|
|
204
|
+
|
|
205
|
+
### Trajectory Viewer
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
swarm viewer
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Browse saved runs in a TUI. View iterations, code, output, sub-queries, and swarm thread DAGs with timing bars and cost breakdowns. Arrow keys to navigate, Enter to drill down into thread details.
|
|
212
|
+
|
|
213
|
+
### Benchmarks
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
swarm benchmark oolong # Oolong Synth long-context benchmark
|
|
217
|
+
swarm benchmark longbench # LongBench NarrativeQA benchmark
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## How It Works
|
|
221
|
+
|
|
222
|
+
1. **Scan**: Codebase is scanned and loaded as context
|
|
223
|
+
2. **Orchestrate**: The RLM loop runs — the LLM writes Python code using swarm primitives
|
|
224
|
+
3. **Decompose**: Tasks are broken into independent, parallelizable units
|
|
225
|
+
4. **Spawn**: `thread()` / `async_thread()` spawn coding agents in isolated git worktrees
|
|
226
|
+
5. **Compress**: Agent output is filtered to successful operations only (episode quality)
|
|
227
|
+
6. **Merge**: `merge_threads()` integrates worktree branches back to main
|
|
228
|
+
7. **Verify**: Optional test thread validates the merged result
|
|
229
|
+
|
|
230
|
+
### Python Primitives
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
# Lightweight LLM query (no file changes)
|
|
234
|
+
analysis = llm_query(context[:5000], "List all API endpoints")
|
|
235
|
+
|
|
236
|
+
# Spawn a coding agent in an isolated worktree
|
|
237
|
+
result = thread("Fix the auth bug", files=["src/auth.ts"])
|
|
238
|
+
|
|
239
|
+
# Parallel threads
|
|
240
|
+
import asyncio
|
|
241
|
+
results = await asyncio.gather(
|
|
242
|
+
async_thread("Add validation to POST /users", files=["src/routes/users.ts"]),
|
|
243
|
+
async_thread("Add validation to POST /orders", files=["src/routes/orders.ts"]),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Merge all thread branches back to main
|
|
247
|
+
merge_threads()
|
|
248
|
+
|
|
249
|
+
# Return final answer
|
|
250
|
+
FINAL("Added input validation to all API routes")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Thread DAG Composition
|
|
254
|
+
|
|
255
|
+
Thread results compose naturally via Python variable persistence:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# Stage 1: Research in parallel
|
|
259
|
+
analysis, test_gaps = await asyncio.gather(
|
|
260
|
+
async_thread("Analyze the auth module", files=["src/auth/"]),
|
|
261
|
+
async_thread("Find files with <50% coverage", files=["package.json"]),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Stage 2: Act on Stage 1 results
|
|
265
|
+
await asyncio.gather(
|
|
266
|
+
async_thread("Add rate limiting", context=analysis, files=["src/auth/middleware.ts"]),
|
|
267
|
+
async_thread("Add tests for low-coverage files", context=test_gaps),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Stage 3: Merge and validate
|
|
271
|
+
merge_threads()
|
|
272
|
+
thread("Run full test suite and fix failures")
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Configuration
|
|
276
|
+
|
|
277
|
+
Create `swarm_config.yaml` in your project root:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
# Concurrency
|
|
281
|
+
max_threads: 5 # Max concurrent threads
|
|
282
|
+
max_total_threads: 20 # Max threads per session
|
|
283
|
+
thread_timeout_ms: 300000 # 5min per thread
|
|
284
|
+
|
|
285
|
+
# Budget
|
|
286
|
+
max_thread_budget_usd: 1.00 # Per-thread cost cap
|
|
287
|
+
max_session_budget_usd: 10.00 # Total session cost cap
|
|
288
|
+
|
|
289
|
+
# Agent
|
|
290
|
+
default_agent: opencode # opencode, claude-code, codex, aider, direct-llm
|
|
291
|
+
default_model: anthropic/claude-sonnet-4-6
|
|
292
|
+
auto_model_selection: false # Enable auto-routing
|
|
293
|
+
|
|
294
|
+
# Compression
|
|
295
|
+
compression_strategy: structured # structured, diff-only, truncate, llm-summary
|
|
296
|
+
|
|
297
|
+
# Model slots — override model per task type
|
|
298
|
+
# model_slot_execution: anthropic/claude-sonnet-4-6
|
|
299
|
+
# model_slot_search: anthropic/claude-haiku-4-5
|
|
300
|
+
# model_slot_reasoning: anthropic/claude-opus-4-6
|
|
301
|
+
# model_slot_planning: anthropic/claude-opus-4-6
|
|
302
|
+
|
|
303
|
+
# Episodic memory — cross-session strategy learning
|
|
304
|
+
episodic_memory_enabled: false
|
|
305
|
+
memory_dir: ~/.swarm/memory
|
|
306
|
+
|
|
307
|
+
# Thread cache persistence
|
|
308
|
+
thread_cache_persist: false
|
|
309
|
+
thread_cache_dir: ~/.swarm/cache
|
|
310
|
+
thread_cache_ttl_hours: 24
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Key Optimizations
|
|
314
|
+
|
|
315
|
+
- **Episode quality**: Compression filters agent output to only successful operations — failed attempts, stack traces, and retries are stripped automatically
|
|
316
|
+
- **Subthread caching**: Identical threads (same task + files + agent + model) are cached in-memory with optional disk persistence and TTL expiry
|
|
317
|
+
- **Named model slots**: Tasks auto-classified into execution/search/reasoning/planning slots, each with preferred agents and optional model overrides
|
|
318
|
+
- **Episodic memory**: Persists successful thread strategies to disk; trigram-based similarity recall informs agent/model selection in future sessions
|
|
319
|
+
- **DAG composition**: Thread results compose via Python variable persistence (T1+T2 → T3); orchestrator prompt teaches multi-stage pipelines and failure re-routing
|
|
320
|
+
- **Failure tracking**: Exponential-decay weighted failure rates per agent/model pair — recent failures penalized more, agents that keep failing get routed around
|
|
321
|
+
|
|
322
|
+
## Architecture
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
src/
|
|
326
|
+
├── main.ts CLI entry point (swarm/run/viewer/benchmark)
|
|
327
|
+
├── swarm.ts Swarm orchestration (single-shot)
|
|
328
|
+
├── interactive-swarm.ts Interactive REPL with live monitoring
|
|
329
|
+
├── cli.ts RLM text mode
|
|
330
|
+
├── core/
|
|
331
|
+
│ ├── rlm.ts Core RLM loop (Algorithm 1)
|
|
332
|
+
│ ├── repl.ts Python REPL bridge (JSON over stdin/stdout)
|
|
333
|
+
│ ├── runtime.py Python runtime (thread/async_thread/merge)
|
|
334
|
+
│ └── types.ts Shared type definitions
|
|
335
|
+
├── agents/
|
|
336
|
+
│ ├── provider.ts AgentProvider interface + registry
|
|
337
|
+
│ ├── opencode.ts OpenCode (subprocess + server mode)
|
|
338
|
+
│ ├── claude-code.ts Claude Code CLI backend
|
|
339
|
+
│ ├── codex.ts Codex CLI backend
|
|
340
|
+
│ ├── aider.ts Aider backend
|
|
341
|
+
│ └── direct-llm.ts Bare LLM calls
|
|
342
|
+
├── threads/
|
|
343
|
+
│ ├── manager.ts Thread lifecycle + concurrency + episodes
|
|
344
|
+
│ └── cache.ts Subthread cache (memory + disk)
|
|
345
|
+
├── worktree/
|
|
346
|
+
│ ├── manager.ts Git worktree CRUD
|
|
347
|
+
│ └── merge.ts Branch merging
|
|
348
|
+
├── compression/
|
|
349
|
+
│ └── compressor.ts Result compression strategies
|
|
350
|
+
├── routing/
|
|
351
|
+
│ └── model-router.ts Auto model/agent selection + failure tracking
|
|
352
|
+
├── memory/
|
|
353
|
+
│ └── episodic.ts Cross-session strategy learning
|
|
354
|
+
├── prompts/
|
|
355
|
+
│ └── orchestrator.ts Swarm system prompt
|
|
356
|
+
├── mcp/
|
|
357
|
+
│ ├── server.ts MCP server entry point (stdio transport)
|
|
358
|
+
│ ├── tools.ts Tool definitions + handlers
|
|
359
|
+
│ └── session.ts Per-directory session state
|
|
360
|
+
├── ui/
|
|
361
|
+
│ ├── onboarding.ts First-run setup wizard
|
|
362
|
+
│ ├── spinner.ts CLI spinner
|
|
363
|
+
│ ├── dashboard.ts Live progress dashboard
|
|
364
|
+
│ └── summary.ts Session summary + JSON output
|
|
365
|
+
└── viewer.ts Trajectory TUI + DAG viewer
|
|
366
|
+
action/
|
|
367
|
+
├── entrypoint.ts GitHub Action orchestration
|
|
368
|
+
├── parse-trigger.ts @swarm comment parsing
|
|
369
|
+
├── security.ts Auth + fork detection + budget caps
|
|
370
|
+
└── pr.ts PR creation + issue commenting
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Development
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
npm install # Install deps
|
|
377
|
+
npx tsx src/main.ts --dir . # Run in dev mode
|
|
378
|
+
npm run build # Compile TypeScript
|
|
379
|
+
npm test # Run tests
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## License
|
|
383
|
+
|
|
384
|
+
MIT
|
package/bin/swarm.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* swarm — Swarm-Native Coding Agent CLI
|
|
5
|
+
*
|
|
6
|
+
* This shim boots the CLI entry point. It tries the compiled dist first,
|
|
7
|
+
* then falls back to tsx for development.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const distEntry = join(__dirname, "..", "dist", "main.js");
|
|
16
|
+
|
|
17
|
+
if (existsSync(distEntry)) {
|
|
18
|
+
// Production: use compiled JS (pathToFileURL needed for Windows)
|
|
19
|
+
await import(pathToFileURL(distEntry).href);
|
|
20
|
+
} else {
|
|
21
|
+
// Development: use tsx to run TypeScript directly
|
|
22
|
+
const srcEntry = join(__dirname, "..", "src", "main.ts");
|
|
23
|
+
const { register } = await import("node:module");
|
|
24
|
+
|
|
25
|
+
// Try to register tsx loader, then import
|
|
26
|
+
try {
|
|
27
|
+
const tsxPath = join(__dirname, "..", "node_modules", "tsx", "dist", "esm", "index.mjs");
|
|
28
|
+
if (existsSync(tsxPath)) {
|
|
29
|
+
register(pathToFileURL(tsxPath).href);
|
|
30
|
+
}
|
|
31
|
+
await import(pathToFileURL(srcEntry).href);
|
|
32
|
+
} catch {
|
|
33
|
+
// Fallback: spawn tsx as a child process
|
|
34
|
+
const { spawn } = await import("node:child_process");
|
|
35
|
+
const tsxBin = join(__dirname, "..", "node_modules", ".bin", "tsx");
|
|
36
|
+
const child = spawn(tsxBin, [srcEntry, ...process.argv.slice(2)], {
|
|
37
|
+
stdio: "inherit",
|
|
38
|
+
});
|
|
39
|
+
child.on("exit", (code) => process.exit(code ?? 1));
|
|
40
|
+
child.on("error", (err) => {
|
|
41
|
+
console.error(`Failed to start swarm: ${err.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aider agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Runs tasks via `aider --yes-always --no-auto-commits --message "prompt"` subprocess.
|
|
5
|
+
* Aider is a git-aware AI coding assistant that makes targeted edits.
|
|
6
|
+
*
|
|
7
|
+
* Output format: Plain text (no JSON mode available).
|
|
8
|
+
* Edit confirmations appear as "Applied edit to <file>" lines.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentProvider } from "../core/types.js";
|
|
11
|
+
declare const aiderProvider: AgentProvider;
|
|
12
|
+
export default aiderProvider;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aider agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Runs tasks via `aider --yes-always --no-auto-commits --message "prompt"` subprocess.
|
|
5
|
+
* Aider is a git-aware AI coding assistant that makes targeted edits.
|
|
6
|
+
*
|
|
7
|
+
* Output format: Plain text (no JSON mode available).
|
|
8
|
+
* Edit confirmations appear as "Applied edit to <file>" lines.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import { registerAgent } from "./provider.js";
|
|
13
|
+
async function commandExists(cmd) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const proc = spawn("which", [cmd], { stdio: "pipe" });
|
|
16
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
17
|
+
proc.on("error", () => resolve(false));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Whitelist of env vars safe to pass to agent subprocess. */
|
|
21
|
+
function buildAgentEnv() {
|
|
22
|
+
const homeDir = os.homedir();
|
|
23
|
+
return {
|
|
24
|
+
PATH: process.env.PATH,
|
|
25
|
+
HOME: homeDir,
|
|
26
|
+
USERPROFILE: homeDir,
|
|
27
|
+
SHELL: process.env.SHELL,
|
|
28
|
+
TERM: process.env.TERM,
|
|
29
|
+
LANG: process.env.LANG,
|
|
30
|
+
// Aider supports multiple providers via these keys
|
|
31
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
32
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
33
|
+
GEMINI_API_KEY: process.env.GEMINI_API_KEY,
|
|
34
|
+
// Aider-specific env vars
|
|
35
|
+
AIDER_MODEL: process.env.AIDER_MODEL,
|
|
36
|
+
AIDER_DARK_MODE: process.env.AIDER_DARK_MODE,
|
|
37
|
+
// Git config
|
|
38
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
|
|
39
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
|
|
40
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
|
|
41
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
|
|
42
|
+
// Python (aider is Python-based)
|
|
43
|
+
VIRTUAL_ENV: process.env.VIRTUAL_ENV,
|
|
44
|
+
CONDA_DEFAULT_ENV: process.env.CONDA_DEFAULT_ENV,
|
|
45
|
+
PYTHONPATH: process.env.PYTHONPATH,
|
|
46
|
+
// Windows
|
|
47
|
+
...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
48
|
+
...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Map provider/model format to aider model flag. */
|
|
52
|
+
function resolveModel(model) {
|
|
53
|
+
// Aider accepts provider/model format natively (e.g., "anthropic/claude-sonnet-4-6")
|
|
54
|
+
// It also has short aliases: sonnet, opus, haiku, 4o, etc.
|
|
55
|
+
return model;
|
|
56
|
+
}
|
|
57
|
+
/** Parse aider output to extract edited file paths. */
|
|
58
|
+
function extractFilesChanged(output) {
|
|
59
|
+
const files = [];
|
|
60
|
+
// Aider outputs lines like:
|
|
61
|
+
// "Applied edit to src/auth.ts"
|
|
62
|
+
// "Wrote src/auth.ts"
|
|
63
|
+
// "Committing src/auth.ts ..."
|
|
64
|
+
// "Created new file src/utils.ts"
|
|
65
|
+
const patterns = [
|
|
66
|
+
/Applied edit to\s+(.+?)(?:\s*$)/gm,
|
|
67
|
+
/Wrote\s+(.+?)(?:\s*$)/gm,
|
|
68
|
+
/Committing\s+(.+?)(?:\s+\.\.\.|\s*$)/gm,
|
|
69
|
+
/(?:Created|Added)\s+new file\s+(.+?)(?:\s*$)/gm,
|
|
70
|
+
];
|
|
71
|
+
for (const pattern of patterns) {
|
|
72
|
+
let match;
|
|
73
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
74
|
+
const file = match[1].trim();
|
|
75
|
+
if (file) {
|
|
76
|
+
files.push(file);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [...new Set(files)];
|
|
81
|
+
}
|
|
82
|
+
const aiderProvider = {
|
|
83
|
+
name: "aider",
|
|
84
|
+
async isAvailable() {
|
|
85
|
+
return commandExists("aider");
|
|
86
|
+
},
|
|
87
|
+
async run(options) {
|
|
88
|
+
const { task, workDir, model, files, signal } = options;
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
const args = [
|
|
91
|
+
"--yes-always", // Auto-confirm all prompts (except shell commands)
|
|
92
|
+
"--no-auto-commits", // Don't auto-commit (worktree manager handles commits)
|
|
93
|
+
"--no-pretty", // Disable ANSI colors for clean parsing
|
|
94
|
+
"--no-stream", // Don't stream (capture full output)
|
|
95
|
+
"--no-fancy-input", // Disable prompt toolkit input
|
|
96
|
+
"--no-suggest-shell-commands", // Suppress shell command suggestions
|
|
97
|
+
"--no-detect-urls", // Suppress URL detection prompts
|
|
98
|
+
];
|
|
99
|
+
if (model) {
|
|
100
|
+
args.push("--model", resolveModel(model));
|
|
101
|
+
}
|
|
102
|
+
// Pass the task via --message (non-interactive mode)
|
|
103
|
+
args.push("--message", task);
|
|
104
|
+
// Add specific files to edit if provided
|
|
105
|
+
if (files && files.length > 0) {
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
args.push("--file", file);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
const proc = spawn("aider", args, {
|
|
112
|
+
cwd: workDir,
|
|
113
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
114
|
+
env: buildAgentEnv(),
|
|
115
|
+
});
|
|
116
|
+
let stdout = "";
|
|
117
|
+
let stderr = "";
|
|
118
|
+
let resolved = false;
|
|
119
|
+
const doResolve = (result) => {
|
|
120
|
+
if (resolved)
|
|
121
|
+
return;
|
|
122
|
+
resolved = true;
|
|
123
|
+
resolve(result);
|
|
124
|
+
};
|
|
125
|
+
proc.stdout?.on("data", (chunk) => {
|
|
126
|
+
const text = chunk.toString();
|
|
127
|
+
stdout += text;
|
|
128
|
+
options.onOutput?.(text);
|
|
129
|
+
});
|
|
130
|
+
proc.stderr?.on("data", (chunk) => {
|
|
131
|
+
stderr += chunk.toString();
|
|
132
|
+
});
|
|
133
|
+
if (signal) {
|
|
134
|
+
const onAbort = () => {
|
|
135
|
+
proc.kill("SIGTERM");
|
|
136
|
+
const killTimer = setTimeout(() => {
|
|
137
|
+
try {
|
|
138
|
+
if (proc.exitCode === null)
|
|
139
|
+
proc.kill("SIGKILL");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* already dead */
|
|
143
|
+
}
|
|
144
|
+
}, 3000);
|
|
145
|
+
proc.on("exit", () => clearTimeout(killTimer));
|
|
146
|
+
};
|
|
147
|
+
if (signal.aborted) {
|
|
148
|
+
onAbort();
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
152
|
+
proc.on("exit", () => signal.removeEventListener("abort", onAbort));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
proc.on("close", (code) => {
|
|
156
|
+
const durationMs = Date.now() - startTime;
|
|
157
|
+
const filesChanged = extractFilesChanged(stdout);
|
|
158
|
+
doResolve({
|
|
159
|
+
success: code === 0,
|
|
160
|
+
output: stdout || stderr,
|
|
161
|
+
filesChanged,
|
|
162
|
+
diff: "", // Diff is captured separately by worktree manager
|
|
163
|
+
durationMs,
|
|
164
|
+
error: code !== 0 ? `aider exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}` : undefined,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
proc.on("error", (err) => {
|
|
168
|
+
doResolve({
|
|
169
|
+
success: false,
|
|
170
|
+
output: "",
|
|
171
|
+
filesChanged: [],
|
|
172
|
+
diff: "",
|
|
173
|
+
durationMs: Date.now() - startTime,
|
|
174
|
+
error: `Failed to spawn aider: ${err.message}`,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
registerAgent(aiderProvider);
|
|
181
|
+
export default aiderProvider;
|
|
182
|
+
//# sourceMappingURL=aider.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Runs tasks via `claude -p --output-format json "prompt"` subprocess.
|
|
5
|
+
* Parses structured JSON output for results, cost, and file changes.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentProvider } from "../core/types.js";
|
|
8
|
+
declare const claudeCodeProvider: AgentProvider;
|
|
9
|
+
export default claudeCodeProvider;
|