@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,244 @@
1
+ "use strict";
2
+ /**
3
+ * Native search intelligence, ported from the author's SearchKit project
4
+ * (~/Code/searchkit): canonical-URL dedup, source classification, quality
5
+ * ranking, and quotable-passage extraction. Pure string/URL processing —
6
+ * no external services or processes; the engines that feed it live in
7
+ * webtools.ts.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.queryTerms = queryTerms;
11
+ exports.expandQueries = expandQueries;
12
+ exports.reformulate = reformulate;
13
+ exports.looksAcademic = looksAcademic;
14
+ exports.canonicalizeUrl = canonicalizeUrl;
15
+ exports.classifySource = classifySource;
16
+ exports.freshnessBoost = freshnessBoost;
17
+ exports.detectDate = detectDate;
18
+ exports.selectPassages = selectPassages;
19
+ exports.scorePage = scorePage;
20
+ exports.resultQualityScore = resultQualityScore;
21
+ exports.mergeCandidates = mergeCandidates;
22
+ exports.passageBonus = passageBonus;
23
+ exports.rankBonus = rankBonus;
24
+ /** Lowercased alphanumeric query tokens, stopword-ish short tokens dropped. */
25
+ function queryTerms(query) {
26
+ const m = query.toLowerCase().match(/[a-z0-9]+/g) || [];
27
+ return [...new Set(m.filter((t) => t.length > 2))];
28
+ }
29
+ /**
30
+ * Generate a few complementary query phrasings to widen source coverage in
31
+ * one search: the original, a stopword-stripped keyword core (different
32
+ * recall on most engines), and a docs/guide angle for question-shaped
33
+ * queries. Deterministic and low-noise — capped at `max`.
34
+ */
35
+ function expandQueries(query, max = 3) {
36
+ const base = query.trim();
37
+ const out = [base];
38
+ const terms = queryTerms(query);
39
+ const core = terms.join(" ");
40
+ if (core && core.length > 4 && core !== base.toLowerCase())
41
+ out.push(core);
42
+ if (/^(how|what|why|when|which|where|who|is|are|can|does|do)\b/i.test(base) && terms.length) {
43
+ out.push(`${core} guide`);
44
+ }
45
+ const seen = new Set();
46
+ return out.map((q) => q.trim()).filter((q) => q && !seen.has(q.toLowerCase()) && seen.add(q.toLowerCase())).slice(0, max);
47
+ }
48
+ /**
49
+ * Fallback phrasing when a query returns nothing: strip quotes and search
50
+ * operators down to the top keyword terms. Returns "" when no useful
51
+ * simplification exists.
52
+ */
53
+ function reformulate(query) {
54
+ const cleaned = query
55
+ .replace(/["'""'']/g, " ")
56
+ .replace(/\b(site|intitle|inurl|filetype):\S+/gi, " ");
57
+ const alt = queryTerms(cleaned).slice(0, 6).join(" ");
58
+ return alt && alt !== query.toLowerCase().trim() ? alt : "";
59
+ }
60
+ /** Queries that smell academic trigger the scholarly engines in deep mode. */
61
+ function looksAcademic(query) {
62
+ return /\b(paper|papers|study|studies|research|arxiv|doi|journal|peer.?review(ed)?|preprint|dataset|benchmark|survey|meta.?analysis|citations?|et al)\b/i.test(query);
63
+ }
64
+ const TRACKING_KEYS = new Set(["fbclid", "gclid", "mc_cid", "mc_eid"]);
65
+ /** Stable canonical form for dedup: strip tracking params, www, trailing slash; sort the query. */
66
+ function canonicalizeUrl(url) {
67
+ let u;
68
+ try {
69
+ u = new URL(url);
70
+ }
71
+ catch {
72
+ return url.toLowerCase();
73
+ }
74
+ const pairs = [...u.searchParams.entries()].filter(([k]) => !TRACKING_KEYS.has(k.toLowerCase()) && !k.toLowerCase().startsWith("utm_"));
75
+ pairs.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
76
+ const query = pairs.length ? "?" + pairs.map(([k, v]) => `${k}=${v}`).join("&") : "";
77
+ const host = u.hostname.toLowerCase().replace(/^www\./, "");
78
+ let path = u.pathname || "/";
79
+ if (path !== "/")
80
+ path = path.replace(/\/+$/, "");
81
+ return `${u.protocol.toLowerCase()}//${host}${path}${query}`;
82
+ }
83
+ const ACADEMIC_HOSTS = [
84
+ "arxiv.org",
85
+ "doi.org",
86
+ "semanticscholar.org",
87
+ "ncbi.nlm.nih.gov",
88
+ "nature.com",
89
+ "sciencedirect.com",
90
+ "springer.com",
91
+ "link.springer.com",
92
+ "scholar.google.com",
93
+ "acm.org",
94
+ "ieee.org",
95
+ ];
96
+ function classifySource(domain) {
97
+ const d = domain.toLowerCase();
98
+ if (d.endsWith(".gov") || d.endsWith(".mil"))
99
+ return "government";
100
+ if (d.endsWith(".edu"))
101
+ return "academic";
102
+ if (ACADEMIC_HOSTS.some((h) => d === h || d.endsWith("." + h)))
103
+ return "academic";
104
+ if (["twitter.com", "x.com", "reddit.com", "facebook.com"].some((s) => d.includes(s)))
105
+ return "social";
106
+ if (d.includes("news") || d.includes("reuters.com") || d.includes("apnews.com") || d.includes("bbc."))
107
+ return "news";
108
+ return "secondary";
109
+ }
110
+ /** Recency boost from an ISO date or bare year: +3 <1y, +2 <2y, +1 <5y, 0 older/undated. */
111
+ function freshnessBoost(date, now = Date.now()) {
112
+ if (!date)
113
+ return 0;
114
+ const m = /^(\d{4})(?:-(\d{1,2})(?:-(\d{1,2}))?)?/.exec(date.trim());
115
+ if (!m)
116
+ return 0;
117
+ const t = Date.UTC(Number(m[1]), m[2] ? Number(m[2]) - 1 : 6, m[3] ? Number(m[3]) : 15);
118
+ const years = (now - t) / 31_557_600_000;
119
+ if (years < 1)
120
+ return 3;
121
+ if (years < 2)
122
+ return 2;
123
+ if (years < 5)
124
+ return 1;
125
+ return 0;
126
+ }
127
+ /** ISO date if present, else a bare year. */
128
+ function detectDate(text) {
129
+ const iso = /\b(20\d{2}-\d{2}-\d{2})\b/.exec(text);
130
+ if (iso)
131
+ return iso[1];
132
+ const year = /\b(20\d{2})\b/.exec(text);
133
+ return year?.[1];
134
+ }
135
+ const WINDOW_WORDS = 60;
136
+ const STRIDE = 30;
137
+ /**
138
+ * Quotable passages: slide a 60-word window (stride 30) over the text and
139
+ * score each window by the fraction of query terms it contains. Deterministic
140
+ * lexical matching — no embeddings. Falls back to the lead window so a hit
141
+ * always carries something quotable.
142
+ */
143
+ function selectPassages(text, query, maxPassages = 3) {
144
+ const body = text.trim();
145
+ if (!body)
146
+ return [];
147
+ const terms = queryTerms(query);
148
+ const tokens = [...body.matchAll(/\S+/g)];
149
+ if (!tokens.length)
150
+ return [];
151
+ const windows = [];
152
+ for (let i = 0; i < tokens.length; i += STRIDE) {
153
+ const slice = tokens.slice(i, i + WINDOW_WORDS);
154
+ const start = slice[0].index;
155
+ const last = slice[slice.length - 1];
156
+ const chunk = body.slice(start, last.index + last[0].length);
157
+ windows.push({ text: chunk, score: scoreChunk(chunk, terms) });
158
+ if (i + WINDOW_WORDS >= tokens.length)
159
+ break;
160
+ }
161
+ const scored = windows.filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
162
+ const picked = (scored.length ? scored : windows).slice(0, maxPassages);
163
+ return picked.map((p) => ({ text: p.text, score: Math.round(p.score * 10_000) / 10_000 }));
164
+ }
165
+ function scoreChunk(chunk, terms) {
166
+ if (!terms.length)
167
+ return 0;
168
+ const lowered = chunk.toLowerCase();
169
+ let hits = 0;
170
+ for (const t of terms)
171
+ if (lowered.includes(t))
172
+ hits++;
173
+ return hits / terms.length;
174
+ }
175
+ /** Content-quality score for a fetched page (deep mode re-ranking). */
176
+ function scorePage(page, terms) {
177
+ let score = 0;
178
+ const domain = page.domain.toLowerCase();
179
+ const url = page.url.toLowerCase();
180
+ const title = page.title.toLowerCase();
181
+ const type = classifySource(domain);
182
+ if (type === "primary" || type === "government" || type === "academic")
183
+ score += 5;
184
+ if (domain.includes("docs") || url.includes("docs") || title.includes("documentation"))
185
+ score += 5;
186
+ if (domain === "github.com" || domain === "gitlab.com")
187
+ score += 4;
188
+ if (["pypi.org", "npmjs.com", "rubygems.org"].includes(domain))
189
+ score -= 2;
190
+ score += freshnessBoost(page.date);
191
+ const lowered = page.text.toLowerCase();
192
+ for (const t of terms)
193
+ if (lowered.includes(t))
194
+ score += 1;
195
+ score += Math.min(page.text.length / 4000, 1);
196
+ return score;
197
+ }
198
+ const LOW_VALUE_SNIPPET = ["copy a direct link", "file metadata"];
199
+ /** Pre-fetch quality score for one engine result (snippet-level signals only). */
200
+ function resultQualityScore(c) {
201
+ const url = c.url.toLowerCase();
202
+ const title = c.title.toLowerCase();
203
+ const snippet = c.snippet.toLowerCase();
204
+ let score = Math.max(0, 20 - c.rank);
205
+ if (title.includes("official") || snippet.includes("official"))
206
+ score += 4;
207
+ if (title.includes("documentation") || snippet.includes("documentation") || url.includes("docs"))
208
+ score += 4;
209
+ if (url.includes("github.com") || url.includes("gitlab.com"))
210
+ score += 3;
211
+ if (c.engine === "arxiv" || c.engine === "crossref")
212
+ score += 3;
213
+ score += Math.min(2, freshnessBoost(c.date));
214
+ if (LOW_VALUE_SNIPPET.some((t) => snippet.includes(t)))
215
+ score -= 10;
216
+ return score;
217
+ }
218
+ /**
219
+ * Merge results from several engines: quality-rank, dedupe by canonical URL
220
+ * (first/best occurrence wins), cap at maxResults.
221
+ */
222
+ function mergeCandidates(candidates, maxResults) {
223
+ const ranked = [...candidates].sort((a, b) => resultQualityScore(b) - resultQualityScore(a));
224
+ const seen = new Set();
225
+ const out = [];
226
+ for (const c of ranked) {
227
+ const key = canonicalizeUrl(c.url);
228
+ if (seen.has(key))
229
+ continue;
230
+ seen.add(key);
231
+ out.push(c);
232
+ if (out.length >= maxResults)
233
+ break;
234
+ }
235
+ return out;
236
+ }
237
+ /** Best-passage bonus used in deep-mode composite scoring. */
238
+ function passageBonus(passages) {
239
+ return passages.length ? passages[0].score * 3 : 0;
240
+ }
241
+ /** Engine-rank decay bonus used in composite scoring. */
242
+ function rankBonus(rank, ceiling) {
243
+ return Math.max(0, ceiling - rank) * 0.2;
244
+ }
package/dist/state.js CHANGED
@@ -14,11 +14,15 @@ class RunState {
14
14
  taskOrder = [];
15
15
  agents = new Map();
16
16
  notes = [];
17
+ phases = [];
18
+ planExcerpt = "";
17
19
  conductorLog = [];
18
20
  operatorNotes = [];
19
21
  usageByModel = new Map();
20
22
  totalUsage = { ...types_1.ZERO_USAGE };
21
23
  cost = 0;
24
+ /** Sampled cumulative token spend over time (budget sparkline). */
25
+ budgetSeries = [];
22
26
  finalSummary;
23
27
  finalReportPath;
24
28
  lastSeq = 0;
@@ -29,10 +33,34 @@ class RunState {
29
33
  constructor(pricing = {}) {
30
34
  this.pricing = pricing;
31
35
  }
36
+ /** Sub-states for hierarchical teams, keyed by the owning task id. */
37
+ teams = new Map();
32
38
  apply(ev) {
33
39
  this.lastSeq = ev.seq;
34
40
  this.lastT = ev.t;
35
41
  this.updatedAt = ev.t;
42
+ // Team-stamped events reduce into their team's sub-state so a sub-swarm's
43
+ // hundred tasks never pollute the root task list. Usage still rolls up
44
+ // here — the run's budget/cost is one number.
45
+ const teamId = typeof ev.teamId === "string" ? ev.teamId : undefined;
46
+ if (teamId) {
47
+ let team = this.teams.get(teamId);
48
+ if (!team) {
49
+ team = new RunState(this.pricing);
50
+ this.teams.set(teamId, team);
51
+ }
52
+ const { teamId: _omit, ...rest } = ev;
53
+ team.apply(rest);
54
+ if (ev.type === "usage") {
55
+ const u = ev.usage;
56
+ const model = ev.model ?? "unknown";
57
+ this.usageByModel.set(model, (0, types_1.addUsage)(this.usageByModel.get(model) ?? { ...types_1.ZERO_USAGE }, u));
58
+ this.totalUsage = (0, types_1.addUsage)(this.totalUsage, u);
59
+ this.cost += (0, types_1.usageCost)(u, this.pricing[model]);
60
+ this.pushBudgetPoint(ev.t);
61
+ }
62
+ return;
63
+ }
36
64
  switch (ev.type) {
37
65
  case "run.created": {
38
66
  this.meta = ev.meta;
@@ -96,9 +124,23 @@ class RunState {
96
124
  t.report = ev.report;
97
125
  t.reportStatus = ev.status;
98
126
  t.artifacts = ev.artifacts ?? t.artifacts;
127
+ if (Array.isArray(ev.keyFacts))
128
+ t.keyFacts = ev.keyFacts;
129
+ if (Array.isArray(ev.openQuestions))
130
+ t.openQuestions = ev.openQuestions;
131
+ if (Array.isArray(ev.filesTouched))
132
+ t.filesTouched = ev.filesTouched;
133
+ if (Array.isArray(ev.sources))
134
+ t.sources = ev.sources;
99
135
  }
100
136
  break;
101
137
  }
138
+ case "task.checkpoint": {
139
+ const t = this.tasks.get(ev.taskId);
140
+ if (t)
141
+ t.lastCheckpoint = ev.summary;
142
+ break;
143
+ }
102
144
  case "verify.result": {
103
145
  const t = this.tasks.get(ev.taskId);
104
146
  if (t)
@@ -146,18 +188,37 @@ class RunState {
146
188
  }
147
189
  break;
148
190
  }
191
+ case "plan.updated":
192
+ this.planExcerpt = String(ev.excerpt ?? "");
193
+ break;
194
+ case "phase.set":
195
+ this.phases.push({
196
+ t: ev.t,
197
+ name: String(ev.name ?? ""),
198
+ goal: ev.goal,
199
+ exitCriteria: ev.exit_criteria,
200
+ });
201
+ break;
149
202
  case "note.added":
150
203
  this.notes.push({
151
204
  t: ev.t,
152
205
  taskId: ev.taskId,
153
206
  agentId: ev.agentId,
154
207
  key: ev.key,
208
+ kind: ev.kind,
155
209
  text: ev.text,
210
+ url: typeof ev.url === "string" ? ev.url : undefined,
156
211
  });
157
212
  // Reduced state is held live by the hub and the resume seed — keep
158
- // only the tail that digests/views actually use.
159
- if (this.notes.length > 1000)
160
- this.notes.splice(0, this.notes.length - 1000);
213
+ // only the tail that digests/views actually use. Decisions and
214
+ // conflicts are never dropped: they anchor long-horizon coherence.
215
+ if (this.notes.length > 1000) {
216
+ const keep = (n) => n.kind === "decision" || n.kind === "conflict";
217
+ const pinned = this.notes.filter(keep);
218
+ const rest = this.notes.filter((n) => !keep(n));
219
+ rest.splice(0, rest.length - Math.max(0, 1000 - pinned.length));
220
+ this.notes = [...pinned, ...rest].sort((a, b) => a.t - b.t);
221
+ }
161
222
  break;
162
223
  case "conductor.say":
163
224
  this.conductorLog.push({ t: ev.t, text: ev.text });
@@ -179,6 +240,7 @@ class RunState {
179
240
  this.usageByModel.set(model, (0, types_1.addUsage)(this.usageByModel.get(model) ?? { ...types_1.ZERO_USAGE }, u));
180
241
  this.totalUsage = (0, types_1.addUsage)(this.totalUsage, u);
181
242
  this.cost += (0, types_1.usageCost)(u, this.pricing[model]);
243
+ this.pushBudgetPoint(ev.t);
182
244
  break;
183
245
  }
184
246
  case "run.final":
@@ -187,6 +249,26 @@ class RunState {
187
249
  break;
188
250
  }
189
251
  }
252
+ /**
253
+ * Sample the cumulative spend: a point per meaningful jump (≥0.5% of the
254
+ * budget cap, or 2k tokens unbounded), halving resolution past 600 points.
255
+ */
256
+ pushBudgetPoint(t) {
257
+ const tokens = this.totalUsage.promptTokens + this.totalUsage.completionTokens;
258
+ const cap = this.meta?.options?.maxTokens ?? 0;
259
+ const minStep = cap > 0 ? Math.max(2000, cap * 0.005) : 2000;
260
+ const last = this.budgetSeries[this.budgetSeries.length - 1];
261
+ if (last && tokens - last.tokens < minStep) {
262
+ last.t = t;
263
+ last.tokens = tokens;
264
+ last.cost = this.cost;
265
+ return;
266
+ }
267
+ this.budgetSeries.push({ t, tokens, cost: this.cost });
268
+ if (this.budgetSeries.length > 600) {
269
+ this.budgetSeries = this.budgetSeries.filter((_, i) => i % 2 === 0 || i === this.budgetSeries.length - 1);
270
+ }
271
+ }
190
272
  taskList() {
191
273
  return this.taskOrder.map((id) => this.tasks.get(id)).filter(Boolean);
192
274
  }