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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/bin/swarm.mjs +45 -0
  4. package/dist/agents/aider.d.ts +12 -0
  5. package/dist/agents/aider.js +182 -0
  6. package/dist/agents/claude-code.d.ts +9 -0
  7. package/dist/agents/claude-code.js +216 -0
  8. package/dist/agents/codex.d.ts +14 -0
  9. package/dist/agents/codex.js +193 -0
  10. package/dist/agents/direct-llm.d.ts +9 -0
  11. package/dist/agents/direct-llm.js +78 -0
  12. package/dist/agents/mock.d.ts +9 -0
  13. package/dist/agents/mock.js +77 -0
  14. package/dist/agents/opencode.d.ts +23 -0
  15. package/dist/agents/opencode.js +571 -0
  16. package/dist/agents/provider.d.ts +11 -0
  17. package/dist/agents/provider.js +31 -0
  18. package/dist/cli.d.ts +15 -0
  19. package/dist/cli.js +285 -0
  20. package/dist/compression/compressor.d.ts +28 -0
  21. package/dist/compression/compressor.js +265 -0
  22. package/dist/config.d.ts +42 -0
  23. package/dist/config.js +170 -0
  24. package/dist/core/repl.d.ts +69 -0
  25. package/dist/core/repl.js +336 -0
  26. package/dist/core/rlm.d.ts +63 -0
  27. package/dist/core/rlm.js +409 -0
  28. package/dist/core/runtime.py +335 -0
  29. package/dist/core/types.d.ts +131 -0
  30. package/dist/core/types.js +19 -0
  31. package/dist/env.d.ts +10 -0
  32. package/dist/env.js +75 -0
  33. package/dist/interactive-swarm.d.ts +20 -0
  34. package/dist/interactive-swarm.js +1041 -0
  35. package/dist/interactive.d.ts +10 -0
  36. package/dist/interactive.js +1765 -0
  37. package/dist/main.d.ts +15 -0
  38. package/dist/main.js +242 -0
  39. package/dist/mcp/server.d.ts +15 -0
  40. package/dist/mcp/server.js +72 -0
  41. package/dist/mcp/session.d.ts +73 -0
  42. package/dist/mcp/session.js +184 -0
  43. package/dist/mcp/tools.d.ts +15 -0
  44. package/dist/mcp/tools.js +377 -0
  45. package/dist/memory/episodic.d.ts +132 -0
  46. package/dist/memory/episodic.js +390 -0
  47. package/dist/prompts/orchestrator.d.ts +5 -0
  48. package/dist/prompts/orchestrator.js +191 -0
  49. package/dist/routing/model-router.d.ts +130 -0
  50. package/dist/routing/model-router.js +515 -0
  51. package/dist/swarm.d.ts +14 -0
  52. package/dist/swarm.js +557 -0
  53. package/dist/threads/cache.d.ts +58 -0
  54. package/dist/threads/cache.js +198 -0
  55. package/dist/threads/manager.d.ts +85 -0
  56. package/dist/threads/manager.js +659 -0
  57. package/dist/ui/banner.d.ts +14 -0
  58. package/dist/ui/banner.js +42 -0
  59. package/dist/ui/dashboard.d.ts +33 -0
  60. package/dist/ui/dashboard.js +151 -0
  61. package/dist/ui/index.d.ts +10 -0
  62. package/dist/ui/index.js +11 -0
  63. package/dist/ui/log.d.ts +39 -0
  64. package/dist/ui/log.js +126 -0
  65. package/dist/ui/onboarding.d.ts +14 -0
  66. package/dist/ui/onboarding.js +518 -0
  67. package/dist/ui/spinner.d.ts +25 -0
  68. package/dist/ui/spinner.js +113 -0
  69. package/dist/ui/summary.d.ts +18 -0
  70. package/dist/ui/summary.js +113 -0
  71. package/dist/ui/theme.d.ts +63 -0
  72. package/dist/ui/theme.js +97 -0
  73. package/dist/viewer.d.ts +12 -0
  74. package/dist/viewer.js +1284 -0
  75. package/dist/worktree/manager.d.ts +45 -0
  76. package/dist/worktree/manager.js +266 -0
  77. package/dist/worktree/merge.d.ts +28 -0
  78. package/dist/worktree/merge.js +138 -0
  79. 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;