@robzilla1738/agentswarm 0.2.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 +142 -0
- package/bin/swarm.js +10 -0
- package/dist/agent.js +211 -0
- package/dist/cli.js +667 -0
- package/dist/config.js +289 -0
- package/dist/control.js +96 -0
- package/dist/deepseek.js +321 -0
- package/dist/executor.js +988 -0
- package/dist/hub.js +553 -0
- package/dist/journal.js +152 -0
- package/dist/prompts.js +232 -0
- package/dist/providers.js +151 -0
- package/dist/run.js +309 -0
- package/dist/sandbox.js +505 -0
- package/dist/state.js +230 -0
- package/dist/terminal.js +298 -0
- package/dist/tools.js +491 -0
- package/dist/types.js +26 -0
- package/dist/util.js +209 -0
- package/dist/webtools.js +205 -0
- package/package.json +63 -0
- package/ui/out/404/index.html +1 -0
- package/ui/out/404.html +1 -0
- package/ui/out/_next/static/chunks/255-2aa030c9ba2867e3.js +1 -0
- package/ui/out/_next/static/chunks/383-289a866b246b41cc.js +1 -0
- package/ui/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/ui/out/_next/static/chunks/619-ba102abea3e3d0e4.js +1 -0
- package/ui/out/_next/static/chunks/677-b37981ba0eca75b2.js +1 -0
- package/ui/out/_next/static/chunks/app/_not-found/page-2d0982e372f7be41.js +1 -0
- package/ui/out/_next/static/chunks/app/layout-37ad32c5fdb26f29.js +1 -0
- package/ui/out/_next/static/chunks/app/page-0c9f35bd4aa8e370.js +1 -0
- package/ui/out/_next/static/chunks/app/run/page-13dc41a57e34da71.js +1 -0
- package/ui/out/_next/static/chunks/app/settings/page-a1763be7f6de888c.js +1 -0
- package/ui/out/_next/static/chunks/framework-2c534e0e662575a2.js +1 -0
- package/ui/out/_next/static/chunks/main-app-889ed884f8bc78e3.js +1 -0
- package/ui/out/_next/static/chunks/main-eb90ae3b35d2fd16.js +1 -0
- package/ui/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/ui/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/ui/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/ui/out/_next/static/chunks/webpack-38639c05c96dbeca.js +1 -0
- package/ui/out/_next/static/css/82edaa7a5942f894.css +3 -0
- package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_buildManifest.js +1 -0
- package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_ssgManifest.js +1 -0
- package/ui/out/_next/static/media/0aa834ed78bf6d07-s.woff2 +0 -0
- package/ui/out/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
- package/ui/out/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
- package/ui/out/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
- package/ui/out/_next/static/media/67957d42bae0796d-s.woff2 +0 -0
- package/ui/out/_next/static/media/875ae681bfde4580-s.woff2 +0 -0
- package/ui/out/_next/static/media/886030b0b59bc5a7-s.woff2 +0 -0
- package/ui/out/_next/static/media/939c4f875ee75fbb-s.woff2 +0 -0
- package/ui/out/_next/static/media/bb3ef058b751a6ad-s.p.woff2 +0 -0
- package/ui/out/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
- package/ui/out/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
- package/ui/out/_next/static/media/f911b923c6adde36-s.woff2 +0 -0
- package/ui/out/icon.png +0 -0
- package/ui/out/index.html +1 -0
- package/ui/out/index.txt +22 -0
- package/ui/out/run/index.html +1 -0
- package/ui/out/run/index.txt +22 -0
- package/ui/out/settings/index.html +1 -0
- package/ui/out/settings/index.txt +22 -0
- package/ui/out/swarm-mark.png +0 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.main = main;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
const control_1 = require("./control");
|
|
42
|
+
const deepseek_1 = require("./deepseek");
|
|
43
|
+
const deepseek_2 = require("./deepseek");
|
|
44
|
+
const providers_1 = require("./providers");
|
|
45
|
+
const hub_1 = require("./hub");
|
|
46
|
+
const journal_1 = require("./journal");
|
|
47
|
+
const run_1 = require("./run");
|
|
48
|
+
const executor_1 = require("./executor");
|
|
49
|
+
const sandbox_1 = require("./sandbox");
|
|
50
|
+
const terminal_1 = require("./terminal");
|
|
51
|
+
const util_1 = require("./util");
|
|
52
|
+
const BIN_PATH = path.join(__dirname, "..", "bin", "swarm.js");
|
|
53
|
+
/** Flags that never take a value — they must not swallow the next positional
|
|
54
|
+
* (`swarm run --fg "mission"` would otherwise eat the mission). */
|
|
55
|
+
const BOOL_FLAGS = new Set(["fg", "open", "resume"]);
|
|
56
|
+
function parseArgs(argv) {
|
|
57
|
+
const _ = [];
|
|
58
|
+
const flags = {};
|
|
59
|
+
for (let i = 0; i < argv.length; i++) {
|
|
60
|
+
const a = argv[i];
|
|
61
|
+
if (a.startsWith("--")) {
|
|
62
|
+
const key = a.slice(2);
|
|
63
|
+
const next = argv[i + 1];
|
|
64
|
+
if (key.startsWith("no-")) {
|
|
65
|
+
flags[key.slice(3)] = false;
|
|
66
|
+
}
|
|
67
|
+
else if (!BOOL_FLAGS.has(key) && next !== undefined && !next.startsWith("--")) {
|
|
68
|
+
flags[key] = next;
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
flags[key] = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
_.push(a);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { _, flags };
|
|
80
|
+
}
|
|
81
|
+
async function main() {
|
|
82
|
+
const argv = process.argv.slice(2);
|
|
83
|
+
const { _, flags } = parseArgs(argv);
|
|
84
|
+
const cmd = _[0];
|
|
85
|
+
try {
|
|
86
|
+
switch (cmd) {
|
|
87
|
+
case undefined:
|
|
88
|
+
case "help":
|
|
89
|
+
case "-h":
|
|
90
|
+
case "--help":
|
|
91
|
+
printHelp();
|
|
92
|
+
break;
|
|
93
|
+
case "run":
|
|
94
|
+
await cmdRun(_.slice(1).join(" "), flags);
|
|
95
|
+
break;
|
|
96
|
+
case "_exec":
|
|
97
|
+
await cmdExec(_[1], Boolean(flags.resume));
|
|
98
|
+
break;
|
|
99
|
+
case "resume":
|
|
100
|
+
await cmdResume(_[1], flags);
|
|
101
|
+
break;
|
|
102
|
+
case "sandbox":
|
|
103
|
+
await cmdSandbox(_[1]);
|
|
104
|
+
break;
|
|
105
|
+
case "serve":
|
|
106
|
+
await cmdServe(flags);
|
|
107
|
+
break;
|
|
108
|
+
case "watch":
|
|
109
|
+
await cmdWatch(_[1]);
|
|
110
|
+
break;
|
|
111
|
+
case "ls":
|
|
112
|
+
case "list":
|
|
113
|
+
cmdList();
|
|
114
|
+
break;
|
|
115
|
+
case "report":
|
|
116
|
+
cmdReport(_[1], flags);
|
|
117
|
+
break;
|
|
118
|
+
case "note":
|
|
119
|
+
cmdNote(_[1], _.slice(2).join(" "));
|
|
120
|
+
break;
|
|
121
|
+
case "cancel":
|
|
122
|
+
cmdCancel(_[1]);
|
|
123
|
+
break;
|
|
124
|
+
case "config":
|
|
125
|
+
await cmdConfig(_.slice(1), flags);
|
|
126
|
+
break;
|
|
127
|
+
case "models":
|
|
128
|
+
await cmdModels();
|
|
129
|
+
break;
|
|
130
|
+
case "demo":
|
|
131
|
+
await cmdDemo(flags);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
console.error(util_1.ansi.red(`unknown command: ${cmd}`));
|
|
135
|
+
printHelp();
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
console.error(util_1.ansi.red("error: ") + (0, util_1.errMsg)(e));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---------------------------------------------------------------- run
|
|
145
|
+
function optionOverrides(flags, cfg) {
|
|
146
|
+
const o = {};
|
|
147
|
+
if (flags.workers)
|
|
148
|
+
o.maxWorkers = Number(flags.workers);
|
|
149
|
+
if (flags.steps)
|
|
150
|
+
o.maxStepsPerTask = Number(flags.steps);
|
|
151
|
+
if (flags.tasks)
|
|
152
|
+
o.maxTasks = Number(flags.tasks);
|
|
153
|
+
if (flags.budget)
|
|
154
|
+
o.maxTokens = Number(flags.budget);
|
|
155
|
+
if (typeof flags.model === "string")
|
|
156
|
+
o.model = flags.model;
|
|
157
|
+
if (typeof flags.conductor === "string")
|
|
158
|
+
o.conductorModel = flags.conductor;
|
|
159
|
+
if (typeof flags.verify === "string" && ["off", "normal", "strict"].includes(flags.verify)) {
|
|
160
|
+
o.verification = flags.verify;
|
|
161
|
+
}
|
|
162
|
+
if (flags.thinking === false)
|
|
163
|
+
o.thinking = false;
|
|
164
|
+
if (typeof flags.effort === "string") {
|
|
165
|
+
if (!["low", "medium", "high", "max"].includes(flags.effort)) {
|
|
166
|
+
throw new Error("--effort must be one of: low | medium | high | max");
|
|
167
|
+
}
|
|
168
|
+
o.reasoningEffort = flags.effort;
|
|
169
|
+
}
|
|
170
|
+
if (flags.safe === false)
|
|
171
|
+
o.safeMode = false;
|
|
172
|
+
if (typeof flags.sandbox === "string") {
|
|
173
|
+
const v = flags.sandbox;
|
|
174
|
+
if (v !== "auto" && !sandbox_1.SANDBOX_KINDS.includes(v)) {
|
|
175
|
+
throw new Error(`--sandbox must be one of: ${sandbox_1.SANDBOX_KINDS.join(" | ")} | auto`);
|
|
176
|
+
}
|
|
177
|
+
o.sandboxRuntime = v === "auto" ? (0, sandbox_1.resolveSandboxKind)({ ...cfg, sandboxRuntime: "auto" }) : v;
|
|
178
|
+
}
|
|
179
|
+
return o;
|
|
180
|
+
}
|
|
181
|
+
async function cmdRun(mission, flags) {
|
|
182
|
+
if (!mission.trim()) {
|
|
183
|
+
console.error(util_1.ansi.red('Provide a mission: swarm run "build X / research Y"'));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const cfg = (0, config_1.loadConfig)();
|
|
187
|
+
// Validate flags before any network round-trip so typos fail instantly.
|
|
188
|
+
const overrides = optionOverrides(flags, cfg);
|
|
189
|
+
if (!cfg.apiKey && providers_1.PROVIDERS[cfg.provider].keyRequired) {
|
|
190
|
+
console.error(util_1.ansi.red(`No ${providers_1.PROVIDERS[cfg.provider].label} API key set. `) + "Run: swarm config set apiKey <...>");
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
process.stdout.write(util_1.ansi.gray("validating API key… "));
|
|
194
|
+
const auth = await (0, deepseek_2.validateAuth)(cfg);
|
|
195
|
+
if (auth.status === "invalid") {
|
|
196
|
+
console.error(util_1.ansi.red(`\n✗ ${providers_1.PROVIDERS[cfg.provider].label} key rejected: `) + (auth.message || "invalid key"));
|
|
197
|
+
console.error(util_1.ansi.gray(" Set a valid key: ") + "swarm config set apiKey <...>");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(auth.status === "ok" ? util_1.ansi.green("ok\n") : util_1.ansi.gray("skipped\n"));
|
|
201
|
+
const sandbox = flags.sandbox !== false && !flags.cwd;
|
|
202
|
+
if (typeof flags.sandbox === "string" && flags.cwd) {
|
|
203
|
+
console.log(util_1.ansi.yellow("note: ") + "--cwd runs execute directly on the host — --sandbox is ignored");
|
|
204
|
+
}
|
|
205
|
+
const cwd = typeof flags.cwd === "string" ? flags.cwd : process.cwd();
|
|
206
|
+
const meta = (0, run_1.createRun)({
|
|
207
|
+
mission: mission.trim(),
|
|
208
|
+
cwd,
|
|
209
|
+
sandbox,
|
|
210
|
+
options: (0, run_1.optionsFromConfig)(cfg, overrides),
|
|
211
|
+
});
|
|
212
|
+
if (flags.fg) {
|
|
213
|
+
await execForeground(cfg, meta, true);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Default: launch detached, attach a live dashboard by tailing the journal.
|
|
217
|
+
(0, run_1.launchDetached)(meta.id, BIN_PATH);
|
|
218
|
+
console.log(`${util_1.ansi.cyan("🐝 swarm launched")} ${util_1.ansi.gray(meta.id)} ${util_1.ansi.gray("· workdir:")} ${meta.cwd}`);
|
|
219
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
220
|
+
let detaching = false;
|
|
221
|
+
const onSig = () => {
|
|
222
|
+
detaching = true;
|
|
223
|
+
};
|
|
224
|
+
process.on("SIGINT", onSig);
|
|
225
|
+
await watchRunUntilSignal(meta.id, cfg.pricing, () => detaching);
|
|
226
|
+
process.off("SIGINT", onSig);
|
|
227
|
+
if (detaching && (0, run_1.isRunLive)(meta.id)) {
|
|
228
|
+
console.log("\n" +
|
|
229
|
+
util_1.ansi.yellow("detached") +
|
|
230
|
+
` — run continues in the background.\n reattach: swarm watch ${meta.id}\n steer: swarm note ${meta.id} "..."\n stop: swarm cancel ${meta.id}`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
printFinalLine(meta.id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** `swarm sandbox [test]` — show the resolved runtime; boot + echo + teardown. */
|
|
237
|
+
async function cmdSandbox(sub) {
|
|
238
|
+
const cfg = (0, config_1.loadConfig)();
|
|
239
|
+
const resolved = (0, sandbox_1.resolveSandboxKind)(cfg);
|
|
240
|
+
console.log(`configured: ${cfg.sandboxRuntime} → resolved: ${util_1.ansi.bold(resolved)}`);
|
|
241
|
+
console.log(util_1.ansi.gray(`docker daemon: ${(0, sandbox_1.dockerAvailable)() ? "up" : "not reachable"} · e2b key: ${cfg.e2bApiKey ? "set" : "—"} · modal: ${cfg.modalTokenId ? "set" : "—"} · vercel: ${cfg.vercelToken ? "set" : "—"}`));
|
|
242
|
+
if (resolved === "host") {
|
|
243
|
+
console.log(util_1.ansi.gray("host = the run's isolated workspace on this machine (the default; nothing to install)."));
|
|
244
|
+
console.log(util_1.ansi.gray("for container/cloud isolation: swarm config set sandboxRuntime docker|e2b|modal|vercel|auto"));
|
|
245
|
+
}
|
|
246
|
+
if (sub === "test" || (sub && sandbox_1.SANDBOX_KINDS.includes(sub))) {
|
|
247
|
+
const kind = sandbox_1.SANDBOX_KINDS.includes(sub) ? sub : resolved;
|
|
248
|
+
process.stdout.write(`testing ${kind}… `);
|
|
249
|
+
const r = await (0, sandbox_1.testSandbox)(cfg, kind);
|
|
250
|
+
console.log(r.ok ? util_1.ansi.green("✓ ok ") + util_1.ansi.gray(r.detail) : util_1.ansi.red("✗ ") + r.detail);
|
|
251
|
+
process.exit(r.ok ? 0 : 1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function cmdExec(id, resume = false) {
|
|
255
|
+
if (!id)
|
|
256
|
+
throw new Error("_exec requires a run id");
|
|
257
|
+
const meta = (0, run_1.loadMeta)(id);
|
|
258
|
+
if (!meta)
|
|
259
|
+
throw new Error(`run not found: ${id}`);
|
|
260
|
+
const cfg = (0, config_1.loadConfig)();
|
|
261
|
+
await execForeground(cfg, meta, false, resume);
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
/** Resume an interrupted run: settled tasks keep their results, in-flight tasks re-run. */
|
|
265
|
+
async function cmdResume(id, flags) {
|
|
266
|
+
if (!id)
|
|
267
|
+
throw new Error("usage: swarm resume <run-id>");
|
|
268
|
+
const info = (0, run_1.resumeInfo)(id);
|
|
269
|
+
if (!info.resumable) {
|
|
270
|
+
console.error(util_1.ansi.red("✗ cannot resume: ") + (info.reason || "unknown"));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
const cfg = (0, config_1.loadConfig)();
|
|
274
|
+
const meta = (0, run_1.loadMeta)(id);
|
|
275
|
+
console.log(`${util_1.ansi.cyan("🐝 resuming")} ${util_1.ansi.gray(id)} ${util_1.ansi.gray("· workdir:")} ${meta.cwd}`);
|
|
276
|
+
if (flags.fg) {
|
|
277
|
+
await execForeground(cfg, meta, true, true);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
(0, run_1.launchDetached)(id, BIN_PATH, true);
|
|
281
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
282
|
+
let detaching = false;
|
|
283
|
+
const onSig = () => {
|
|
284
|
+
detaching = true;
|
|
285
|
+
};
|
|
286
|
+
process.on("SIGINT", onSig);
|
|
287
|
+
await watchRunUntilSignal(id, cfg.pricing, () => detaching);
|
|
288
|
+
process.off("SIGINT", onSig);
|
|
289
|
+
if (detaching && (0, run_1.isRunLive)(id)) {
|
|
290
|
+
console.log("\n" + util_1.ansi.yellow("detached") + ` — run continues in the background. Reattach: swarm watch ${id}`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
printFinalLine(id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function execForeground(cfg, meta, render, resume = false) {
|
|
297
|
+
// Reduce the journal BEFORE opening it for appends — the seed must reflect
|
|
298
|
+
// exactly what the dead engine left behind.
|
|
299
|
+
const seed = resume ? (0, run_1.loadRunState)(meta.id, cfg.pricing) : null;
|
|
300
|
+
const journal = new journal_1.Journal((0, config_1.runDir)(meta.id));
|
|
301
|
+
const renderer = render ? new terminal_1.TerminalRenderer(cfg.pricing) : null;
|
|
302
|
+
if (renderer) {
|
|
303
|
+
journal.onEvent = (ev) => renderer.ingest(ev);
|
|
304
|
+
renderer.start();
|
|
305
|
+
}
|
|
306
|
+
(0, run_1.writePid)(meta.id);
|
|
307
|
+
const executor = new executor_1.Executor(cfg, meta, journal);
|
|
308
|
+
if (resume && seed) {
|
|
309
|
+
const resets = seed
|
|
310
|
+
.taskList()
|
|
311
|
+
.filter((t) => t.status === "running" || t.status === "verifying")
|
|
312
|
+
.map((t) => t.id);
|
|
313
|
+
journal.append("run.resumed", { resets });
|
|
314
|
+
executor.seedFromState(seed, resets);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
journal.append("run.created", { meta });
|
|
318
|
+
}
|
|
319
|
+
const onSig = () => {
|
|
320
|
+
if (renderer) {
|
|
321
|
+
renderer.stop();
|
|
322
|
+
console.log(util_1.ansi.yellow("\ncancelling…"));
|
|
323
|
+
}
|
|
324
|
+
executor.cancel();
|
|
325
|
+
};
|
|
326
|
+
process.on("SIGINT", onSig);
|
|
327
|
+
// A crash without a terminal status would leave the run "running" forever
|
|
328
|
+
// in every viewer. Record the failure, flush, and exit non-zero.
|
|
329
|
+
const onFatal = (e) => {
|
|
330
|
+
try {
|
|
331
|
+
journal.append("run.status", { status: "failed", reason: `engine crashed: ${(0, util_1.errMsg)(e)}` });
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
/* nothing left to do */
|
|
335
|
+
}
|
|
336
|
+
journal.flush().finally(() => {
|
|
337
|
+
(0, run_1.clearPid)(meta.id);
|
|
338
|
+
if (renderer)
|
|
339
|
+
renderer.stop();
|
|
340
|
+
process.exit(1);
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
process.on("uncaughtException", onFatal);
|
|
344
|
+
process.on("unhandledRejection", onFatal);
|
|
345
|
+
try {
|
|
346
|
+
await executor.run();
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
process.off("SIGINT", onSig);
|
|
350
|
+
process.off("uncaughtException", onFatal);
|
|
351
|
+
process.off("unhandledRejection", onFatal);
|
|
352
|
+
(0, run_1.clearPid)(meta.id);
|
|
353
|
+
await journal.flush();
|
|
354
|
+
if (renderer)
|
|
355
|
+
renderer.stop();
|
|
356
|
+
}
|
|
357
|
+
if (render)
|
|
358
|
+
printFinalLine(meta.id);
|
|
359
|
+
}
|
|
360
|
+
async function watchRunUntilSignal(id, pricing, shouldStop) {
|
|
361
|
+
const { eventsFile, readNewEvents } = await Promise.resolve().then(() => __importStar(require("./journal")));
|
|
362
|
+
const renderer = new terminal_1.TerminalRenderer(pricing);
|
|
363
|
+
const file = eventsFile((0, config_1.runDir)(id));
|
|
364
|
+
const tail = { offset: 0, carry: "" };
|
|
365
|
+
renderer.start();
|
|
366
|
+
return new Promise((resolve) => {
|
|
367
|
+
const tick = () => {
|
|
368
|
+
if (shouldStop()) {
|
|
369
|
+
renderer.stop();
|
|
370
|
+
return resolve();
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
for (const ev of readNewEvents(file, tail))
|
|
374
|
+
renderer.ingest(ev);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
/* not ready */
|
|
378
|
+
}
|
|
379
|
+
const st = renderer.getState().status;
|
|
380
|
+
if (["done", "failed", "cancelled"].includes(st)) {
|
|
381
|
+
setTimeout(() => {
|
|
382
|
+
try {
|
|
383
|
+
for (const ev of readNewEvents(file, tail))
|
|
384
|
+
renderer.ingest(ev);
|
|
385
|
+
}
|
|
386
|
+
catch { /* ignore */ }
|
|
387
|
+
renderer.stop();
|
|
388
|
+
resolve();
|
|
389
|
+
}, 400);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
setTimeout(tick, 250);
|
|
393
|
+
};
|
|
394
|
+
tick();
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------- serve
|
|
398
|
+
async function cmdServe(flags) {
|
|
399
|
+
const cfg = (0, config_1.loadConfig)();
|
|
400
|
+
const port = Number(flags.port) || (flags.port === "0" ? 0 : cfg.hubPort);
|
|
401
|
+
const uiDir = findUiDir();
|
|
402
|
+
const server = (0, hub_1.startHub)({ port, uiDir, binPath: BIN_PATH });
|
|
403
|
+
// Report the port actually bound (matters for --port 0 / collisions).
|
|
404
|
+
server.on("listening", () => {
|
|
405
|
+
const addr = server.address();
|
|
406
|
+
const bound = typeof addr === "object" && addr ? addr.port : port;
|
|
407
|
+
const url = `http://localhost:${bound}`;
|
|
408
|
+
console.log(`${util_1.ansi.cyan("🐝 agentswarm hub")} ${util_1.ansi.gray("·")} ${util_1.ansi.bold(url)}`);
|
|
409
|
+
console.log(util_1.ansi.gray(` api: ${url}/api`));
|
|
410
|
+
console.log(util_1.ansi.gray(` ui: ${uiDir ? "built ✓ (served here)" : "not built — run: npm run setup (or: npm run build:ui)"}`));
|
|
411
|
+
console.log(util_1.ansi.gray(` api key: ${cfg.apiKey ? (0, config_1.maskKey)(cfg.apiKey) + " ✓" : util_1.ansi.red("not set — open Settings or: swarm config set apiKey <sk-...>")}`));
|
|
412
|
+
console.log(util_1.ansi.gray(" Ctrl-C to stop the hub (background runs keep going).\n"));
|
|
413
|
+
if (flags.open)
|
|
414
|
+
openBrowser(url);
|
|
415
|
+
});
|
|
416
|
+
server.on("error", (e) => {
|
|
417
|
+
console.error(util_1.ansi.red(`hub failed: ${(0, util_1.errMsg)(e)}`));
|
|
418
|
+
process.exit(1);
|
|
419
|
+
});
|
|
420
|
+
await new Promise(() => { }); // run forever
|
|
421
|
+
}
|
|
422
|
+
function findUiDir() {
|
|
423
|
+
const candidates = [
|
|
424
|
+
path.join(__dirname, "..", "ui", "out"),
|
|
425
|
+
path.join(process.cwd(), "ui", "out"),
|
|
426
|
+
];
|
|
427
|
+
for (const c of candidates) {
|
|
428
|
+
if (fs.existsSync(path.join(c, "index.html")))
|
|
429
|
+
return c;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function openBrowser(url) {
|
|
434
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
435
|
+
try {
|
|
436
|
+
(0, child_process_1.spawn)(cmd, [url], { detached: true, stdio: "ignore" }).unref();
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
/* ignore */
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// ---------------------------------------------------------------- watch / list / report
|
|
443
|
+
async function cmdWatch(id) {
|
|
444
|
+
if (!id)
|
|
445
|
+
throw new Error("usage: swarm watch <id>");
|
|
446
|
+
id = resolveId(id);
|
|
447
|
+
if (!(0, run_1.loadMeta)(id))
|
|
448
|
+
throw new Error(`run not found: ${id}`);
|
|
449
|
+
const cfg = (0, config_1.loadConfig)();
|
|
450
|
+
await (0, terminal_1.watchRun)(id, cfg.pricing);
|
|
451
|
+
printFinalLine(id);
|
|
452
|
+
}
|
|
453
|
+
function cmdList() {
|
|
454
|
+
const cfg = (0, config_1.loadConfig)();
|
|
455
|
+
const runs = (0, run_1.listRuns)(cfg.pricing);
|
|
456
|
+
if (!runs.length) {
|
|
457
|
+
console.log(util_1.ansi.gray("no runs yet. start one: ") + 'swarm run "your mission"');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
console.log(util_1.ansi.bold("runs") + util_1.ansi.gray(` (~/.agentswarm/runs)`));
|
|
461
|
+
for (const r of runs) {
|
|
462
|
+
const live = r.pid ? util_1.ansi.cyan(" ●live") : "";
|
|
463
|
+
const st = r.status === "done" ? util_1.ansi.green(r.status) : r.status === "failed" ? util_1.ansi.red(r.status) : util_1.ansi.yellow(r.status);
|
|
464
|
+
const tok = (0, util_1.fmtTokens)(r.usage.promptTokens + r.usage.completionTokens);
|
|
465
|
+
console.log(` ${util_1.ansi.gray(r.id)} ${st.padEnd(14)} ${util_1.ansi.gray(`${r.tasks.done}/${r.tasks.total} tasks`)} ${util_1.ansi.gray(tok + " tok")} ${util_1.ansi.green((0, util_1.fmtMoney)(r.cost))}${live}`);
|
|
466
|
+
console.log(` ${clipLine(r.mission, 90)}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function cmdReport(id, flags) {
|
|
470
|
+
if (!id)
|
|
471
|
+
throw new Error("usage: swarm report <id>");
|
|
472
|
+
id = resolveId(id);
|
|
473
|
+
const file = path.join((0, config_1.runDir)(id), "artifacts", "final-report.md");
|
|
474
|
+
if (!fs.existsSync(file)) {
|
|
475
|
+
console.error(util_1.ansi.yellow("no final report yet for ") + id);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
if (flags.open) {
|
|
479
|
+
openBrowser("file://" + file);
|
|
480
|
+
console.log(file);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
process.stdout.write(fs.readFileSync(file, "utf8") + "\n");
|
|
484
|
+
const arts = path.join((0, config_1.runDir)(id), "artifacts");
|
|
485
|
+
console.log(util_1.ansi.gray(`\nartifacts: ${arts}`));
|
|
486
|
+
}
|
|
487
|
+
function cmdNote(id, text) {
|
|
488
|
+
if (!id || !text)
|
|
489
|
+
throw new Error('usage: swarm note <id> "message"');
|
|
490
|
+
id = resolveId(id);
|
|
491
|
+
if (!(0, run_1.loadMeta)(id))
|
|
492
|
+
throw new Error(`run not found: ${id}`);
|
|
493
|
+
(0, control_1.appendControl)((0, config_1.runDir)(id), { kind: "note", text });
|
|
494
|
+
console.log(util_1.ansi.green("✓ ") + ((0, run_1.isRunLive)(id) ? "note delivered to the conductor" : "note queued (run is not live)"));
|
|
495
|
+
}
|
|
496
|
+
function cmdCancel(id) {
|
|
497
|
+
if (!id)
|
|
498
|
+
throw new Error("usage: swarm cancel <id>");
|
|
499
|
+
id = resolveId(id);
|
|
500
|
+
if (!(0, run_1.loadMeta)(id))
|
|
501
|
+
throw new Error(`run not found: ${id}`);
|
|
502
|
+
(0, control_1.appendControl)((0, config_1.runDir)(id), { kind: "cancel" });
|
|
503
|
+
console.log(util_1.ansi.yellow("⛔ cancel requested for ") + id);
|
|
504
|
+
}
|
|
505
|
+
// ---------------------------------------------------------------- config / models
|
|
506
|
+
async function cmdConfig(rest, flags) {
|
|
507
|
+
const sub = rest[0] || "list";
|
|
508
|
+
if (sub === "list" || sub === "get") {
|
|
509
|
+
const cfg = (0, config_1.loadConfig)();
|
|
510
|
+
if (sub === "get" && rest[1]) {
|
|
511
|
+
const key = rest[1];
|
|
512
|
+
const v = key === "apiKey" || key === "tinyfishApiKey" ? (0, config_1.maskKey)(String(cfg[key])) : cfg[key];
|
|
513
|
+
console.log(typeof v === "object" ? JSON.stringify(v, null, 2) : String(v));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
console.log(util_1.ansi.bold("config") + util_1.ansi.gray(` (${(0, config_1.configPath)()})`));
|
|
517
|
+
for (const k of config_1.SETTABLE_KEYS) {
|
|
518
|
+
let v = cfg[k];
|
|
519
|
+
if (k === "apiKey" || k === "tinyfishApiKey")
|
|
520
|
+
v = v ? (0, config_1.maskKey)(String(v)) : util_1.ansi.red("(not set)");
|
|
521
|
+
console.log(` ${k.padEnd(18)} ${util_1.ansi.gray(String(v))}`);
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (sub === "set") {
|
|
526
|
+
const key = rest[1];
|
|
527
|
+
const value = rest.slice(2).join(" ");
|
|
528
|
+
if (!key || value === "")
|
|
529
|
+
throw new Error("usage: swarm config set <key> <value>");
|
|
530
|
+
if (!config_1.SETTABLE_KEYS.includes(key)) {
|
|
531
|
+
throw new Error(`unknown/settable keys: ${config_1.SETTABLE_KEYS.join(", ")}`);
|
|
532
|
+
}
|
|
533
|
+
const coerced = (0, config_1.coerceConfigValue)(key, value);
|
|
534
|
+
if (key === "apiKey") {
|
|
535
|
+
const k = String(coerced);
|
|
536
|
+
if (k.includes("...") || k.includes("…") || k.length < 20) {
|
|
537
|
+
throw new Error(`that doesn't look like a real API key (got ${k.length} chars). ` +
|
|
538
|
+
`Paste the full key from your provider's console.`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
(0, config_1.saveConfig)({ [key]: coerced });
|
|
542
|
+
console.log(util_1.ansi.green("✓ ") + `set ${key}`);
|
|
543
|
+
if (key === "apiKey")
|
|
544
|
+
console.log(util_1.ansi.gray(" verify it works: ") + "swarm models");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (sub === "path") {
|
|
548
|
+
console.log((0, config_1.configPath)());
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
throw new Error("usage: swarm config [list|get <key>|set <key> <value>|path]");
|
|
552
|
+
}
|
|
553
|
+
async function cmdModels() {
|
|
554
|
+
const cfg = (0, config_1.loadConfig)();
|
|
555
|
+
if (!cfg.apiKey) {
|
|
556
|
+
console.log(util_1.ansi.gray("known (priced) models:"));
|
|
557
|
+
for (const m of Object.keys(cfg.pricing))
|
|
558
|
+
console.log(" " + m);
|
|
559
|
+
console.log(util_1.ansi.gray("\nset an API key to list live models: swarm config set apiKey <sk-...>"));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
const models = await (0, deepseek_1.listModels)(cfg);
|
|
564
|
+
console.log(util_1.ansi.bold("available models"));
|
|
565
|
+
for (const m of models) {
|
|
566
|
+
const priced = cfg.pricing[m] ? util_1.ansi.green(" priced") : "";
|
|
567
|
+
console.log(" " + m + priced);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (e) {
|
|
571
|
+
console.error(util_1.ansi.red("could not list models: ") + (0, util_1.errMsg)(e));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ---------------------------------------------------------------- demo
|
|
575
|
+
async function cmdDemo(flags) {
|
|
576
|
+
const cfg = (0, config_1.loadConfig)();
|
|
577
|
+
if (!cfg.apiKey && providers_1.PROVIDERS[cfg.provider].keyRequired) {
|
|
578
|
+
console.error(util_1.ansi.red("Demo needs an API key for the active provider. ") + "Run: swarm config set apiKey <...>");
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
const mission = typeof flags.mission === "string"
|
|
582
|
+
? flags.mission
|
|
583
|
+
: "Research the current state of open-weight agent-swarm systems (Kimi K2.6, others), then produce a concise comparison report with a recommendation for a developer wanting long-horizon autonomy on a budget. Save the report as comparison.md.";
|
|
584
|
+
const meta = (0, run_1.createRun)({
|
|
585
|
+
mission,
|
|
586
|
+
cwd: process.cwd(),
|
|
587
|
+
sandbox: true,
|
|
588
|
+
options: (0, run_1.optionsFromConfig)(cfg, { maxWorkers: 4, maxTasks: 12, ...optionOverrides(flags, cfg) }),
|
|
589
|
+
});
|
|
590
|
+
console.log(util_1.ansi.cyan("running demo mission in an isolated workspace…\n"));
|
|
591
|
+
await execForeground(cfg, meta, true);
|
|
592
|
+
}
|
|
593
|
+
// ---------------------------------------------------------------- shared
|
|
594
|
+
function resolveId(idOrPrefix) {
|
|
595
|
+
if ((0, run_1.loadMeta)(idOrPrefix))
|
|
596
|
+
return idOrPrefix;
|
|
597
|
+
// allow short prefixes
|
|
598
|
+
try {
|
|
599
|
+
const dir = path.join((0, config_1.runDir)(idOrPrefix), "..");
|
|
600
|
+
const ids = fs.readdirSync(dir).filter((d) => d.startsWith(idOrPrefix));
|
|
601
|
+
if (ids.length === 1)
|
|
602
|
+
return ids[0];
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
/* ignore */
|
|
606
|
+
}
|
|
607
|
+
return idOrPrefix;
|
|
608
|
+
}
|
|
609
|
+
function printFinalLine(id) {
|
|
610
|
+
const meta = (0, run_1.loadMeta)(id);
|
|
611
|
+
if (!meta)
|
|
612
|
+
return;
|
|
613
|
+
const reportFile = path.join((0, config_1.runDir)(id), "artifacts", "final-report.md");
|
|
614
|
+
console.log("");
|
|
615
|
+
if (fs.existsSync(reportFile)) {
|
|
616
|
+
console.log(util_1.ansi.green("✓ final report: ") + reportFile);
|
|
617
|
+
console.log(util_1.ansi.gray(" view: ") + `swarm report ${id}`);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
console.log(util_1.ansi.gray(`run ${id} ended without a final report (see: swarm watch ${id})`));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function clipLine(s, n) {
|
|
624
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
625
|
+
return util_1.ansi.gray(t.length > n ? t.slice(0, n - 1) + "…" : t);
|
|
626
|
+
}
|
|
627
|
+
function printHelp() {
|
|
628
|
+
const b = util_1.ansi.bold;
|
|
629
|
+
console.log(`${b("agentswarm")} — a local agent swarm for long-horizon work (DeepSeek, OpenAI, Anthropic, Grok, MiniMax, OpenRouter, Ollama, LM Studio)
|
|
630
|
+
|
|
631
|
+
${b("USAGE")}
|
|
632
|
+
swarm run "<mission>" [options] decompose & execute a mission with a parallel swarm
|
|
633
|
+
swarm serve [--port 7777] [--open] start the mission-control web UI + API
|
|
634
|
+
swarm watch <id> attach a live dashboard to a run
|
|
635
|
+
swarm resume <id> [--fg] resume an interrupted run (done tasks keep their results)
|
|
636
|
+
swarm ls list runs
|
|
637
|
+
swarm report <id> [--open] print (or open) a run's final report
|
|
638
|
+
swarm note <id> "<text>" steer a live run (the conductor reads it)
|
|
639
|
+
swarm cancel <id> stop a run gracefully (still synthesizes)
|
|
640
|
+
swarm config [list|get|set ...] manage config (~/.agentswarm/config.json)
|
|
641
|
+
swarm sandbox [test|<runtime>] show / smoke-test the shell runtime (host, docker, e2b, modal, vercel)
|
|
642
|
+
swarm models list models from the active provider
|
|
643
|
+
swarm demo run a self-contained demo mission
|
|
644
|
+
|
|
645
|
+
${b("RUN OPTIONS")}
|
|
646
|
+
--workers N max parallel agents (default ${(0, config_1.loadConfig)().maxWorkers})
|
|
647
|
+
--steps N max tool steps per task (default ${(0, config_1.loadConfig)().maxStepsPerTask})
|
|
648
|
+
--tasks N max total tasks (default ${(0, config_1.loadConfig)().maxTasks})
|
|
649
|
+
--budget N token budget for the whole run (default ${(0, util_1.fmtTokens)((0, config_1.loadConfig)().maxTokensPerRun)})
|
|
650
|
+
--model X worker model (default ${(0, config_1.loadConfig)().model})
|
|
651
|
+
--conductor X conductor model (default ${(0, config_1.loadConfig)().conductorModel})
|
|
652
|
+
--verify off|normal|strict adversarial verification (default ${(0, config_1.loadConfig)().verification})
|
|
653
|
+
--effort low|medium|high|max reasoning effort (default ${(0, config_1.loadConfig)().reasoningEffort})
|
|
654
|
+
--no-thinking disable thinking mode
|
|
655
|
+
--no-safe disable command/path safety guards (careful)
|
|
656
|
+
--sandbox X shell runtime for this run: host | docker | e2b | modal | vercel | auto
|
|
657
|
+
(default ${(0, config_1.loadConfig)().sandboxRuntime}; host = isolated workspace, no install needed)
|
|
658
|
+
--cwd <path> run against a real directory (default: isolated workspace)
|
|
659
|
+
--fg run in the foreground in this process (Ctrl-C cancels)
|
|
660
|
+
|
|
661
|
+
${b("FIRST RUN")}
|
|
662
|
+
swarm config set apiKey <key> # key for the active provider (default: DeepSeek)
|
|
663
|
+
swarm config set provider <id> # deepseek | openai | anthropic | xai | minimax | openrouter | ollama | lmstudio | custom
|
|
664
|
+
pip install searchkit # optional: local, citable web search for agents
|
|
665
|
+
swarm serve --open # open the web UI
|
|
666
|
+
`);
|
|
667
|
+
}
|