@robzilla1738/agentswarm 0.3.0 → 0.6.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 (45) hide show
  1. package/README.md +51 -11
  2. package/dist/agent.js +18 -2
  3. package/dist/cli.js +39 -8
  4. package/dist/config.js +62 -6
  5. package/dist/crawltools.js +247 -0
  6. package/dist/deepseek.js +125 -10
  7. package/dist/executor.js +993 -144
  8. package/dist/hub.js +85 -6
  9. package/dist/journal.js +61 -11
  10. package/dist/memory.js +84 -0
  11. package/dist/pdftext.js +211 -0
  12. package/dist/prompts.js +124 -23
  13. package/dist/report.js +289 -0
  14. package/dist/run.js +15 -2
  15. package/dist/sandbox.js +11 -0
  16. package/dist/searchcore.js +244 -0
  17. package/dist/state.js +85 -3
  18. package/dist/tools.js +392 -25
  19. package/dist/util.js +85 -0
  20. package/dist/webtools.js +327 -66
  21. package/package.json +3 -2
  22. package/ui/out/404/index.html +1 -1
  23. package/ui/out/404.html +1 -1
  24. package/ui/out/_next/static/chunks/532-35122e93f37719b9.js +1 -0
  25. package/ui/out/_next/static/chunks/677-721ce1c8b7a6a317.js +1 -0
  26. package/ui/out/_next/static/chunks/app/page-dc9f6744d203e76c.js +1 -0
  27. package/ui/out/_next/static/chunks/app/run/page-3674e103981703a2.js +1 -0
  28. package/ui/out/_next/static/chunks/app/settings/page-41a5d8ba43ecfd4a.js +1 -0
  29. package/ui/out/_next/static/css/d95c2ba395730031.css +3 -0
  30. package/ui/out/fonts/PlanetKosmos.ttf +0 -0
  31. package/ui/out/index.html +1 -1
  32. package/ui/out/index.txt +3 -3
  33. package/ui/out/run/index.html +1 -1
  34. package/ui/out/run/index.txt +3 -3
  35. package/ui/out/settings/index.html +1 -1
  36. package/ui/out/settings/index.txt +3 -3
  37. package/ui/out/_next/static/chunks/383-289a866b246b41cc.js +0 -1
  38. package/ui/out/_next/static/chunks/619-ba102abea3e3d0e4.js +0 -1
  39. package/ui/out/_next/static/chunks/677-7ab85a6f38c3a235.js +0 -1
  40. package/ui/out/_next/static/chunks/app/page-0fda5b8e77d90b84.js +0 -1
  41. package/ui/out/_next/static/chunks/app/run/page-07aab6b1224c3c8c.js +0 -1
  42. package/ui/out/_next/static/chunks/app/settings/page-528482d468d84cfa.js +0 -1
  43. package/ui/out/_next/static/css/e2c82b53bf4519e8.css +0 -3
  44. /package/ui/out/_next/static/{Rm5Fhkds2-wIOnVlME55J → 7_pihFubDGD40BCy2ynlr}/_buildManifest.js +0 -0
  45. /package/ui/out/_next/static/{Rm5Fhkds2-wIOnVlME55J → 7_pihFubDGD40BCy2ynlr}/_ssgManifest.js +0 -0
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveCrawlBackend = resolveCrawlBackend;
4
+ exports.hasScrapeBackend = hasScrapeBackend;
5
+ exports.crawlSite = crawlSite;
6
+ exports.scrapeUrl = scrapeUrl;
7
+ exports.slugForUrl = slugForUrl;
8
+ const util_1 = require("./util");
9
+ const PER_PAGE_CHAR_CAP = 200_000;
10
+ const TOTAL_CHAR_BUDGET = 8_000_000;
11
+ const CRAWL_DEADLINE_MS = 120_000;
12
+ /** auto = first configured: Firecrawl → context.dev → deepcrawl. "off" or nothing configured → null. */
13
+ function resolveCrawlBackend(cfg) {
14
+ if (cfg.crawlBackend === "off")
15
+ return null;
16
+ const configured = {
17
+ firecrawl: Boolean(cfg.firecrawlApiKey),
18
+ contextdev: Boolean(cfg.contextdevApiKey),
19
+ deepcrawl: Boolean(cfg.deepcrawlApiKey && cfg.deepcrawlBaseUrl),
20
+ };
21
+ if (cfg.crawlBackend !== "auto")
22
+ return configured[cfg.crawlBackend] ? cfg.crawlBackend : null;
23
+ for (const id of ["firecrawl", "contextdev", "deepcrawl"]) {
24
+ if (configured[id])
25
+ return id;
26
+ }
27
+ return null;
28
+ }
29
+ /** Backends usable for single-page scrape in fetch_url (the custom deepcrawl contract has no scrape endpoint). */
30
+ function hasScrapeBackend(cfg) {
31
+ const b = resolveCrawlBackend(cfg);
32
+ return b === "firecrawl" || b === "contextdev";
33
+ }
34
+ async function crawlSite(cfg, opts) {
35
+ const backend = resolveCrawlBackend(cfg);
36
+ if (!backend)
37
+ throw new Error("no crawl backend configured — add a Firecrawl/context.dev/deepcrawl key in Settings");
38
+ const warnings = [];
39
+ let pages;
40
+ if (backend === "firecrawl")
41
+ pages = await firecrawlCrawl(cfg, opts, warnings);
42
+ else if (backend === "contextdev")
43
+ pages = await contextdevCrawl(cfg, opts);
44
+ else
45
+ pages = await deepcrawlCrawl(cfg, opts);
46
+ // Normalize: drop empty/binary pages, cap per-page and total size.
47
+ const clean = [];
48
+ let skipped = 0;
49
+ let total = 0;
50
+ for (const p of pages) {
51
+ if (clean.length >= opts.maxPages)
52
+ break;
53
+ const md = (p.markdown || "").trim();
54
+ if (!md || md.includes("\u0000")) {
55
+ skipped++;
56
+ continue;
57
+ }
58
+ const body = (0, util_1.truncateMiddle)(md, PER_PAGE_CHAR_CAP, "chars");
59
+ if (total + body.length > TOTAL_CHAR_BUDGET) {
60
+ warnings.push(`stopped at ${clean.length} pages: total content budget reached`);
61
+ break;
62
+ }
63
+ total += body.length;
64
+ clean.push({ url: p.url, title: p.title, markdown: body });
65
+ }
66
+ if (skipped)
67
+ warnings.push(`${skipped} empty page${skipped > 1 ? "s" : ""} skipped`);
68
+ return { backend, pages: clean, warnings };
69
+ }
70
+ /** Single-page scrape via the configured backend. Throws on failure — callers fall through to their own fetch path. */
71
+ async function scrapeUrl(cfg, url, signal) {
72
+ const backend = resolveCrawlBackend(cfg);
73
+ if (backend === "firecrawl") {
74
+ const data = await callJson("firecrawl", "https://api.firecrawl.dev/v1/scrape", cfg.firecrawlApiKey, { url, formats: ["markdown"] }, 30_000, signal);
75
+ const md = String(data?.data?.markdown ?? "");
76
+ if (!md.trim())
77
+ throw new Error("firecrawl: empty scrape result");
78
+ const title = data?.data?.metadata?.title;
79
+ return title ? `# ${title}\n\n${md}` : md;
80
+ }
81
+ if (backend === "contextdev") {
82
+ const data = await callJson("context.dev", "https://api.context.dev/v1/web/scrape", cfg.contextdevApiKey, { url }, 30_000, signal);
83
+ const md = String(data?.markdown ?? data?.results?.[0]?.markdown ?? "");
84
+ if (!md.trim())
85
+ throw new Error("context.dev: empty scrape result");
86
+ const title = data?.metadata?.title ?? data?.results?.[0]?.metadata?.title;
87
+ return title ? `# ${title}\n\n${md}` : md;
88
+ }
89
+ throw new Error("no scrape-capable crawl backend configured");
90
+ }
91
+ /** "https://docs.foo.com/a/b?x=1" → filesystem-safe { host, slug } with no separators or traversal. */
92
+ function slugForUrl(url) {
93
+ let u;
94
+ try {
95
+ u = new URL(url);
96
+ }
97
+ catch {
98
+ return { host: "site", slug: sanitize(url) || "page" };
99
+ }
100
+ const host = sanitize(u.hostname) || "site";
101
+ const slug = sanitize(u.pathname.replace(/\/+$/, "")) || "index";
102
+ return { host, slug };
103
+ }
104
+ function sanitize(s) {
105
+ return s
106
+ .toLowerCase()
107
+ .replace(/[^a-z0-9._-]+/g, "-")
108
+ .replace(/\.{2,}/g, ".")
109
+ .replace(/-{2,}/g, "-")
110
+ .replace(/^[-.]+|[-.]+$/g, "")
111
+ .slice(0, 120);
112
+ }
113
+ // ---------------------------------------------------------------- backends
114
+ async function firecrawlCrawl(cfg, opts, warnings) {
115
+ const start = await callJson("firecrawl", "https://api.firecrawl.dev/v1/crawl", cfg.firecrawlApiKey, {
116
+ url: opts.url,
117
+ limit: opts.maxPages,
118
+ ...(opts.includePaths?.length ? { includePaths: opts.includePaths } : {}),
119
+ scrapeOptions: { formats: ["markdown"] },
120
+ }, 30_000, opts.signal);
121
+ const jobId = start?.id;
122
+ if (!jobId)
123
+ throw new Error(`firecrawl: crawl did not start (${start?.error || "no job id"})`);
124
+ const pollMs = opts.pollMs ?? 3000;
125
+ const deadline = Date.now() + CRAWL_DEADLINE_MS;
126
+ let last = null;
127
+ for (;;) {
128
+ opts.signal?.throwIfAborted();
129
+ last = await getJson("firecrawl", `https://api.firecrawl.dev/v1/crawl/${jobId}`, cfg.firecrawlApiKey, opts.signal);
130
+ if (last?.status === "completed")
131
+ break;
132
+ if (last?.status === "failed")
133
+ throw new Error(`firecrawl: crawl failed (${last?.error || "unknown error"})`);
134
+ if (Date.now() > deadline) {
135
+ const partial = mapFirecrawlPages(last);
136
+ if (!partial.length)
137
+ throw new Error("firecrawl: crawl still running after 120s with no pages yet — try fewer pages");
138
+ warnings.push(`crawl still running after 120s; returning ${partial.length} partial pages`);
139
+ return partial;
140
+ }
141
+ await sleep(pollMs, opts.signal);
142
+ }
143
+ // Completed: collect pages, following `next` pagination until maxPages.
144
+ const pages = mapFirecrawlPages(last);
145
+ let next = last?.next;
146
+ while (next && pages.length < opts.maxPages) {
147
+ const more = await getJson("firecrawl", String(next), cfg.firecrawlApiKey, opts.signal);
148
+ pages.push(...mapFirecrawlPages(more));
149
+ next = more?.next;
150
+ }
151
+ return pages;
152
+ }
153
+ function mapFirecrawlPages(res) {
154
+ const data = Array.isArray(res?.data) ? res.data : [];
155
+ return data.map((d) => ({
156
+ url: String(d?.metadata?.sourceURL ?? d?.metadata?.url ?? ""),
157
+ title: String(d?.metadata?.title ?? ""),
158
+ markdown: String(d?.markdown ?? ""),
159
+ }));
160
+ }
161
+ async function contextdevCrawl(cfg, opts) {
162
+ const data = await callJson("context.dev", "https://api.context.dev/v1/web/crawl", cfg.contextdevApiKey, {
163
+ url: opts.url,
164
+ max_pages: opts.maxPages,
165
+ ...(opts.includePaths?.length ? { include_paths: opts.includePaths } : {}),
166
+ }, CRAWL_DEADLINE_MS, opts.signal);
167
+ const results = Array.isArray(data?.results) ? data.results : [];
168
+ return results.map((r) => ({
169
+ url: String(r?.metadata?.url ?? r?.url ?? ""),
170
+ title: String(r?.metadata?.title ?? r?.title ?? ""),
171
+ markdown: String(r?.markdown ?? ""),
172
+ }));
173
+ }
174
+ async function deepcrawlCrawl(cfg, opts) {
175
+ const base = cfg.deepcrawlBaseUrl.replace(/\/+$/, "");
176
+ const data = await callJson("deepcrawl", `${base}/crawl`, cfg.deepcrawlApiKey, {
177
+ url: opts.url,
178
+ max_pages: opts.maxPages,
179
+ ...(opts.includePaths?.length ? { include_paths: opts.includePaths } : {}),
180
+ }, CRAWL_DEADLINE_MS, opts.signal);
181
+ // Accept either the context.dev-compatible shape or a flat pages[] list.
182
+ if (Array.isArray(data?.results)) {
183
+ return data.results.map((r) => ({
184
+ url: String(r?.metadata?.url ?? r?.url ?? ""),
185
+ title: String(r?.metadata?.title ?? r?.title ?? ""),
186
+ markdown: String(r?.markdown ?? ""),
187
+ }));
188
+ }
189
+ if (Array.isArray(data?.pages)) {
190
+ return data.pages.map((p) => ({
191
+ url: String(p?.url ?? ""),
192
+ title: String(p?.title ?? ""),
193
+ markdown: String(p?.markdown ?? p?.content ?? ""),
194
+ }));
195
+ }
196
+ throw new Error("deepcrawl: unrecognized response shape (expected results[] or pages[])");
197
+ }
198
+ // ---------------------------------------------------------------- plumbing
199
+ function friendlyHttpError(service, status, body) {
200
+ if (status === 401 || status === 403) {
201
+ return new Error(`${service} API key invalid or unauthorized (HTTP ${status}) — check Settings → Crawl integrations`);
202
+ }
203
+ if (status === 402)
204
+ return new Error(`${service}: quota or credits exhausted (HTTP 402)`);
205
+ if (status === 429)
206
+ return new Error(`${service}: rate limited (HTTP 429) — retry later`);
207
+ return new Error(`${service}: HTTP ${status} ${(0, util_1.truncateMiddle)(body, 300, "chars")}`);
208
+ }
209
+ function mergeSignal(timeoutMs, signal) {
210
+ const t = AbortSignal.timeout(timeoutMs);
211
+ if (!signal)
212
+ return t;
213
+ return typeof AbortSignal.any === "function" ? AbortSignal.any([t, signal]) : signal;
214
+ }
215
+ async function callJson(service, url, key, body, timeoutMs, signal) {
216
+ const res = await fetch(url, {
217
+ method: "POST",
218
+ headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
219
+ body: JSON.stringify(body),
220
+ signal: mergeSignal(timeoutMs, signal),
221
+ });
222
+ if (!res.ok)
223
+ throw friendlyHttpError(service, res.status, await res.text().catch(() => ""));
224
+ return res.json();
225
+ }
226
+ async function getJson(service, url, key, signal) {
227
+ const res = await fetch(url, {
228
+ headers: { authorization: `Bearer ${key}` },
229
+ signal: mergeSignal(30_000, signal),
230
+ });
231
+ if (!res.ok)
232
+ throw friendlyHttpError(service, res.status, await res.text().catch(() => ""));
233
+ return res.json();
234
+ }
235
+ function sleep(ms, signal) {
236
+ return new Promise((resolve, reject) => {
237
+ const t = setTimeout(() => {
238
+ signal?.removeEventListener("abort", onAbort);
239
+ resolve();
240
+ }, ms);
241
+ const onAbort = () => {
242
+ clearTimeout(t);
243
+ reject(new Error("aborted"));
244
+ };
245
+ signal?.addEventListener("abort", onAbort, { once: true });
246
+ });
247
+ }
package/dist/deepseek.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CancelledError = exports.ApiError = void 0;
3
+ exports.CallGate = exports.CancelledError = exports.ApiError = void 0;
4
+ exports.gateFor = gateFor;
4
5
  exports.chat = chat;
5
6
  exports.isFatalAuthError = isFatalAuthError;
6
7
  exports.validateAuth = validateAuth;
@@ -13,10 +14,13 @@ function providerOf(cfg) {
13
14
  class ApiError extends Error {
14
15
  status;
15
16
  body;
16
- constructor(status, body) {
17
+ /** Parsed Retry-After (ms) when the server sent one with a 429. */
18
+ retryAfterMs;
19
+ constructor(status, body, retryAfterMs) {
17
20
  super(`API ${status}: ${body.slice(0, 600)}`);
18
21
  this.status = status;
19
22
  this.body = body;
23
+ this.retryAfterMs = retryAfterMs;
20
24
  }
21
25
  }
22
26
  exports.ApiError = ApiError;
@@ -66,25 +70,135 @@ function sanitizeMessages(messages, thinking) {
66
70
  });
67
71
  }
68
72
  /**
69
- * One streaming chat-completions call with retries. Streams deltas (text and
70
- * reasoning), accumulates tool calls, captures usage from the final chunk.
73
+ * Bounds concurrent streaming calls per provider endpoint so a 100-agent swarm
74
+ * doesn't turn into a 429 storm. AIMD: a 429 halves the ceiling (min 2) and
75
+ * imposes the server's Retry-After as a cool-down; sustained successes recover
76
+ * it additively back toward the configured max. Two-tier FIFO: "high" priority
77
+ * (conductor/orchestration) jumps ahead so queued workers can't starve the
78
+ * brain of the swarm.
79
+ */
80
+ class CallGate {
81
+ max;
82
+ ceiling;
83
+ active = 0;
84
+ high = [];
85
+ low = [];
86
+ successes = 0;
87
+ cooldownUntil = 0;
88
+ onState;
89
+ constructor(max) {
90
+ this.max = Math.max(1, max);
91
+ this.ceiling = this.max;
92
+ }
93
+ state() {
94
+ return { ceiling: this.ceiling, active: this.active, queued: this.high.length + this.low.length };
95
+ }
96
+ configure(max) {
97
+ this.max = Math.max(1, max);
98
+ if (this.ceiling > this.max)
99
+ this.ceiling = this.max;
100
+ this.pump();
101
+ }
102
+ async acquire(priority, signal) {
103
+ const wait = this.cooldownUntil - Date.now();
104
+ if (wait > 0)
105
+ await (0, util_1.sleep)(wait);
106
+ if (signal?.aborted)
107
+ throw new CancelledError();
108
+ if (this.active < this.ceiling) {
109
+ this.active++;
110
+ return;
111
+ }
112
+ await new Promise((resolve, reject) => {
113
+ const queue = priority === "high" ? this.high : this.low;
114
+ const entry = () => {
115
+ signal?.removeEventListener("abort", onAbort);
116
+ resolve();
117
+ };
118
+ const onAbort = () => {
119
+ const i = queue.indexOf(entry);
120
+ if (i >= 0)
121
+ queue.splice(i, 1);
122
+ reject(new CancelledError());
123
+ };
124
+ signal?.addEventListener("abort", onAbort, { once: true });
125
+ queue.push(entry);
126
+ });
127
+ }
128
+ release() {
129
+ this.active = Math.max(0, this.active - 1);
130
+ this.pump();
131
+ }
132
+ reportRateLimit(retryAfterMs) {
133
+ this.ceiling = Math.max(2, Math.floor(this.ceiling / 2));
134
+ this.successes = 0;
135
+ if (retryAfterMs && retryAfterMs > 0) {
136
+ this.cooldownUntil = Math.max(this.cooldownUntil, Date.now() + Math.min(retryAfterMs, 60_000));
137
+ }
138
+ this.onState?.(this.state());
139
+ }
140
+ reportSuccess() {
141
+ if (this.ceiling >= this.max)
142
+ return;
143
+ if (++this.successes >= 10) {
144
+ this.successes = 0;
145
+ this.ceiling++;
146
+ this.onState?.(this.state());
147
+ this.pump();
148
+ }
149
+ }
150
+ pump() {
151
+ while (this.active < this.ceiling) {
152
+ const next = this.high.shift() ?? this.low.shift();
153
+ if (!next)
154
+ break;
155
+ this.active++;
156
+ next();
157
+ }
158
+ }
159
+ }
160
+ exports.CallGate = CallGate;
161
+ const gates = new Map();
162
+ function gateFor(cfg) {
163
+ const key = cfg.baseUrl;
164
+ let g = gates.get(key);
165
+ if (!g) {
166
+ g = new CallGate(cfg.maxConcurrentCalls);
167
+ gates.set(key, g);
168
+ }
169
+ g.configure(cfg.maxConcurrentCalls);
170
+ return g;
171
+ }
172
+ /**
173
+ * One streaming chat-completions call with retries, behind the global gate.
174
+ * The retry backoff sleeps OUTSIDE the gate so a waiting call never holds a
175
+ * concurrency slot.
71
176
  */
72
177
  async function chat(cfg, o) {
178
+ const gate = gateFor(cfg);
73
179
  let lastErr;
74
180
  const attempts = 4;
75
181
  for (let i = 0; i < attempts; i++) {
182
+ await gate.acquire(o.priority ?? "normal", o.signal);
76
183
  try {
77
- return await chatOnce(cfg, o);
184
+ const res = await chatOnce(cfg, o);
185
+ gate.reportSuccess();
186
+ return res;
78
187
  }
79
188
  catch (e) {
80
189
  lastErr = e;
190
+ if (e instanceof ApiError && e.status === 429)
191
+ gate.reportRateLimit(e.retryAfterMs);
81
192
  if (!retryable(e) || i === attempts - 1)
82
193
  throw e;
83
- const backoff = [1500, 5000, 15000][i] ?? 15000;
84
- await (0, util_1.sleep)(backoff + Math.random() * 1000);
85
- if (o.signal?.aborted)
86
- throw new CancelledError();
87
194
  }
195
+ finally {
196
+ gate.release();
197
+ }
198
+ const backoff = [1500, 5000, 15000][i] ?? 15000;
199
+ await (0, util_1.sleep)(backoff + Math.random() * 1000);
200
+ if (o.signal?.aborted)
201
+ throw new CancelledError();
88
202
  }
89
203
  throw lastErr;
90
204
  }
@@ -149,7 +263,8 @@ async function chatOnce(cfg, o) {
149
263
  });
150
264
  if (!res.ok) {
151
265
  const text = await res.text().catch(() => "");
152
- throw new ApiError(res.status, text);
266
+ const ra = Number(res.headers.get("retry-after"));
267
+ throw new ApiError(res.status, text, Number.isFinite(ra) && ra >= 0 ? ra * 1000 : undefined);
153
268
  }
154
269
  if (!res.body)
155
270
  throw new ApiError(0, "empty response body");