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
|
@@ -0,0 +1,216 @@
|
|
|
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 { spawn } from "node:child_process";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import { registerAgent } from "./provider.js";
|
|
10
|
+
async function commandExists(cmd) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const proc = spawn("which", [cmd], { stdio: "pipe" });
|
|
13
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
14
|
+
proc.on("error", () => resolve(false));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Extract JSON result from output (may contain non-JSON lines from debug/stderr bleed). */
|
|
18
|
+
function extractJson(raw) {
|
|
19
|
+
// Try direct parse first
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* mixed output */
|
|
25
|
+
}
|
|
26
|
+
// Try last line (claude outputs JSON as final line)
|
|
27
|
+
const lines = raw.trim().split("\n");
|
|
28
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
29
|
+
const line = lines[i].trim();
|
|
30
|
+
if (line.startsWith("{")) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(line);
|
|
33
|
+
if (parsed.type === "result")
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Fallback: find first { to last }
|
|
40
|
+
const firstBrace = raw.indexOf("{");
|
|
41
|
+
const lastBrace = raw.lastIndexOf("}");
|
|
42
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(raw.slice(firstBrace, lastBrace + 1));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* give up */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/** Whitelist of env vars safe to pass to agent subprocess. */
|
|
53
|
+
function buildAgentEnv() {
|
|
54
|
+
const homeDir = os.homedir();
|
|
55
|
+
return {
|
|
56
|
+
PATH: process.env.PATH,
|
|
57
|
+
HOME: homeDir,
|
|
58
|
+
USERPROFILE: homeDir,
|
|
59
|
+
SHELL: process.env.SHELL,
|
|
60
|
+
TERM: process.env.TERM,
|
|
61
|
+
LANG: process.env.LANG,
|
|
62
|
+
// Claude Code uses ANTHROPIC_API_KEY
|
|
63
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
64
|
+
// Git config
|
|
65
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
|
|
66
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
|
|
67
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
|
|
68
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
|
|
69
|
+
// XDG dirs (claude may use for config)
|
|
70
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
71
|
+
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
|
|
72
|
+
// Windows
|
|
73
|
+
...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
74
|
+
...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Map provider/model format to claude model flag. */
|
|
78
|
+
function resolveModel(model) {
|
|
79
|
+
// Strip provider prefix if present (e.g., "anthropic/claude-sonnet-4-6" → "claude-sonnet-4-6")
|
|
80
|
+
const modelName = model.includes("/") ? model.split("/").pop() : model;
|
|
81
|
+
// Claude Code accepts short aliases
|
|
82
|
+
const aliases = {
|
|
83
|
+
"claude-sonnet-4-6": "sonnet",
|
|
84
|
+
"claude-opus-4-6": "opus",
|
|
85
|
+
"claude-haiku-4-5": "haiku",
|
|
86
|
+
};
|
|
87
|
+
return aliases[modelName] || modelName;
|
|
88
|
+
}
|
|
89
|
+
const claudeCodeProvider = {
|
|
90
|
+
name: "claude-code",
|
|
91
|
+
async isAvailable() {
|
|
92
|
+
return commandExists("claude");
|
|
93
|
+
},
|
|
94
|
+
async run(options) {
|
|
95
|
+
const { task, workDir, model, files, signal } = options;
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
const args = ["-p", "--output-format", "json", "--dangerously-skip-permissions", "--no-session-persistence"];
|
|
98
|
+
if (model) {
|
|
99
|
+
args.push("--model", resolveModel(model));
|
|
100
|
+
}
|
|
101
|
+
// Build the prompt — include file hints if provided
|
|
102
|
+
let prompt = task;
|
|
103
|
+
if (files && files.length > 0) {
|
|
104
|
+
prompt += `\n\nRelevant files to focus on:\n${files.map((f) => `- ${f}`).join("\n")}`;
|
|
105
|
+
}
|
|
106
|
+
args.push(prompt);
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
const proc = spawn("claude", args, {
|
|
109
|
+
cwd: workDir,
|
|
110
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
111
|
+
env: buildAgentEnv(),
|
|
112
|
+
});
|
|
113
|
+
let stdout = "";
|
|
114
|
+
let stderr = "";
|
|
115
|
+
let resolved = false;
|
|
116
|
+
const doResolve = (result) => {
|
|
117
|
+
if (resolved)
|
|
118
|
+
return;
|
|
119
|
+
resolved = true;
|
|
120
|
+
resolve(result);
|
|
121
|
+
};
|
|
122
|
+
proc.stdout?.on("data", (chunk) => {
|
|
123
|
+
const text = chunk.toString();
|
|
124
|
+
stdout += text;
|
|
125
|
+
options.onOutput?.(text);
|
|
126
|
+
});
|
|
127
|
+
proc.stderr?.on("data", (chunk) => {
|
|
128
|
+
stderr += chunk.toString();
|
|
129
|
+
});
|
|
130
|
+
if (signal) {
|
|
131
|
+
const onAbort = () => {
|
|
132
|
+
proc.kill("SIGTERM");
|
|
133
|
+
const killTimer = setTimeout(() => {
|
|
134
|
+
try {
|
|
135
|
+
if (proc.exitCode === null)
|
|
136
|
+
proc.kill("SIGKILL");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
/* already dead */
|
|
140
|
+
}
|
|
141
|
+
}, 3000);
|
|
142
|
+
proc.on("exit", () => clearTimeout(killTimer));
|
|
143
|
+
};
|
|
144
|
+
if (signal.aborted) {
|
|
145
|
+
onAbort();
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
149
|
+
proc.on("exit", () => signal.removeEventListener("abort", onAbort));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
proc.on("close", (code) => {
|
|
153
|
+
const durationMs = Date.now() - startTime;
|
|
154
|
+
const parsed = extractJson(stdout);
|
|
155
|
+
if (parsed) {
|
|
156
|
+
// Extract token usage from Claude Code's structured output
|
|
157
|
+
let usage;
|
|
158
|
+
if (parsed.usage) {
|
|
159
|
+
const inputTokens = parsed.usage.input_tokens ?? 0;
|
|
160
|
+
const outputTokens = parsed.usage.output_tokens ?? 0;
|
|
161
|
+
if (inputTokens > 0 || outputTokens > 0) {
|
|
162
|
+
usage = {
|
|
163
|
+
inputTokens,
|
|
164
|
+
outputTokens,
|
|
165
|
+
totalTokens: inputTokens + outputTokens,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (parsed.is_error) {
|
|
170
|
+
doResolve({
|
|
171
|
+
success: false,
|
|
172
|
+
output: parsed.result || stderr,
|
|
173
|
+
filesChanged: [],
|
|
174
|
+
diff: "",
|
|
175
|
+
durationMs: parsed.duration_ms || durationMs,
|
|
176
|
+
error: parsed.result || `Claude Code error: ${parsed.subtype}`,
|
|
177
|
+
usage,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
doResolve({
|
|
182
|
+
success: true,
|
|
183
|
+
output: parsed.result || "",
|
|
184
|
+
filesChanged: [], // Diff is captured separately by worktree manager
|
|
185
|
+
diff: "",
|
|
186
|
+
durationMs: parsed.duration_ms || durationMs,
|
|
187
|
+
usage,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Fallback: no JSON parsed — use raw output
|
|
192
|
+
doResolve({
|
|
193
|
+
success: code === 0,
|
|
194
|
+
output: stdout || stderr,
|
|
195
|
+
filesChanged: [],
|
|
196
|
+
diff: "",
|
|
197
|
+
durationMs,
|
|
198
|
+
error: code !== 0 ? `claude exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}` : undefined,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
proc.on("error", (err) => {
|
|
202
|
+
doResolve({
|
|
203
|
+
success: false,
|
|
204
|
+
output: "",
|
|
205
|
+
filesChanged: [],
|
|
206
|
+
diff: "",
|
|
207
|
+
durationMs: Date.now() - startTime,
|
|
208
|
+
error: `Failed to spawn claude: ${err.message}`,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
registerAgent(claudeCodeProvider);
|
|
215
|
+
export default claudeCodeProvider;
|
|
216
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI agent backend (OpenAI).
|
|
3
|
+
*
|
|
4
|
+
* Runs tasks via `codex exec --json --full-auto "prompt"` subprocess.
|
|
5
|
+
* Codex CLI is OpenAI's open-source coding agent.
|
|
6
|
+
*
|
|
7
|
+
* Output format: JSONL events streamed to stdout, with event types:
|
|
8
|
+
* - thread.started, turn.started, turn.completed, turn.failed
|
|
9
|
+
* - item.started, item.updated, item.completed
|
|
10
|
+
* Item types: assistant_message, command_execution, file_change, etc.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentProvider } from "../core/types.js";
|
|
13
|
+
declare const codexProvider: AgentProvider;
|
|
14
|
+
export default codexProvider;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI agent backend (OpenAI).
|
|
3
|
+
*
|
|
4
|
+
* Runs tasks via `codex exec --json --full-auto "prompt"` subprocess.
|
|
5
|
+
* Codex CLI is OpenAI's open-source coding agent.
|
|
6
|
+
*
|
|
7
|
+
* Output format: JSONL events streamed to stdout, with event types:
|
|
8
|
+
* - thread.started, turn.started, turn.completed, turn.failed
|
|
9
|
+
* - item.started, item.updated, item.completed
|
|
10
|
+
* Item types: assistant_message, command_execution, file_change, etc.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
import { registerAgent } from "./provider.js";
|
|
15
|
+
async function commandExists(cmd) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const proc = spawn("which", [cmd], { stdio: "pipe" });
|
|
18
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
19
|
+
proc.on("error", () => resolve(false));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Whitelist of env vars safe to pass to agent subprocess. */
|
|
23
|
+
function buildAgentEnv() {
|
|
24
|
+
const homeDir = os.homedir();
|
|
25
|
+
return {
|
|
26
|
+
PATH: process.env.PATH,
|
|
27
|
+
HOME: homeDir,
|
|
28
|
+
USERPROFILE: homeDir,
|
|
29
|
+
SHELL: process.env.SHELL,
|
|
30
|
+
TERM: process.env.TERM,
|
|
31
|
+
LANG: process.env.LANG,
|
|
32
|
+
// Codex uses CODEX_API_KEY or OPENAI_API_KEY
|
|
33
|
+
CODEX_API_KEY: process.env.CODEX_API_KEY || process.env.OPENAI_API_KEY,
|
|
34
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
35
|
+
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,
|
|
36
|
+
// Codex config home
|
|
37
|
+
CODEX_HOME: process.env.CODEX_HOME,
|
|
38
|
+
// Git config
|
|
39
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME,
|
|
40
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL,
|
|
41
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME,
|
|
42
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL,
|
|
43
|
+
// Windows
|
|
44
|
+
...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
45
|
+
...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Map provider/model format to codex model flag. */
|
|
49
|
+
function resolveModel(model) {
|
|
50
|
+
// Strip provider prefix (e.g., "openai/o3" → "o3")
|
|
51
|
+
return model.includes("/") ? model.split("/").pop() : model;
|
|
52
|
+
}
|
|
53
|
+
/** Parse JSONL output to extract result, file changes, and token usage. */
|
|
54
|
+
function parseCodexJsonl(stdout) {
|
|
55
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
56
|
+
let output = "";
|
|
57
|
+
const filesChanged = [];
|
|
58
|
+
let success = true;
|
|
59
|
+
let error;
|
|
60
|
+
let totalInput = 0;
|
|
61
|
+
let totalOutput = 0;
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
try {
|
|
64
|
+
const event = JSON.parse(line);
|
|
65
|
+
if (event.type === "turn.failed") {
|
|
66
|
+
success = false;
|
|
67
|
+
error = event.error || "Turn failed";
|
|
68
|
+
}
|
|
69
|
+
if (event.type === "item.completed") {
|
|
70
|
+
if (event.item_type === "assistant_message") {
|
|
71
|
+
// Capture assistant messages
|
|
72
|
+
const content = event.message?.content || event.content || "";
|
|
73
|
+
if (content)
|
|
74
|
+
output += (output ? "\n" : "") + content;
|
|
75
|
+
}
|
|
76
|
+
if (event.item_type === "file_change" && event.file_path) {
|
|
77
|
+
filesChanged.push(event.file_path);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Accumulate token usage from events
|
|
81
|
+
if (event.usage) {
|
|
82
|
+
totalInput += event.usage.input_tokens ?? 0;
|
|
83
|
+
totalOutput += event.usage.output_tokens ?? 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Non-JSON line — ignore
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let usage;
|
|
91
|
+
if (totalInput > 0 || totalOutput > 0) {
|
|
92
|
+
usage = {
|
|
93
|
+
inputTokens: totalInput,
|
|
94
|
+
outputTokens: totalOutput,
|
|
95
|
+
totalTokens: totalInput + totalOutput,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return { output, filesChanged: [...new Set(filesChanged)], success, error, usage };
|
|
99
|
+
}
|
|
100
|
+
const codexProvider = {
|
|
101
|
+
name: "codex",
|
|
102
|
+
async isAvailable() {
|
|
103
|
+
return commandExists("codex");
|
|
104
|
+
},
|
|
105
|
+
async run(options) {
|
|
106
|
+
const { task, workDir, model, files, signal } = options;
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
const args = ["exec", "--json", "--full-auto", "--ephemeral", "--skip-git-repo-check", "--cd", workDir];
|
|
109
|
+
if (model) {
|
|
110
|
+
args.push("--model", resolveModel(model));
|
|
111
|
+
}
|
|
112
|
+
// Build the prompt — include file hints if provided
|
|
113
|
+
let prompt = task;
|
|
114
|
+
if (files && files.length > 0) {
|
|
115
|
+
prompt += `\n\nRelevant files to focus on:\n${files.map((f) => `- ${f}`).join("\n")}`;
|
|
116
|
+
}
|
|
117
|
+
args.push(prompt);
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const proc = spawn("codex", args, {
|
|
120
|
+
cwd: workDir,
|
|
121
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
122
|
+
env: buildAgentEnv(),
|
|
123
|
+
});
|
|
124
|
+
let stdout = "";
|
|
125
|
+
let stderr = "";
|
|
126
|
+
let resolved = false;
|
|
127
|
+
const doResolve = (result) => {
|
|
128
|
+
if (resolved)
|
|
129
|
+
return;
|
|
130
|
+
resolved = true;
|
|
131
|
+
resolve(result);
|
|
132
|
+
};
|
|
133
|
+
proc.stdout?.on("data", (chunk) => {
|
|
134
|
+
const text = chunk.toString();
|
|
135
|
+
stdout += text;
|
|
136
|
+
options.onOutput?.(text);
|
|
137
|
+
});
|
|
138
|
+
proc.stderr?.on("data", (chunk) => {
|
|
139
|
+
stderr += chunk.toString();
|
|
140
|
+
});
|
|
141
|
+
if (signal) {
|
|
142
|
+
const onAbort = () => {
|
|
143
|
+
proc.kill("SIGTERM");
|
|
144
|
+
const killTimer = setTimeout(() => {
|
|
145
|
+
try {
|
|
146
|
+
if (proc.exitCode === null)
|
|
147
|
+
proc.kill("SIGKILL");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* already dead */
|
|
151
|
+
}
|
|
152
|
+
}, 3000);
|
|
153
|
+
proc.on("exit", () => clearTimeout(killTimer));
|
|
154
|
+
};
|
|
155
|
+
if (signal.aborted) {
|
|
156
|
+
onAbort();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
160
|
+
proc.on("exit", () => signal.removeEventListener("abort", onAbort));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
proc.on("close", (code) => {
|
|
164
|
+
const durationMs = Date.now() - startTime;
|
|
165
|
+
// Parse JSONL events
|
|
166
|
+
const parsed = parseCodexJsonl(stdout);
|
|
167
|
+
doResolve({
|
|
168
|
+
success: code === 0 && parsed.success,
|
|
169
|
+
output: parsed.output || stdout || stderr,
|
|
170
|
+
filesChanged: parsed.filesChanged,
|
|
171
|
+
diff: "", // Diff is captured separately by worktree manager
|
|
172
|
+
durationMs,
|
|
173
|
+
error: parsed.error ||
|
|
174
|
+
(code !== 0 ? `codex exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}` : undefined),
|
|
175
|
+
usage: parsed.usage,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
proc.on("error", (err) => {
|
|
179
|
+
doResolve({
|
|
180
|
+
success: false,
|
|
181
|
+
output: "",
|
|
182
|
+
filesChanged: [],
|
|
183
|
+
diff: "",
|
|
184
|
+
durationMs: Date.now() - startTime,
|
|
185
|
+
error: `Failed to spawn codex: ${err.message}`,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
registerAgent(codexProvider);
|
|
192
|
+
export default codexProvider;
|
|
193
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct LLM agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Preserves the original llm_query behavior — sends a prompt directly to an LLM
|
|
5
|
+
* without any coding agent wrapper. Useful for lightweight analysis tasks.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentProvider } from "../core/types.js";
|
|
8
|
+
declare const directLlmProvider: AgentProvider;
|
|
9
|
+
export default directLlmProvider;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct LLM agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Preserves the original llm_query behavior — sends a prompt directly to an LLM
|
|
5
|
+
* without any coding agent wrapper. Useful for lightweight analysis tasks.
|
|
6
|
+
*/
|
|
7
|
+
import { registerAgent } from "./provider.js";
|
|
8
|
+
const directLlmProvider = {
|
|
9
|
+
name: "direct-llm",
|
|
10
|
+
async isAvailable() {
|
|
11
|
+
// Available if any LLM API key is set
|
|
12
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY);
|
|
13
|
+
},
|
|
14
|
+
async run(options) {
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
try {
|
|
17
|
+
// Import pi-ai dynamically to avoid loading it at module level
|
|
18
|
+
const { completeSimple, getModels, getProviders } = await import("@mariozechner/pi-ai");
|
|
19
|
+
const modelId = options.model || process.env.RLM_MODEL || "claude-sonnet-4-6";
|
|
20
|
+
// Find model
|
|
21
|
+
let model;
|
|
22
|
+
for (const provider of getProviders()) {
|
|
23
|
+
for (const m of getModels(provider)) {
|
|
24
|
+
if (m.id === modelId) {
|
|
25
|
+
model = m;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (model)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
if (!model) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
output: "",
|
|
36
|
+
filesChanged: [],
|
|
37
|
+
diff: "",
|
|
38
|
+
durationMs: Date.now() - startTime,
|
|
39
|
+
error: `Model "${modelId}" not found`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const response = await completeSimple(model, {
|
|
43
|
+
systemPrompt: "You are a helpful assistant. Be concise and thorough.",
|
|
44
|
+
messages: [
|
|
45
|
+
{
|
|
46
|
+
role: "user",
|
|
47
|
+
content: options.task,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const output = response.content
|
|
53
|
+
.filter((b) => b.type === "text")
|
|
54
|
+
.map((b) => b.text)
|
|
55
|
+
.join("\n");
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
output,
|
|
59
|
+
filesChanged: [],
|
|
60
|
+
diff: "",
|
|
61
|
+
durationMs: Date.now() - startTime,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
output: "",
|
|
68
|
+
filesChanged: [],
|
|
69
|
+
diff: "",
|
|
70
|
+
durationMs: Date.now() - startTime,
|
|
71
|
+
error: err instanceof Error ? err.message : String(err),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
registerAgent(directLlmProvider);
|
|
77
|
+
export default directLlmProvider;
|
|
78
|
+
//# sourceMappingURL=direct-llm.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock agent backend — for integration testing without real API keys.
|
|
3
|
+
*
|
|
4
|
+
* Simulates a coding agent by writing actual files in the work directory.
|
|
5
|
+
* Registered as "mock" in the agent registry.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentProvider } from "../core/types.js";
|
|
8
|
+
declare const mockProvider: AgentProvider;
|
|
9
|
+
export default mockProvider;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock agent backend — for integration testing without real API keys.
|
|
3
|
+
*
|
|
4
|
+
* Simulates a coding agent by writing actual files in the work directory.
|
|
5
|
+
* Registered as "mock" in the agent registry.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { registerAgent } from "./provider.js";
|
|
10
|
+
const mockProvider = {
|
|
11
|
+
name: "mock",
|
|
12
|
+
async isAvailable() {
|
|
13
|
+
return true;
|
|
14
|
+
},
|
|
15
|
+
async run(options) {
|
|
16
|
+
const { task, workDir, signal } = options;
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
// Check for abort
|
|
19
|
+
if (signal?.aborted) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
output: "Aborted",
|
|
23
|
+
filesChanged: [],
|
|
24
|
+
diff: "",
|
|
25
|
+
durationMs: Date.now() - startTime,
|
|
26
|
+
error: "Aborted before start",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Simulate work delay (50ms)
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
31
|
+
// Check for forced failure mode
|
|
32
|
+
if (task.includes("__FAIL__")) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
output: "Simulated failure",
|
|
36
|
+
filesChanged: [],
|
|
37
|
+
diff: "",
|
|
38
|
+
durationMs: Date.now() - startTime,
|
|
39
|
+
error: "Mock agent forced failure",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Write actual files in the work directory to simulate agent edits
|
|
43
|
+
const filesChanged = [];
|
|
44
|
+
// Create or modify a file based on the task
|
|
45
|
+
const targetFile = path.join(workDir, "hello.ts");
|
|
46
|
+
const content = `// Generated by mock agent\n// Task: ${task}\nexport function hello(): string {\n\treturn "Hello from swarm thread!";\n}\n`;
|
|
47
|
+
fs.writeFileSync(targetFile, content, "utf-8");
|
|
48
|
+
filesChanged.push("hello.ts");
|
|
49
|
+
// If task mentions "multi", create additional files
|
|
50
|
+
if (task.toLowerCase().includes("multi")) {
|
|
51
|
+
const extraFile = path.join(workDir, "utils.ts");
|
|
52
|
+
fs.writeFileSync(extraFile, `// Utility module\nexport const VERSION = "1.0.0";\n`, "utf-8");
|
|
53
|
+
filesChanged.push("utils.ts");
|
|
54
|
+
}
|
|
55
|
+
const output = [
|
|
56
|
+
"Applied edit to hello.ts",
|
|
57
|
+
`Created ${filesChanged.length} file(s)`,
|
|
58
|
+
"Result: Successfully completed task",
|
|
59
|
+
].join("\n");
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
output,
|
|
63
|
+
filesChanged,
|
|
64
|
+
diff: "", // Diff is captured by worktree manager
|
|
65
|
+
durationMs: Date.now() - startTime,
|
|
66
|
+
// Simulate token usage for testing budget tracking
|
|
67
|
+
usage: {
|
|
68
|
+
inputTokens: 1500,
|
|
69
|
+
outputTokens: 800,
|
|
70
|
+
totalTokens: 2300,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
registerAgent(mockProvider);
|
|
76
|
+
export default mockProvider;
|
|
77
|
+
//# sourceMappingURL=mock.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode agent backend.
|
|
3
|
+
*
|
|
4
|
+
* Three execution modes (in priority order):
|
|
5
|
+
* 1. Server mode: Connects to a managed `opencode serve` instance via HTTP API.
|
|
6
|
+
* Avoids cold-start overhead. Server is shared across threads.
|
|
7
|
+
* 2. Attach mode: Uses `opencode run --attach <url>` to connect to a running server.
|
|
8
|
+
* Falls back here if direct HTTP API call fails.
|
|
9
|
+
* 3. Subprocess mode: Cold-starts `opencode run --format json` per invocation.
|
|
10
|
+
* Always-available fallback.
|
|
11
|
+
*
|
|
12
|
+
* The server pool manages lifecycle of `opencode serve` processes, starting them
|
|
13
|
+
* lazily and shutting them down when the session ends.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentProvider } from "../core/types.js";
|
|
16
|
+
/** Enable server mode for the OpenCode agent. Call before spawning threads. */
|
|
17
|
+
export declare function enableServerMode(): void;
|
|
18
|
+
/** Disable server mode and shut down all managed servers. */
|
|
19
|
+
export declare function disableServerMode(): Promise<void>;
|
|
20
|
+
/** Get the number of active server instances. */
|
|
21
|
+
export declare function getActiveServerCount(): number;
|
|
22
|
+
declare const openCodeProvider: AgentProvider;
|
|
23
|
+
export default openCodeProvider;
|