@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/hub.js
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
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.startHub = startHub;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const http = __importStar(require("http"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const url_1 = require("url");
|
|
41
|
+
const config_1 = require("./config");
|
|
42
|
+
const control_1 = require("./control");
|
|
43
|
+
const deepseek_1 = require("./deepseek");
|
|
44
|
+
const providers_1 = require("./providers");
|
|
45
|
+
const journal_1 = require("./journal");
|
|
46
|
+
const run_1 = require("./run");
|
|
47
|
+
const sandbox_1 = require("./sandbox");
|
|
48
|
+
const util_1 = require("./util");
|
|
49
|
+
const PKG_VERSION = (() => {
|
|
50
|
+
try {
|
|
51
|
+
// dist/hub.js → ../package.json; npm always ships package.json.
|
|
52
|
+
return String(require("../package.json").version || "0.0.0");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return "0.0.0";
|
|
56
|
+
}
|
|
57
|
+
})();
|
|
58
|
+
const MIME = {
|
|
59
|
+
".html": "text/html; charset=utf-8",
|
|
60
|
+
".js": "text/javascript; charset=utf-8",
|
|
61
|
+
".css": "text/css; charset=utf-8",
|
|
62
|
+
".json": "application/json; charset=utf-8",
|
|
63
|
+
".svg": "image/svg+xml",
|
|
64
|
+
".png": "image/png",
|
|
65
|
+
".jpg": "image/jpeg",
|
|
66
|
+
".ico": "image/x-icon",
|
|
67
|
+
".woff2": "font/woff2",
|
|
68
|
+
".txt": "text/plain; charset=utf-8",
|
|
69
|
+
".md": "text/markdown; charset=utf-8",
|
|
70
|
+
};
|
|
71
|
+
function startHub(opts) {
|
|
72
|
+
const server = http.createServer((req, res) => {
|
|
73
|
+
handle(req, res, opts).catch((e) => {
|
|
74
|
+
sendJson(res, 500, { error: (0, util_1.errMsg)(e) });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
server.listen(opts.port, "127.0.0.1");
|
|
78
|
+
return server;
|
|
79
|
+
}
|
|
80
|
+
async function handle(req, res, opts) {
|
|
81
|
+
const url = new url_1.URL(req.url || "/", `http://localhost:${opts.port}`);
|
|
82
|
+
const p = url.pathname;
|
|
83
|
+
res.setHeader("access-control-allow-origin", "*");
|
|
84
|
+
res.setHeader("access-control-allow-methods", "GET, POST, DELETE, OPTIONS");
|
|
85
|
+
res.setHeader("access-control-allow-headers", "content-type");
|
|
86
|
+
if (req.method === "OPTIONS") {
|
|
87
|
+
res.writeHead(204);
|
|
88
|
+
res.end();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (p.startsWith("/api/")) {
|
|
92
|
+
await api(req, res, url, opts);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
serveStatic(req, res, p, opts.uiDir);
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------- api
|
|
98
|
+
async function api(req, res, url, opts) {
|
|
99
|
+
const cfg = (0, config_1.loadConfig)();
|
|
100
|
+
const p = url.pathname;
|
|
101
|
+
const method = req.method || "GET";
|
|
102
|
+
if (p === "/api/health")
|
|
103
|
+
return sendJson(res, 200, { ok: true, version: PKG_VERSION, apiKey: Boolean(cfg.apiKey) });
|
|
104
|
+
if (p === "/api/config" && method === "GET")
|
|
105
|
+
return sendJson(res, 200, publicConfig(cfg));
|
|
106
|
+
if (p === "/api/config" && method === "POST") {
|
|
107
|
+
const body = await readBody(req);
|
|
108
|
+
const patch = {};
|
|
109
|
+
const errors = [];
|
|
110
|
+
for (const [k, v] of Object.entries(body)) {
|
|
111
|
+
if (k === "providers")
|
|
112
|
+
continue; // handled below
|
|
113
|
+
if (!config_1.SETTABLE_KEYS.includes(k))
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
patch[k] = (0, config_1.coerceConfigValue)(k, v);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
errors.push((0, util_1.errMsg)(e));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Per-provider credentials: { providers: { openai: { apiKey, baseUrl } } }
|
|
123
|
+
if (body.providers && typeof body.providers === "object") {
|
|
124
|
+
const creds = {};
|
|
125
|
+
for (const [id, cred] of Object.entries(body.providers)) {
|
|
126
|
+
if (!(0, providers_1.isProviderId)(id)) {
|
|
127
|
+
errors.push(`unknown provider: ${id}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!cred || typeof cred !== "object")
|
|
131
|
+
continue;
|
|
132
|
+
const c = {};
|
|
133
|
+
if (typeof cred.apiKey === "string")
|
|
134
|
+
c.apiKey = cred.apiKey.trim();
|
|
135
|
+
if (typeof cred.baseUrl === "string")
|
|
136
|
+
c.baseUrl = cred.baseUrl.trim();
|
|
137
|
+
creds[id] = c;
|
|
138
|
+
}
|
|
139
|
+
patch.providers = creds;
|
|
140
|
+
}
|
|
141
|
+
// All-or-nothing: a typo in one field must not half-apply the form.
|
|
142
|
+
if (errors.length)
|
|
143
|
+
return sendJson(res, 400, { error: errors.join("; ") });
|
|
144
|
+
const next = (0, config_1.saveConfig)(patch);
|
|
145
|
+
return sendJson(res, 200, publicConfig(next));
|
|
146
|
+
}
|
|
147
|
+
if (p === "/api/validate" && method === "GET") {
|
|
148
|
+
const r = await (0, deepseek_1.validateAuth)(cfg);
|
|
149
|
+
return sendJson(res, 200, r);
|
|
150
|
+
}
|
|
151
|
+
if (p === "/api/sandbox/test" && method === "POST") {
|
|
152
|
+
const body = await readBody(req);
|
|
153
|
+
const kind = sandbox_1.SANDBOX_KINDS.includes(body.runtime)
|
|
154
|
+
? body.runtime
|
|
155
|
+
: (0, sandbox_1.resolveSandboxKind)(cfg);
|
|
156
|
+
const r = await (0, sandbox_1.testSandbox)(cfg, kind);
|
|
157
|
+
return sendJson(res, 200, { kind, ...r });
|
|
158
|
+
}
|
|
159
|
+
if (p === "/api/models" && method === "GET") {
|
|
160
|
+
try {
|
|
161
|
+
const models = await (0, deepseek_1.listModels)(cfg);
|
|
162
|
+
return sendJson(res, 200, { models });
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return sendJson(res, 200, { models: Object.keys(cfg.pricing), error: (0, util_1.errMsg)(e) });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (p === "/api/runs" && method === "GET") {
|
|
169
|
+
return sendJson(res, 200, { runs: (0, run_1.listRuns)(cfg.pricing) });
|
|
170
|
+
}
|
|
171
|
+
if (p === "/api/runs" && method === "POST") {
|
|
172
|
+
const body = await readBody(req);
|
|
173
|
+
if (!body.mission || typeof body.mission !== "string") {
|
|
174
|
+
return sendJson(res, 400, { error: "mission is required" });
|
|
175
|
+
}
|
|
176
|
+
const provider = providers_1.PROVIDERS[cfg.provider];
|
|
177
|
+
if (!cfg.apiKey && provider.keyRequired) {
|
|
178
|
+
return sendJson(res, 400, { error: `No ${provider.label} API key configured. Set it in Settings first.` });
|
|
179
|
+
}
|
|
180
|
+
const auth = await (0, deepseek_1.validateAuth)(cfg);
|
|
181
|
+
if (auth.status === "invalid") {
|
|
182
|
+
return sendJson(res, 400, {
|
|
183
|
+
error: `${provider.label} key rejected: ${auth.message || "invalid"}. Open Settings and paste a valid key.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const overrides = sanitizeOptions(body.options);
|
|
187
|
+
const sandbox = body.sandbox !== false; // default to sandbox for UI-created runs
|
|
188
|
+
const cwd = sandbox ? process.cwd() : String(body.cwd || process.cwd());
|
|
189
|
+
if (!sandbox) {
|
|
190
|
+
if (!path.isAbsolute(cwd)) {
|
|
191
|
+
return sendJson(res, 400, { error: "cwd must be an absolute path" });
|
|
192
|
+
}
|
|
193
|
+
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
|
|
194
|
+
return sendJson(res, 400, { error: `directory not found: ${cwd}` });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const meta = (0, run_1.createRun)({
|
|
198
|
+
mission: body.mission.trim(),
|
|
199
|
+
cwd,
|
|
200
|
+
sandbox,
|
|
201
|
+
options: (0, run_1.optionsFromConfig)(cfg, overrides),
|
|
202
|
+
});
|
|
203
|
+
(0, run_1.launchDetached)(meta.id, opts.binPath);
|
|
204
|
+
return sendJson(res, 200, { id: meta.id });
|
|
205
|
+
}
|
|
206
|
+
const m = p.match(/^\/api\/runs\/([^/]+)(\/.*)?$/);
|
|
207
|
+
if (m) {
|
|
208
|
+
const id = m[1];
|
|
209
|
+
const sub = m[2] || "";
|
|
210
|
+
const meta = (0, run_1.loadMeta)(id);
|
|
211
|
+
if (!meta)
|
|
212
|
+
return sendJson(res, 404, { error: "run not found" });
|
|
213
|
+
if (sub === "" && method === "GET") {
|
|
214
|
+
const state = (0, run_1.loadRunState)(id, cfg.pricing);
|
|
215
|
+
if (!state)
|
|
216
|
+
return sendJson(res, 404, { error: "run not found" });
|
|
217
|
+
return sendJson(res, 200, snapshot(state, id));
|
|
218
|
+
}
|
|
219
|
+
if (sub === "" && method === "DELETE") {
|
|
220
|
+
try {
|
|
221
|
+
(0, run_1.deleteRun)(id);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
return sendJson(res, 409, { error: (0, util_1.errMsg)(e) });
|
|
225
|
+
}
|
|
226
|
+
return sendJson(res, 200, { ok: true });
|
|
227
|
+
}
|
|
228
|
+
if (sub === "/events" && method === "GET") {
|
|
229
|
+
const since = Number(url.searchParams.get("since") || "0");
|
|
230
|
+
const events = (0, journal_1.readEvents)((0, config_1.runDir)(id)).filter((e) => e.seq > since);
|
|
231
|
+
return sendJson(res, 200, { events, live: (0, run_1.isRunLive)(id) });
|
|
232
|
+
}
|
|
233
|
+
if (sub === "/stream" && method === "GET") {
|
|
234
|
+
return streamEvents(res, id);
|
|
235
|
+
}
|
|
236
|
+
if (sub === "/note" && method === "POST") {
|
|
237
|
+
const body = await readBody(req);
|
|
238
|
+
if (!body.text)
|
|
239
|
+
return sendJson(res, 400, { error: "text required" });
|
|
240
|
+
(0, control_1.appendControl)((0, config_1.runDir)(id), { kind: "note", text: String(body.text) });
|
|
241
|
+
return sendJson(res, 200, { ok: true });
|
|
242
|
+
}
|
|
243
|
+
if (sub === "/cancel" && method === "POST") {
|
|
244
|
+
(0, control_1.appendControl)((0, config_1.runDir)(id), { kind: "cancel" });
|
|
245
|
+
// Belt and braces: SIGINT the engine so the abort lands instantly even
|
|
246
|
+
// mid-API-call. The engine's handler cancels gracefully (still
|
|
247
|
+
// synthesizes a report from completed work).
|
|
248
|
+
const pid = (0, run_1.readPid)(id);
|
|
249
|
+
if (pid) {
|
|
250
|
+
try {
|
|
251
|
+
process.kill(pid, "SIGINT");
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
/* already gone — the control line covers it */
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return sendJson(res, 200, { ok: true });
|
|
258
|
+
}
|
|
259
|
+
if (sub === "/resume" && method === "POST") {
|
|
260
|
+
const info = (0, run_1.resumeInfo)(id);
|
|
261
|
+
if (!info.resumable)
|
|
262
|
+
return sendJson(res, 409, { error: info.reason || "not resumable" });
|
|
263
|
+
(0, run_1.launchDetached)(id, opts.binPath, true);
|
|
264
|
+
return sendJson(res, 200, { ok: true });
|
|
265
|
+
}
|
|
266
|
+
if (sub === "/report" && method === "GET") {
|
|
267
|
+
const file = path.join((0, config_1.runDir)(id), "artifacts", "final-report.md");
|
|
268
|
+
if (!fs.existsSync(file))
|
|
269
|
+
return sendJson(res, 404, { error: "no report yet" });
|
|
270
|
+
res.writeHead(200, { "content-type": "text/markdown; charset=utf-8" });
|
|
271
|
+
res.end(fs.readFileSync(file));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (sub === "/artifacts" && method === "GET") {
|
|
275
|
+
return sendJson(res, 200, { artifacts: listArtifactFiles(id) });
|
|
276
|
+
}
|
|
277
|
+
const art = sub.match(/^\/artifacts\/(.+)$/);
|
|
278
|
+
if (art && method === "GET") {
|
|
279
|
+
const rel = decodeURIComponent(art[1]);
|
|
280
|
+
const base = path.join((0, config_1.runDir)(id), "artifacts");
|
|
281
|
+
const file = path.join(base, rel);
|
|
282
|
+
// pathInside (not startsWith): "artifacts-evil" passes a prefix check.
|
|
283
|
+
if (!(0, util_1.pathInside)(base, file) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) {
|
|
284
|
+
return sendJson(res, 404, { error: "not found" });
|
|
285
|
+
}
|
|
286
|
+
res.writeHead(200, {
|
|
287
|
+
"content-type": MIME[path.extname(file)] || "application/octet-stream",
|
|
288
|
+
});
|
|
289
|
+
fs.createReadStream(file).pipe(res);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
sendJson(res, 404, { error: "not found" });
|
|
294
|
+
}
|
|
295
|
+
function streamEvents(res, id) {
|
|
296
|
+
res.writeHead(200, {
|
|
297
|
+
"content-type": "text/event-stream",
|
|
298
|
+
"cache-control": "no-cache, no-transform",
|
|
299
|
+
connection: "keep-alive",
|
|
300
|
+
"x-accel-buffering": "no",
|
|
301
|
+
});
|
|
302
|
+
res.write(": connected\n\n");
|
|
303
|
+
const file = (0, journal_1.eventsFile)((0, config_1.runDir)(id));
|
|
304
|
+
const state = { offset: 0, carry: "" };
|
|
305
|
+
const flush = () => {
|
|
306
|
+
let evs;
|
|
307
|
+
try {
|
|
308
|
+
evs = (0, journal_1.readNewEvents)(file, state);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
for (const ev of evs) {
|
|
314
|
+
res.write(`data: ${JSON.stringify(ev)}\n\n`);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
// Liveness side-channel: lets the UI distinguish "quiet but alive" from
|
|
318
|
+
// "engine process died without a terminal status".
|
|
319
|
+
const sendLive = () => {
|
|
320
|
+
res.write(`event: live\ndata: ${JSON.stringify({ live: (0, run_1.isRunLive)(id) })}\n\n`);
|
|
321
|
+
};
|
|
322
|
+
// Initial backlog + then poll. (fs.watch is unreliable on some platforms;
|
|
323
|
+
// a 400ms poll over an append-only file is cheap and robust.)
|
|
324
|
+
flush();
|
|
325
|
+
sendLive();
|
|
326
|
+
const poll = setInterval(flush, 400);
|
|
327
|
+
const ping = setInterval(() => res.write(": ping\n\n"), 15000);
|
|
328
|
+
const liveTimer = setInterval(sendLive, 3000);
|
|
329
|
+
const stop = () => {
|
|
330
|
+
clearInterval(poll);
|
|
331
|
+
clearInterval(ping);
|
|
332
|
+
clearInterval(liveTimer);
|
|
333
|
+
};
|
|
334
|
+
res.on("close", stop);
|
|
335
|
+
res.on("error", stop);
|
|
336
|
+
}
|
|
337
|
+
// ---------------------------------------------------------------- helpers
|
|
338
|
+
function publicConfig(cfg) {
|
|
339
|
+
const active = providers_1.PROVIDERS[cfg.provider];
|
|
340
|
+
// Active provider's suggestions first, then anything else with pricing.
|
|
341
|
+
const knownModels = [
|
|
342
|
+
...active.knownModels,
|
|
343
|
+
...Object.keys(cfg.pricing).filter((m) => !active.knownModels.includes(m)),
|
|
344
|
+
];
|
|
345
|
+
return {
|
|
346
|
+
provider: cfg.provider,
|
|
347
|
+
providers: providers_1.PROVIDER_IDS.map((id) => {
|
|
348
|
+
const info = providers_1.PROVIDERS[id];
|
|
349
|
+
const cred = cfg.providers[id] || {};
|
|
350
|
+
const key = id === cfg.provider ? cfg.apiKey : cred.apiKey || "";
|
|
351
|
+
return {
|
|
352
|
+
id,
|
|
353
|
+
label: info.label,
|
|
354
|
+
keyRequired: info.keyRequired,
|
|
355
|
+
keyUrl: info.keyUrl,
|
|
356
|
+
local: Boolean(info.local),
|
|
357
|
+
note: info.note,
|
|
358
|
+
keySet: Boolean(key),
|
|
359
|
+
keyMasked: (0, config_1.maskKey)(key),
|
|
360
|
+
baseUrl: cred.baseUrl || info.baseUrl,
|
|
361
|
+
defaultBaseUrl: info.baseUrl,
|
|
362
|
+
defaultModel: info.defaultModel,
|
|
363
|
+
knownModels: info.knownModels,
|
|
364
|
+
};
|
|
365
|
+
}),
|
|
366
|
+
apiKeySet: Boolean(cfg.apiKey) || !active.keyRequired,
|
|
367
|
+
apiKeyMasked: (0, config_1.maskKey)(cfg.apiKey),
|
|
368
|
+
tinyfishKeySet: Boolean(cfg.tinyfishApiKey),
|
|
369
|
+
tinyfishKeyMasked: (0, config_1.maskKey)(cfg.tinyfishApiKey),
|
|
370
|
+
searchBackend: cfg.searchBackend,
|
|
371
|
+
searchkitCmd: cfg.searchkitCmd,
|
|
372
|
+
sandboxRuntime: cfg.sandboxRuntime,
|
|
373
|
+
sandboxResolved: (0, sandbox_1.resolveSandboxKind)(cfg),
|
|
374
|
+
sandboxImage: cfg.sandboxImage,
|
|
375
|
+
dockerUp: (0, sandbox_1.dockerAvailable)(),
|
|
376
|
+
e2bKeySet: Boolean(cfg.e2bApiKey),
|
|
377
|
+
e2bKeyMasked: (0, config_1.maskKey)(cfg.e2bApiKey),
|
|
378
|
+
e2bTemplate: cfg.e2bTemplate,
|
|
379
|
+
modalConfigured: Boolean(cfg.modalTokenId && cfg.modalTokenSecret),
|
|
380
|
+
vercelConfigured: Boolean(cfg.vercelToken),
|
|
381
|
+
vercelTeamId: cfg.vercelTeamId,
|
|
382
|
+
vercelProjectId: cfg.vercelProjectId,
|
|
383
|
+
baseUrl: cfg.baseUrl,
|
|
384
|
+
model: cfg.model,
|
|
385
|
+
conductorModel: cfg.conductorModel,
|
|
386
|
+
maxWorkers: cfg.maxWorkers,
|
|
387
|
+
maxStepsPerTask: cfg.maxStepsPerTask,
|
|
388
|
+
maxTasks: cfg.maxTasks,
|
|
389
|
+
maxTokensPerRun: cfg.maxTokensPerRun,
|
|
390
|
+
verification: cfg.verification,
|
|
391
|
+
thinking: cfg.thinking,
|
|
392
|
+
reasoningEffort: cfg.reasoningEffort,
|
|
393
|
+
safeMode: cfg.safeMode,
|
|
394
|
+
contextTokenLimit: cfg.contextTokenLimit,
|
|
395
|
+
knownModels,
|
|
396
|
+
pricing: cfg.pricing,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function sanitizeOptions(raw) {
|
|
400
|
+
if (!raw || typeof raw !== "object")
|
|
401
|
+
return {};
|
|
402
|
+
const o = raw;
|
|
403
|
+
const out = {};
|
|
404
|
+
const num = (k, lo, hi) => {
|
|
405
|
+
const v = Number(o[k]);
|
|
406
|
+
if (Number.isFinite(v))
|
|
407
|
+
out[k] = Math.min(hi, Math.max(lo, Math.round(v)));
|
|
408
|
+
};
|
|
409
|
+
if (typeof o.model === "string" && o.model.trim())
|
|
410
|
+
out.model = o.model.trim();
|
|
411
|
+
if (typeof o.conductorModel === "string" && o.conductorModel.trim())
|
|
412
|
+
out.conductorModel = o.conductorModel.trim();
|
|
413
|
+
num("maxWorkers", 1, 32);
|
|
414
|
+
num("maxStepsPerTask", 3, 200);
|
|
415
|
+
num("maxTasks", 1, 1000);
|
|
416
|
+
num("maxTokens", 50_000, 2_000_000_000);
|
|
417
|
+
if (o.verification === "off" || o.verification === "normal" || o.verification === "strict") {
|
|
418
|
+
out.verification = o.verification;
|
|
419
|
+
}
|
|
420
|
+
if (typeof o.thinking === "boolean")
|
|
421
|
+
out.thinking = o.thinking;
|
|
422
|
+
if (o.reasoningEffort === "low" || o.reasoningEffort === "medium" || o.reasoningEffort === "high" || o.reasoningEffort === "max") {
|
|
423
|
+
out.reasoningEffort = o.reasoningEffort;
|
|
424
|
+
}
|
|
425
|
+
if (typeof o.safeMode === "boolean")
|
|
426
|
+
out.safeMode = o.safeMode;
|
|
427
|
+
if (sandbox_1.SANDBOX_KINDS.includes(o.sandboxRuntime)) {
|
|
428
|
+
out.sandboxRuntime = o.sandboxRuntime;
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
function snapshot(state, id) {
|
|
433
|
+
if (!state)
|
|
434
|
+
return { error: "not found" };
|
|
435
|
+
return {
|
|
436
|
+
id,
|
|
437
|
+
meta: state.meta,
|
|
438
|
+
status: state.status,
|
|
439
|
+
statusReason: state.statusReason,
|
|
440
|
+
summary: state.summary(),
|
|
441
|
+
tasks: state.taskList(),
|
|
442
|
+
agents: [...state.agents.values()],
|
|
443
|
+
notes: state.notes,
|
|
444
|
+
conductorLog: state.conductorLog,
|
|
445
|
+
operatorNotes: state.operatorNotes,
|
|
446
|
+
usageByModel: Object.fromEntries(state.usageByModel),
|
|
447
|
+
cost: state.cost,
|
|
448
|
+
finalSummary: state.finalSummary,
|
|
449
|
+
finalReportPath: state.finalReportPath,
|
|
450
|
+
live: (0, run_1.isRunLive)(id),
|
|
451
|
+
lastSeq: state.lastSeq,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function listArtifactFiles(id) {
|
|
455
|
+
const base = path.join((0, config_1.runDir)(id), "artifacts");
|
|
456
|
+
const out = [];
|
|
457
|
+
const walk = (d, prefix) => {
|
|
458
|
+
let entries;
|
|
459
|
+
try {
|
|
460
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
for (const e of entries) {
|
|
466
|
+
if (e.isDirectory())
|
|
467
|
+
walk(path.join(d, e.name), prefix + e.name + "/");
|
|
468
|
+
else {
|
|
469
|
+
let size = 0;
|
|
470
|
+
try {
|
|
471
|
+
size = fs.statSync(path.join(d, e.name)).size;
|
|
472
|
+
}
|
|
473
|
+
catch { /* race */ }
|
|
474
|
+
out.push({ name: prefix + e.name, size });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
walk(base, "");
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
function serveStatic(req, res, p, uiDir) {
|
|
482
|
+
if (!uiDir || !fs.existsSync(uiDir)) {
|
|
483
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
484
|
+
res.end(fallbackPage());
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
let rel = decodeURIComponent(p.replace(/^\/+/, ""));
|
|
488
|
+
if (rel === "")
|
|
489
|
+
rel = "index.html";
|
|
490
|
+
let file = path.join(uiDir, rel);
|
|
491
|
+
if (!(0, util_1.pathInside)(uiDir, file)) {
|
|
492
|
+
res.writeHead(403);
|
|
493
|
+
res.end("forbidden");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// SPA / static-export fallbacks.
|
|
497
|
+
if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) {
|
|
498
|
+
const htmlCandidate = file.replace(/\/$/, "") + ".html";
|
|
499
|
+
if (fs.existsSync(htmlCandidate))
|
|
500
|
+
file = htmlCandidate;
|
|
501
|
+
else if (fs.existsSync(path.join(file, "index.html")))
|
|
502
|
+
file = path.join(file, "index.html");
|
|
503
|
+
else
|
|
504
|
+
file = path.join(uiDir, "index.html");
|
|
505
|
+
}
|
|
506
|
+
if (!fs.existsSync(file)) {
|
|
507
|
+
res.writeHead(404);
|
|
508
|
+
res.end("not found");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
res.writeHead(200, { "content-type": MIME[path.extname(file)] || "application/octet-stream" });
|
|
512
|
+
fs.createReadStream(file).pipe(res);
|
|
513
|
+
}
|
|
514
|
+
function fallbackPage() {
|
|
515
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>agentswarm hub</title>
|
|
516
|
+
<style>body{background:#050505;color:#f2f2f2;font:15px/1.6 -apple-system,system-ui,sans-serif;max-width:680px;margin:8vh auto;padding:0 24px}code{background:#161616;border:1px solid #262626;padding:2px 7px;border-radius:5px;color:#e5e5e5;font-family:ui-monospace,monospace}a{color:#fff}p{color:#a1a1a1}</style>
|
|
517
|
+
</head><body>
|
|
518
|
+
<h1>agentswarm hub is running</h1>
|
|
519
|
+
<p>The API is live, but the web UI hasn't been built yet.</p>
|
|
520
|
+
<p>Build it once (from the repo root):</p>
|
|
521
|
+
<p><code>npm run setup</code></p>
|
|
522
|
+
<p>…then restart <code>swarm serve</code> and reload this page. (Engine already built? <code>npm run build:ui</code> is enough.) Or run the UI in dev mode against this hub:</p>
|
|
523
|
+
<p><code>npm run dev:ui</code> → open <a href="http://localhost:7780">http://localhost:7780</a></p>
|
|
524
|
+
<p>The REST API is available under <code>/api/*</code>.</p>
|
|
525
|
+
</body></html>`;
|
|
526
|
+
}
|
|
527
|
+
// ---------------------------------------------------------------- io
|
|
528
|
+
function sendJson(res, status, body) {
|
|
529
|
+
const s = JSON.stringify(body);
|
|
530
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
531
|
+
res.end(s);
|
|
532
|
+
}
|
|
533
|
+
function readBody(req) {
|
|
534
|
+
return new Promise((resolve) => {
|
|
535
|
+
let data = "";
|
|
536
|
+
req.on("data", (c) => {
|
|
537
|
+
data += c;
|
|
538
|
+
if (data.length > 20_000_000)
|
|
539
|
+
req.destroy();
|
|
540
|
+
});
|
|
541
|
+
req.on("end", () => {
|
|
542
|
+
if (!data.trim())
|
|
543
|
+
return resolve({});
|
|
544
|
+
try {
|
|
545
|
+
resolve(JSON.parse(data));
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
resolve({});
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
req.on("error", () => resolve({}));
|
|
552
|
+
});
|
|
553
|
+
}
|