@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
package/README.md CHANGED
@@ -1,8 +1,16 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset=".github/assets/swarm-mark-light.png">
4
+ <img src=".github/assets/swarm-mark-dark.png" alt="agentswarm" width="120">
5
+ </picture>
6
+ </p>
7
+
1
8
  # agentswarm
2
9
 
3
10
  [![npm](https://img.shields.io/npm/v/@robzilla1738/agentswarm)](https://www.npmjs.com/package/@robzilla1738/agentswarm)
4
11
  [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
5
12
  [![node](https://img.shields.io/badge/node-%E2%89%A520.10-brightgreen)](package.json)
13
+ [![support](https://img.shields.io/badge/support-buy%20me%20a%20coffee-yellow)](https://buymeacoffee.com/robcourson)
6
14
 
7
15
  A local agent-swarm orchestrator with a terminal dashboard and a localhost web UI. Works with DeepSeek, OpenAI, Anthropic, xAI, MiniMax, OpenRouter, Ollama, LM Studio, or any OpenAI-compatible endpoint.
8
16
 
@@ -21,7 +29,7 @@ You give it a mission. A conductor model breaks the mission into tasks and hands
21
29
  │ T4 dep │◀─────│ verify │ adversarial verification
22
30
  └────┬─────┘ └─────────┘
23
31
  ┌────▼─────┐
24
- │Synthesize│ → final-report.md + artifacts
32
+ │Synthesize│ → final report (.md + .html) + artifacts
25
33
  └──────────┘
26
34
  ```
27
35
 
@@ -87,23 +95,45 @@ swarm run "Research the best open-source vector DBs in 2026 and write a recommen
87
95
  | `swarm cancel <id>` | Stop a run. It still synthesizes a report from completed work. |
88
96
  | `swarm config [list\|get\|set …]` | Manage `~/.agentswarm/config.json`. |
89
97
  | `swarm models` | List models from the active provider. |
98
+ | `swarm config unset <key>` | Remove a setting (e.g., `swarm config unset firecrawlApiKey`). |
90
99
  | `swarm demo` | Run a self-contained demo mission in an isolated workspace. |
91
100
 
92
101
  Run options (also on the UI launch form under Options): `--workers N` (parallelism), `--tasks N`, `--steps N` (tool steps per task), `--budget N` (token cap), `--model`, `--conductor`, `--verify off|normal|strict`, `--effort low|medium|high|max`, `--no-thinking`, `--sandbox host|docker|e2b|modal|vercel|auto` (shell runtime for this run), `--cwd <path>` (run against a real directory instead of an isolated workspace), `--fg` (foreground in this process).
93
102
 
94
103
  ## How it works
95
104
 
96
- The conductor is a model with three tools: `spawn_tasks`, `wait`, and `finish`. It reads the mission, spawns self-contained tasks (each with an objective, success criteria, a role, optional dependencies, and an optional `verify` flag), then reacts as reports come back.
105
+ The conductor is a model with six tools: `spawn_tasks`, `set_phase`, `update_plan`, `read_report`, `wait`, and `finish`. It reads the mission, spawns self-contained tasks (each with an objective, success criteria, a role, optional dependencies, and an optional `verify` flag), then reacts as reports come back. On long missions it declares phases (`set_phase`) whose goals and exit criteria are pinned into every update — so the plan survives even when old history is trimmed and replaced by a mission ledger (settled tasks, decisions, current phase). On resume, the conductor is re-seeded with this ledger so it picks up where it left off without losing context.
106
+
107
+ Each task becomes an autonomous agent with a tool budget. It works in small steps, posts durable findings to the blackboard (decisions are never trimmed from digests; `search_notes` now supports `kind` filters to find decisions, context, or source links without noise), journals progress checkpoints on long tasks, saves artifacts, and ends by reporting back with structured handoff fields (`key_facts`, `open_questions`, `files_touched`) plus any sources discovered. Sources flow through to the final report as numbered citations — every source is deduplicated, attributed, and linked inline (`[1]`) with a full bibliography at the end. Dependent tasks receive report excerpts plus those fields, and can pull full text with `read_report`.
108
+
109
+ **Search & research.** Web search now includes engine rate-limit cooldowns (on a 429, the engine skips it for a while and re-plans); queries reformulate themselves down to keywords if they get zero results (lifting recall without noise); results are freshness-ranked so recent content bubbles up. For academic queries, agents can use `academic_search` to query arXiv and Crossref directly — no API key needed. Fetches from the web pull plain text via `fetch_url`, which extracts text from PDFs (zero runtime dependencies, zlib only), decodes non-UTF-8 charsets, and flags paywall shells so agents know when they hit a wall.
110
+
111
+ **Scale.** A global AIMD limiter (`maxConcurrentCalls`) bounds concurrent model calls per endpoint — a 429 halves the ceiling, successes recover it, and conductor calls always jump the queue, so a 100-agent swarm degrades gracefully instead of melting down. Settles are debounced before waking the conductor; on big runs the task table collapses settled waves (failures stay itemized) and excess reports become one-liners the conductor can expand with `read_report`. Spawn specs take a `model` tier (`cheap` for scouts, `strong` for leads/verifiers via `cheapModel`/`strongModel` config) and `team:true` to run a task as a full sub-swarm — its own conductor decomposes it in parallel and reports one consolidated result, with all activity journaled under its `teamId`. Context windows are configurable per model via `contextWindows` config; the engine respects each model's actual limit and compacts agent context accordingly.
112
+
113
+ **Worker tools.** The toolbelt gained `grep_files` for structured content search and `replace_in_file` with atomic multi-edit batches — both portable across sandboxes (Docker, E2B, Modal, Vercel).
97
114
 
98
- Each task becomes an autonomous agent with a tool budget. It works in small steps, posts durable findings to the blackboard, saves artifacts, and ends by reporting back. The report is the only thing the conductor sees, which keeps reports specific.
115
+ **Verification & quality.** Tasks pass a mechanical format pre-check (JSON/CSV/HTML structure), then a blind LLM verifier with its own tools. Failed verifications retry with structured feedback (problem/evidence/fix). The verifier gets copies of all dependencies' reports for context. In `--verify strict` mode, the verifier must back verdicts with tool-gathered evidence (not just a pass statement), a completeness critic reviews the whole run for gaps before synthesis, and the final report is checked for faithfulness against the task reports.
116
+
117
+ **Long horizon.** The conductor maintains a living `mission-plan.md` (`update_plan`) pinned into every update and restored on resume; every 25 settled tasks a progress snapshot lands in `artifacts/` so multi-day runs always have a partial deliverable; and real-directory runs leave a memory (`~/.agentswarm/memory/`) of missions, outcomes, and decisions that seeds the next swarm in the same workspace. When tasks fail, the cascade carries the root cause transitively — blocked tasks know why rather than just "dependency did not complete". Failed tasks surface their last failing tool call as diagnostics.
118
+
119
+ **Planning & steering.** The UI now includes a Plan tab showing the living `mission-plan.md`, and the conductor can update it from an agent note (`swarm note <id> "update the plan: ..."`). The budget sparkline in the run dashboard shows at-a-glance how much token budget remains.
99
120
 
100
121
  The scheduler starts a task as soon as its dependencies are done, up to the parallelism cap. Tasks whose dependencies failed are blocked and surfaced to the conductor for re-planning.
101
122
 
102
- When the conductor finishes (or the budget forces it), a synthesizer composes `final-report.md` from every task report.
123
+ When the conductor finishes (or the budget forces it), a synthesizer composes the final deliverable from every task report. Deliverables ship in the format the mission calls for — code, `.csv`/`.json` data, styled documents — alongside `final-report.md` and a self-contained `final-report.html` rendering (open it with `swarm report <id> --open`). The final report includes an inline-cited Sources section and all findings are preserved.
103
124
 
104
125
  The journal is the source of truth. Every run is an append-only `events.jsonl`; the terminal dashboard, the web UI, and `swarm ls` all reduce the same file. That's why runs survive crashes and can be resumed or replayed. Runs live under `~/.agentswarm/runs/<id>/`.
105
126
 
106
- If the engine process dies without writing a terminal status (kill -9, reboot), the hub notices the missing process and shows the run as interrupted instead of leaving it "running" forever.
127
+ If the engine process dies without writing a terminal status (kill -9, reboot), the hub notices the missing process and shows the run as interrupted instead of leaving it "running" forever. `swarm resume <id>` continues it: settled tasks keep their results, and tasks that were mid-flight restart *warm* from their last journaled checkpoint instead of from scratch. SIGTERM flushes the journal synchronously and leaves the run resumable.
128
+
129
+ ## Troubleshooting
130
+
131
+ - **"interrupted — the engine process is no longer running"** — the engine died without a terminal status (kill -9, reboot, crash). Check `~/.agentswarm/runs/<id>/exec.log` for the crash output, then `swarm resume <id>`.
132
+ - **Run ended with "conductor unavailable"** — five consecutive conductor API calls failed (after backoff). Usually a provider outage or a bad model name; check the run's activity log for the underlying error, fix, and resume.
133
+ - **"journal writes are failing"** — the engine could not append to `events.jsonl` (disk full, permissions). The run aborts deliberately rather than doing unrecorded work.
134
+ - **A verified task keeps failing with "Claimed artifact(s) do not exist"** — the worker reported files it never wrote. That's the mechanical pre-verifier doing its job; the retry prompt tells the worker to actually create them.
135
+ - **Docker sandbox fails to start** — confirm `docker info` works as your user, and that the configured `sandboxImage` can be pulled. `swarm sandbox test` checks the configured runtime end-to-end.
136
+ - **Hung or wedged run** — `swarm cancel <id>` aborts in-flight agents within ~1s; sandbox teardown is bounded by a 15s timeout so it can't hang shutdown.
107
137
 
108
138
  ## Architecture
109
139
 
@@ -114,15 +144,24 @@ src/ TypeScript engine (zero runtime deps)
114
144
  sandbox.ts sandbox runtimes: host, docker, E2B, Modal, Vercel
115
145
  agent.ts the agent loop: stream → tool calls → results → repeat, with compaction
116
146
  executor.ts the orchestrator: conductor loop, parallel scheduler, verify, synth, budget
117
- tools.ts worker toolbelt (shell, files, web, blackboard, artifacts) + safety
118
- webtools.ts web search/fetch: SearchKit → TinyFish → DuckDuckGo fallback chain
147
+ tools.ts worker toolbelt (shell, files, web, blackboard, artifacts) + safety + grep/replace
148
+ webtools.ts web search/fetch: SearchKit → TinyFish → DuckDuckGo fallback chain, with cooldowns + reformulation
149
+ searchcore.ts search ranking (freshness boost, academic intent detection) + academic_search (arXiv/Crossref)
150
+ pdftext.ts PDF text extraction (zero deps, zlib only)
151
+ crawltools.ts crawl backend resolver (firecrawl/context.dev/deepcrawl)
119
152
  journal.ts append-only crash-safe event log (single source of truth)
120
- state.ts pure reducer: events → live run state
121
- hub.ts localhost HTTP API + SSE + static UI server
153
+ state.ts pure reducer: events → live run state (with budgetSeries sampling)
154
+ hub.ts localhost HTTP API + SSE + static UI server (CORS locked to localhost)
122
155
  terminal.ts live TTY dashboard
123
156
  cli.ts command-line interface
157
+ memory.ts atomic runId-keyed cross-run memory + interim snapshots
124
158
  ui/ Next.js 15 + Tailwind 4 web app (static-exported, served by the hub)
159
+ components/SideRail Plan tab showing mission-plan.md
160
+ app/run/page.tsx Blackboard search with kind filters + budget sparkline
161
+ app/settings/page.tsx Test buttons for crawl/search backends, key management
125
162
  test/ end-to-end test with a scripted mock model (no API key needed)
163
+ e2e.js 21 phases covering the full pipeline, including citations + force + resume + budget + verify + teams
164
+ unit/*.test.js individual suites for tools, crawl, memory, pdftext, webtools, searchcore, citations
126
165
  ```
127
166
 
128
167
  ## Testing
@@ -135,11 +174,12 @@ Boots a mock model server and drives real missions through the engine, offline,
135
174
 
136
175
  ## Safety notes
137
176
 
138
- - Safe mode is on by default. It blocks obviously destructive shell commands and confines writes to the working directory. `--no-safe` turns it off for a run; only do that when you trust the mission.
177
+ - Safe mode is on by default. It blocks obviously destructive shell commands and confines writes to the working directory, plus symlink escapes to parent directories. `--no-safe` turns it off for a run; only do that when you trust the mission.
178
+ - The hub API (started by `swarm serve`) only accepts requests from localhost origins (`http://localhost:*` and `127.0.0.1:*`). The web UI runs in your browser locally and never phones home.
139
179
  - Runs default to an isolated per-run workspace on this machine. That's a private directory, not a container. Agents still execute with your user's permissions; the engine strips API keys and sandbox credentials from their environment, and safe mode constrains commands and writes. For untrusted or risky missions, use `--sandbox docker` or a cloud runtime.
140
180
  - Use `--cwd <path>` (or Workspace → "A directory on disk" in the UI) to let agents touch a real project. Those runs always execute on the host, since touching your real files is the point.
141
181
  - Costs are estimates based on list prices and the token counts the API reports. Models without pricing data show $0. Set a `--budget` either way.
142
- - Keys are stored in `~/.agentswarm/config.json` (chmod 600) and are only sent to the APIs you configured.
182
+ - Keys are stored in `~/.agentswarm/config.json` (chmod 600) and are only sent to the APIs you configured. Use `swarm config unset <key>` to remove a key, or the Settings UI for test buttons on crawl/search backends.
143
183
 
144
184
  ## Author
145
185
 
package/dist/agent.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runAgent = runAgent;
4
4
  exports.estimateMessages = estimateMessages;
5
+ const config_1 = require("./config");
5
6
  const deepseek_1 = require("./deepseek");
6
7
  const prompts_1 = require("./prompts");
7
8
  const types_1 = require("./types");
@@ -58,7 +59,20 @@ async function runAgent(p) {
58
59
  if (stopReason)
59
60
  break;
60
61
  steps++;
61
- const res = await callModel();
62
+ let res;
63
+ try {
64
+ res = await callModel();
65
+ }
66
+ catch (e) {
67
+ // The chat client already retries 429/5xx; this catches the rest of the
68
+ // transient class (connection resets, DNS blips) once per step so a
69
+ // single network hiccup doesn't burn a whole task attempt.
70
+ if (p.signal.aborted)
71
+ throw e;
72
+ hooks.onLog?.("warn", `${p.agentId}: model call failed (${(0, util_1.errMsg)(e)}); retrying once`);
73
+ await new Promise((r) => setTimeout(r, 1500));
74
+ res = await callModel();
75
+ }
62
76
  hooks.onUsage?.(p.model, res.usage);
63
77
  usage = (0, types_1.addUsage)(usage, res.usage);
64
78
  if (res.toolCalls.length === 0) {
@@ -129,7 +143,7 @@ async function runAgent(p) {
129
143
  messages.push({ role: "tool", tool_call_id: call.id, content: result });
130
144
  }
131
145
  hooks.onTranscript?.(messages);
132
- if (estimateMessages(messages) > cfg.contextTokenLimit) {
146
+ if (estimateMessages(messages) > (0, config_1.contextLimitFor)(cfg, p.model)) {
133
147
  messages = await compact(p, messages);
134
148
  hooks.onTranscript?.(messages);
135
149
  hooks.onLog?.("info", `${p.agentId}: context compacted`);
@@ -211,6 +225,8 @@ async function compact(p, messages) {
211
225
  });
212
226
  p.hooks.onUsage?.(p.model, res.usage);
213
227
  summary = res.content || "(compaction produced no summary)";
228
+ if (res.content)
229
+ p.hooks.onCheckpoint?.(res.content);
214
230
  }
215
231
  catch (e) {
216
232
  // Compaction is best-effort; fall back to hard truncation.
package/dist/cli.js CHANGED
@@ -342,11 +342,24 @@ async function execForeground(cfg, meta, render, resume = false) {
342
342
  };
343
343
  process.on("uncaughtException", onFatal);
344
344
  process.on("unhandledRejection", onFatal);
345
+ // SIGTERM (kill, system shutdown): flush buffered journal lines synchronously
346
+ // and exit WITHOUT a terminal status — the run stays resumable, and viewers
347
+ // show it as interrupted once the pid disappears.
348
+ const onTerm = () => {
349
+ journal.append("log", { level: "warn", msg: "engine received SIGTERM — exiting; resume with: swarm resume " + meta.id });
350
+ journal.flushSync();
351
+ (0, run_1.clearPid)(meta.id);
352
+ if (renderer)
353
+ renderer.stop();
354
+ process.exit(143);
355
+ };
356
+ process.on("SIGTERM", onTerm);
345
357
  try {
346
358
  await executor.run();
347
359
  }
348
360
  finally {
349
361
  process.off("SIGINT", onSig);
362
+ process.off("SIGTERM", onTerm);
350
363
  process.off("uncaughtException", onFatal);
351
364
  process.off("unhandledRejection", onFatal);
352
365
  (0, run_1.clearPid)(meta.id);
@@ -476,8 +489,10 @@ function cmdReport(id, flags) {
476
489
  process.exit(1);
477
490
  }
478
491
  if (flags.open) {
479
- openBrowser("file://" + file);
480
- console.log(file);
492
+ const html = path.join((0, config_1.runDir)(id), "artifacts", "final-report.html");
493
+ const target = fs.existsSync(html) ? html : file;
494
+ openBrowser("file://" + target);
495
+ console.log(target);
481
496
  return;
482
497
  }
483
498
  process.stdout.write(fs.readFileSync(file, "utf8") + "\n");
@@ -509,15 +524,18 @@ async function cmdConfig(rest, flags) {
509
524
  const cfg = (0, config_1.loadConfig)();
510
525
  if (sub === "get" && rest[1]) {
511
526
  const key = rest[1];
512
- const v = key === "apiKey" || key === "tinyfishApiKey" ? (0, config_1.maskKey)(String(cfg[key])) : cfg[key];
527
+ const v = /apikey|token|secret/i.test(key) ? (0, config_1.maskKey)(String(cfg[key] ?? "")) : cfg[key];
513
528
  console.log(typeof v === "object" ? JSON.stringify(v, null, 2) : String(v));
514
529
  return;
515
530
  }
516
531
  console.log(util_1.ansi.bold("config") + util_1.ansi.gray(` (${(0, config_1.configPath)()})`));
517
532
  for (const k of config_1.SETTABLE_KEYS) {
518
533
  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)");
534
+ // Every secret-bearing key prints masked `config list` output ends up
535
+ // in terminal scrollback and pasted bug reports.
536
+ if (/apikey|token|secret/i.test(k)) {
537
+ v = v ? (0, config_1.maskKey)(String(v)) : k === "apiKey" ? util_1.ansi.red("(not set)") : "(not set)";
538
+ }
521
539
  console.log(` ${k.padEnd(18)} ${util_1.ansi.gray(String(v))}`);
522
540
  }
523
541
  return;
@@ -544,11 +562,25 @@ async function cmdConfig(rest, flags) {
544
562
  console.log(util_1.ansi.gray(" verify it works: ") + "swarm models");
545
563
  return;
546
564
  }
565
+ if (sub === "unset") {
566
+ const key = rest[1];
567
+ if (!key)
568
+ throw new Error("usage: swarm config unset <key>");
569
+ // Only string-valued keys can sensibly clear to "" — numbers/enums keep
570
+ // their defaults via `set`.
571
+ const clearable = config_1.SETTABLE_KEYS.filter((k) => /apikey|token|secret|url|model/i.test(k));
572
+ if (!clearable.includes(key)) {
573
+ throw new Error(`not clearable. Clearable keys: ${clearable.join(", ")}`);
574
+ }
575
+ (0, config_1.saveConfig)({ [key]: "" });
576
+ console.log(util_1.ansi.green("✓ ") + `cleared ${key}`);
577
+ return;
578
+ }
547
579
  if (sub === "path") {
548
580
  console.log((0, config_1.configPath)());
549
581
  return;
550
582
  }
551
- throw new Error("usage: swarm config [list|get <key>|set <key> <value>|path]");
583
+ throw new Error("usage: swarm config [list|get <key>|set <key> <value>|unset <key>|path]");
552
584
  }
553
585
  async function cmdModels() {
554
586
  const cfg = (0, config_1.loadConfig)();
@@ -614,7 +646,7 @@ function printFinalLine(id) {
614
646
  console.log("");
615
647
  if (fs.existsSync(reportFile)) {
616
648
  console.log(util_1.ansi.green("✓ final report: ") + reportFile);
617
- console.log(util_1.ansi.gray(" view: ") + `swarm report ${id}`);
649
+ console.log(util_1.ansi.gray(" view: ") + `swarm report ${id}` + util_1.ansi.gray(" · open in browser: ") + `swarm report ${id} --open`);
618
650
  }
619
651
  else {
620
652
  console.log(util_1.ansi.gray(`run ${id} ended without a final report (see: swarm watch ${id})`));
@@ -661,7 +693,6 @@ ${b("RUN OPTIONS")}
661
693
  ${b("FIRST RUN")}
662
694
  swarm config set apiKey <key> # key for the active provider (default: DeepSeek)
663
695
  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
696
  swarm serve --open # open the web UI
666
697
  `);
667
698
  }
package/dist/config.js CHANGED
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.SETTABLE_KEYS = exports.SECRET_ENV_KEYS = exports.DEFAULTS = exports.DEFAULT_PRICING = void 0;
36
+ exports.SETTABLE_KEYS = exports.SECRET_ENV_KEYS = exports.DEFAULTS = exports.DEFAULT_WINDOWS = exports.DEFAULT_PRICING = void 0;
37
+ exports.contextLimitFor = contextLimitFor;
37
38
  exports.home = home;
38
39
  exports.runsDir = runsDir;
39
40
  exports.runDir = runDir;
@@ -63,6 +64,20 @@ exports.DEFAULT_PRICING = {
63
64
  "MiniMax-M2.1": { inMiss: 0.3, inHit: 0.03, out: 1.2 },
64
65
  "MiniMax-M2": { inMiss: 0.3, inHit: 0.03, out: 1.2 },
65
66
  };
67
+ exports.DEFAULT_WINDOWS = {
68
+ // tokens (June 2026 published limits; conservative where ranges exist)
69
+ "deepseek-v4-flash": 128_000,
70
+ "deepseek-v4-pro": 128_000,
71
+ "deepseek-chat": 128_000,
72
+ "deepseek-reasoner": 128_000,
73
+ "gpt-5.1": 272_000,
74
+ "gpt-5.1-mini": 272_000,
75
+ "claude-opus-4-8": 200_000,
76
+ "claude-sonnet-4-6": 200_000,
77
+ "claude-haiku-4-5": 200_000,
78
+ "MiniMax-M2.1": 192_000,
79
+ "MiniMax-M2": 192_000,
80
+ };
66
81
  exports.DEFAULTS = {
67
82
  provider: "deepseek",
68
83
  providers: {},
@@ -70,17 +85,24 @@ exports.DEFAULTS = {
70
85
  baseUrl: providers_1.PROVIDERS.deepseek.baseUrl,
71
86
  model: "deepseek-v4-flash",
72
87
  conductorModel: "deepseek-v4-flash",
88
+ cheapModel: "",
89
+ strongModel: "",
73
90
  maxWorkers: 6,
74
91
  maxStepsPerTask: 30,
75
- maxTasks: 48,
92
+ maxTasks: 200,
76
93
  maxTokensPerRun: 12_000_000,
77
94
  verification: "normal",
95
+ verifyMaxAttempts: 2,
78
96
  thinking: true,
79
97
  reasoningEffort: "high",
80
98
  safeMode: true,
81
99
  tinyfishApiKey: "",
82
100
  searchBackend: "auto",
83
- searchkitCmd: "searchkit",
101
+ firecrawlApiKey: "",
102
+ contextdevApiKey: "",
103
+ deepcrawlApiKey: "",
104
+ deepcrawlBaseUrl: "",
105
+ crawlBackend: "auto",
84
106
  sandboxRuntime: "host",
85
107
  sandboxImage: "node:22-bookworm",
86
108
  e2bApiKey: "",
@@ -90,14 +112,25 @@ exports.DEFAULTS = {
90
112
  vercelToken: "",
91
113
  vercelTeamId: "",
92
114
  vercelProjectId: "",
115
+ maxConcurrentCalls: 16,
93
116
  requestTimeoutMs: 900_000,
94
117
  idleTimeoutMs: 180_000,
95
118
  contextTokenLimit: 120_000,
96
- maxToolResultChars: 12_000,
119
+ maxToolResultChars: 20_000,
97
120
  hubPort: 7777,
98
121
  uiPort: 7780,
99
122
  pricing: exports.DEFAULT_PRICING,
123
+ contextWindows: exports.DEFAULT_WINDOWS,
100
124
  };
125
+ /**
126
+ * Effective compaction/trim threshold for a model: the configured limit,
127
+ * hard-capped by the model's known context window (15% headroom for output
128
+ * and estimation error). Models we don't know keep the configured limit.
129
+ */
130
+ function contextLimitFor(cfg, model) {
131
+ const win = cfg.contextWindows[model];
132
+ return win ? Math.min(cfg.contextTokenLimit, Math.floor(win * 0.85)) : cfg.contextTokenLimit;
133
+ }
101
134
  /**
102
135
  * Env vars that must never leak into agent shell commands when they execute
103
136
  * directly on the host: every provider key env plus the search/sandbox
@@ -109,6 +142,9 @@ exports.SECRET_ENV_KEYS = [
109
142
  .map((p) => p.keyEnv)
110
143
  .filter((k) => Boolean(k)),
111
144
  "TINYFISH_API_KEY",
145
+ "FIRECRAWL_API_KEY",
146
+ "CONTEXT_DEV_API_KEY",
147
+ "DEEPCRAWL_API_KEY",
112
148
  "E2B_API_KEY",
113
149
  "MODAL_TOKEN_ID",
114
150
  "MODAL_TOKEN_SECRET",
@@ -152,6 +188,7 @@ function loadConfig() {
152
188
  provider,
153
189
  providers: file.providers || {},
154
190
  pricing: { ...exports.DEFAULT_PRICING, ...(file.pricing || {}) },
191
+ contextWindows: { ...exports.DEFAULT_WINDOWS, ...(file.contextWindows || {}) },
155
192
  apiKey: cred.apiKey || "",
156
193
  baseUrl: cred.baseUrl || info.baseUrl,
157
194
  };
@@ -160,6 +197,14 @@ function loadConfig() {
160
197
  cfg.apiKey = process.env[info.keyEnv];
161
198
  if (process.env.TINYFISH_API_KEY)
162
199
  cfg.tinyfishApiKey = process.env.TINYFISH_API_KEY;
200
+ if (process.env.FIRECRAWL_API_KEY)
201
+ cfg.firecrawlApiKey = process.env.FIRECRAWL_API_KEY;
202
+ if (process.env.CONTEXT_DEV_API_KEY)
203
+ cfg.contextdevApiKey = process.env.CONTEXT_DEV_API_KEY;
204
+ if (process.env.DEEPCRAWL_API_KEY)
205
+ cfg.deepcrawlApiKey = process.env.DEEPCRAWL_API_KEY;
206
+ if (process.env.DEEPCRAWL_BASE_URL)
207
+ cfg.deepcrawlBaseUrl = process.env.DEEPCRAWL_BASE_URL;
163
208
  if (process.env.E2B_API_KEY)
164
209
  cfg.e2bApiKey = process.env.E2B_API_KEY;
165
210
  if (process.env.MODAL_TOKEN_ID)
@@ -218,17 +263,24 @@ exports.SETTABLE_KEYS = [
218
263
  "baseUrl",
219
264
  "model",
220
265
  "conductorModel",
266
+ "cheapModel",
267
+ "strongModel",
221
268
  "maxWorkers",
222
269
  "maxStepsPerTask",
223
270
  "maxTasks",
224
271
  "maxTokensPerRun",
225
272
  "verification",
273
+ "verifyMaxAttempts",
226
274
  "thinking",
227
275
  "reasoningEffort",
228
276
  "safeMode",
229
277
  "tinyfishApiKey",
230
278
  "searchBackend",
231
- "searchkitCmd",
279
+ "firecrawlApiKey",
280
+ "contextdevApiKey",
281
+ "deepcrawlApiKey",
282
+ "deepcrawlBaseUrl",
283
+ "crawlBackend",
232
284
  "sandboxRuntime",
233
285
  "sandboxImage",
234
286
  "e2bApiKey",
@@ -238,15 +290,18 @@ exports.SETTABLE_KEYS = [
238
290
  "vercelToken",
239
291
  "vercelTeamId",
240
292
  "vercelProjectId",
293
+ "maxConcurrentCalls",
241
294
  "contextTokenLimit",
242
295
  "hubPort",
243
296
  "uiPort",
244
297
  ];
245
298
  /** Allowed ranges for numeric settings (values are clamped, not rejected). */
246
299
  const NUM_RANGES = {
247
- maxWorkers: [1, 32],
300
+ maxWorkers: [1, 128],
301
+ maxConcurrentCalls: [1, 256],
248
302
  maxStepsPerTask: [3, 200],
249
303
  maxTasks: [1, 1000],
304
+ verifyMaxAttempts: [1, 5],
250
305
  maxTokensPerRun: [50_000, 2_000_000_000],
251
306
  contextTokenLimit: [8_000, 900_000],
252
307
  hubPort: [0, 65535],
@@ -256,6 +311,7 @@ const ENUMS = {
256
311
  verification: ["off", "normal", "strict"],
257
312
  reasoningEffort: ["low", "medium", "high", "max"],
258
313
  searchBackend: ["auto", "tinyfish", "ddg"],
314
+ crawlBackend: ["auto", "firecrawl", "contextdev", "deepcrawl", "off"],
259
315
  sandboxRuntime: ["auto", "host", "docker", "e2b", "modal", "vercel"],
260
316
  provider: Object.keys(providers_1.PROVIDERS),
261
317
  };