@robzilla1738/agentswarm 0.2.0 → 0.5.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/README.md +36 -5
- package/dist/agent.js +64 -32
- package/dist/cli.js +18 -4
- package/dist/config.js +35 -5
- package/dist/crawltools.js +247 -0
- package/dist/deepseek.js +125 -10
- package/dist/executor.js +771 -122
- package/dist/hub.js +40 -3
- package/dist/journal.js +61 -11
- package/dist/memory.js +83 -0
- package/dist/prompts.js +109 -16
- package/dist/report.js +252 -0
- package/dist/run.js +7 -2
- package/dist/searchcore.js +191 -0
- package/dist/state.js +57 -3
- package/dist/tools.js +202 -12
- package/dist/webtools.js +191 -60
- package/package.json +3 -2
- package/ui/out/404/index.html +1 -1
- package/ui/out/404.html +1 -1
- package/ui/out/_next/static/chunks/532-35122e93f37719b9.js +1 -0
- package/ui/out/_next/static/chunks/677-859e8d42add1806b.js +1 -0
- package/ui/out/_next/static/chunks/app/page-dc9f6744d203e76c.js +1 -0
- package/ui/out/_next/static/chunks/app/run/page-2420c9e4c963d9b3.js +1 -0
- package/ui/out/_next/static/chunks/app/settings/page-092a6bf42dfde57d.js +1 -0
- package/ui/out/_next/static/css/9f7bd82b8e4c762c.css +3 -0
- package/ui/out/fonts/PlanetKosmos.ttf +0 -0
- package/ui/out/index.html +1 -1
- package/ui/out/index.txt +3 -3
- package/ui/out/run/index.html +1 -1
- package/ui/out/run/index.txt +3 -3
- package/ui/out/settings/index.html +1 -1
- package/ui/out/settings/index.txt +3 -3
- package/ui/out/_next/static/chunks/383-289a866b246b41cc.js +0 -1
- package/ui/out/_next/static/chunks/619-ba102abea3e3d0e4.js +0 -1
- package/ui/out/_next/static/chunks/677-b37981ba0eca75b2.js +0 -1
- package/ui/out/_next/static/chunks/app/page-0c9f35bd4aa8e370.js +0 -1
- package/ui/out/_next/static/chunks/app/run/page-13dc41a57e34da71.js +0 -1
- package/ui/out/_next/static/chunks/app/settings/page-a1763be7f6de888c.js +0 -1
- package/ui/out/_next/static/css/82edaa7a5942f894.css +0 -3
- /package/ui/out/_next/static/{eiQeDU9uBHNsBj0CFkp8M → errjtBR_bKoee8ogLp8xk}/_buildManifest.js +0 -0
- /package/ui/out/_next/static/{eiQeDU9uBHNsBj0CFkp8M → errjtBR_bKoee8ogLp8xk}/_ssgManifest.js +0 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dependency-free markdown → styled HTML rendering for final reports.
|
|
4
|
+
*
|
|
5
|
+
* Every run writes artifacts/final-report.html next to final-report.md so the
|
|
6
|
+
* operator always gets a readable, shareable document — even for fallback and
|
|
7
|
+
* failure reports. This is NOT a full CommonMark implementation; it covers the
|
|
8
|
+
* subset models actually emit in reports: headings, paragraphs, lists (nested),
|
|
9
|
+
* fenced code, inline code, bold/italic, links, images, tables, blockquotes,
|
|
10
|
+
* and horizontal rules. Unknown constructs degrade to escaped text — never to
|
|
11
|
+
* broken markup.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.mdToHtml = mdToHtml;
|
|
15
|
+
exports.renderFinalHtml = renderFinalHtml;
|
|
16
|
+
function esc(s) {
|
|
17
|
+
return s
|
|
18
|
+
.replace(/&/g, "&")
|
|
19
|
+
.replace(/</g, "<")
|
|
20
|
+
.replace(/>/g, ">")
|
|
21
|
+
.replace(/"/g, """);
|
|
22
|
+
}
|
|
23
|
+
/** Inline markdown on an already-escaped string. Code spans are opaque. */
|
|
24
|
+
function inline(s) {
|
|
25
|
+
const out = [];
|
|
26
|
+
// Split on code spans first so no other rule fires inside them.
|
|
27
|
+
const parts = s.split(/(`+[^`]*`+)/g);
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
const code = /^(`+)([^`]*)\1$/.exec(part);
|
|
30
|
+
if (code) {
|
|
31
|
+
out.push(`<code>${code[2].trim() || "`"}</code>`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let t = part;
|
|
35
|
+
// Images before links (same bracket syntax).
|
|
36
|
+
t = t.replace(/!\[([^\]]*)\]\((https?:[^()\s]+)\)/g, '<img src="$2" alt="$1" loading="lazy">');
|
|
37
|
+
t = t.replace(/\[([^\]]+)\]\(((?:https?:|#|\.{0,2}\/)[^()\s]*)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
|
38
|
+
t = t.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
|
|
39
|
+
t = t.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
40
|
+
t = t.replace(/(^|[\s(])\*([^*\s][^*]*)\*/g, "$1<em>$2</em>");
|
|
41
|
+
t = t.replace(/(^|[\s(])_([^_\s][^_]*)_(?=[\s.,;:!?)]|$)/g, "$1<em>$2</em>");
|
|
42
|
+
// Bare URLs become links (escaped text, so no quotes can appear inside).
|
|
43
|
+
t = t.replace(/(^|[\s(])(https?:\/\/[^\s<)]+[^\s<).,;:!?])/g, '$1<a href="$2" target="_blank" rel="noopener">$2</a>');
|
|
44
|
+
out.push(t);
|
|
45
|
+
}
|
|
46
|
+
return out.join("");
|
|
47
|
+
}
|
|
48
|
+
function mdToHtml(md) {
|
|
49
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
50
|
+
const html = [];
|
|
51
|
+
const lists = [];
|
|
52
|
+
let para = [];
|
|
53
|
+
let quote = [];
|
|
54
|
+
const closeLists = (toIndent = -1) => {
|
|
55
|
+
while (lists.length && lists[lists.length - 1].indent > toIndent) {
|
|
56
|
+
html.push(`</li></${lists.pop().tag}>`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const flushPara = () => {
|
|
60
|
+
if (para.length) {
|
|
61
|
+
html.push(`<p>${inline(esc(para.join(" ")))}</p>`);
|
|
62
|
+
para = [];
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const flushQuote = () => {
|
|
66
|
+
if (quote.length) {
|
|
67
|
+
html.push(`<blockquote>${mdToHtml(quote.join("\n"))}</blockquote>`);
|
|
68
|
+
quote = [];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const flushAll = () => {
|
|
72
|
+
flushPara();
|
|
73
|
+
flushQuote();
|
|
74
|
+
closeLists();
|
|
75
|
+
};
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const line = lines[i];
|
|
78
|
+
// Fenced code block.
|
|
79
|
+
const fence = /^\s*(```|~~~)\s*(\S*)/.exec(line);
|
|
80
|
+
if (fence) {
|
|
81
|
+
flushAll();
|
|
82
|
+
const buf = [];
|
|
83
|
+
for (i++; i < lines.length && !lines[i].trim().startsWith(fence[1]); i++)
|
|
84
|
+
buf.push(lines[i]);
|
|
85
|
+
const lang = fence[2] ? ` class="lang-${esc(fence[2])}"` : "";
|
|
86
|
+
html.push(`<pre><code${lang}>${esc(buf.join("\n"))}</code></pre>`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Blockquote (grouped, recursively rendered).
|
|
90
|
+
const q = /^\s*>\s?(.*)$/.exec(line);
|
|
91
|
+
if (q) {
|
|
92
|
+
flushPara();
|
|
93
|
+
closeLists();
|
|
94
|
+
quote.push(q[1]);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
flushQuote();
|
|
98
|
+
// Blank line ends the current paragraph / list run (unless the next
|
|
99
|
+
// non-blank line continues the list).
|
|
100
|
+
if (!line.trim()) {
|
|
101
|
+
flushPara();
|
|
102
|
+
if (lists.length) {
|
|
103
|
+
let j = i + 1;
|
|
104
|
+
while (j < lines.length && !lines[j].trim())
|
|
105
|
+
j++;
|
|
106
|
+
if (j >= lines.length || !/^(\s*)([-*+]|\d+[.)])\s+/.test(lines[j]))
|
|
107
|
+
closeLists();
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Heading.
|
|
112
|
+
const h = /^(#{1,6})\s+(.*)$/.exec(line.trim());
|
|
113
|
+
if (h) {
|
|
114
|
+
flushAll();
|
|
115
|
+
const level = h[1].length;
|
|
116
|
+
const text = h[2].replace(/\s+#+\s*$/, "");
|
|
117
|
+
html.push(`<h${level}>${inline(esc(text))}</h${level}>`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Horizontal rule.
|
|
121
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
122
|
+
flushAll();
|
|
123
|
+
html.push("<hr>");
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Table: header row + |---| separator.
|
|
127
|
+
if (line.includes("|") && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|[\s:|-]*$/.test(lines[i + 1]) && lines[i + 1].includes("-")) {
|
|
128
|
+
flushAll();
|
|
129
|
+
const cells = (row) => row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => inline(esc(c.trim())));
|
|
130
|
+
const head = cells(line);
|
|
131
|
+
const rows = [];
|
|
132
|
+
for (i += 2; i < lines.length && lines[i].includes("|") && lines[i].trim(); i++)
|
|
133
|
+
rows.push(cells(lines[i]));
|
|
134
|
+
i--;
|
|
135
|
+
html.push("<table><thead><tr>" +
|
|
136
|
+
head.map((c) => `<th>${c}</th>`).join("") +
|
|
137
|
+
"</tr></thead><tbody>" +
|
|
138
|
+
rows.map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`).join("") +
|
|
139
|
+
"</tbody></table>");
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// List item (unordered or ordered, nested by indentation).
|
|
143
|
+
const li = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(line);
|
|
144
|
+
if (li) {
|
|
145
|
+
flushPara();
|
|
146
|
+
const indent = li[1].length;
|
|
147
|
+
const tag = /\d/.test(li[2]) ? "ol" : "ul";
|
|
148
|
+
const top = lists[lists.length - 1];
|
|
149
|
+
if (!top || indent > top.indent) {
|
|
150
|
+
lists.push({ indent, tag });
|
|
151
|
+
html.push(`<${tag}><li>${inline(esc(li[3]))}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
closeLists(indent);
|
|
155
|
+
const cur = lists[lists.length - 1];
|
|
156
|
+
if (cur && cur.indent === indent && cur.tag !== tag) {
|
|
157
|
+
html.push(`</li></${lists.pop().tag}>`);
|
|
158
|
+
}
|
|
159
|
+
if (lists.length && lists[lists.length - 1].indent === indent) {
|
|
160
|
+
html.push(`</li><li>${inline(esc(li[3]))}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
lists.push({ indent, tag });
|
|
164
|
+
html.push(`<${tag}><li>${inline(esc(li[3]))}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Continuation line inside a list item.
|
|
170
|
+
if (lists.length && /^\s{2,}\S/.test(line)) {
|
|
171
|
+
html.push(` ${inline(esc(line.trim()))}`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
closeLists();
|
|
175
|
+
para.push(line.trim());
|
|
176
|
+
}
|
|
177
|
+
flushAll();
|
|
178
|
+
return html.join("\n");
|
|
179
|
+
}
|
|
180
|
+
const CSS = `
|
|
181
|
+
:root { color-scheme: light dark; }
|
|
182
|
+
* { box-sizing: border-box; }
|
|
183
|
+
body {
|
|
184
|
+
margin: 0; padding: 48px 24px 96px;
|
|
185
|
+
font: 16px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
186
|
+
background: #fcfcfa; color: #1c1c1a;
|
|
187
|
+
}
|
|
188
|
+
@media (prefers-color-scheme: dark) { body { background: #131312; color: #e8e6e1; } }
|
|
189
|
+
main { max-width: 860px; margin: 0 auto; }
|
|
190
|
+
header.run-meta {
|
|
191
|
+
max-width: 860px; margin: 0 auto 36px; padding-bottom: 20px;
|
|
192
|
+
border-bottom: 1px solid rgba(128,128,128,.25);
|
|
193
|
+
font-size: 13px; color: #6e6e68; display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center;
|
|
194
|
+
}
|
|
195
|
+
.badge { padding: 2px 10px; border-radius: 999px; font-weight: 600; font-size: 12px; letter-spacing: .02em; }
|
|
196
|
+
.badge.done { background: rgba(34,160,84,.14); color: #1d8a4c; }
|
|
197
|
+
.badge.failed { background: rgba(214,60,60,.14); color: #c23b3b; }
|
|
198
|
+
.badge.cancelled { background: rgba(150,150,150,.18); color: #77756f; }
|
|
199
|
+
h1, h2, h3, h4 { line-height: 1.25; letter-spacing: -0.012em; }
|
|
200
|
+
h1 { font-size: 30px; margin: 0 0 18px; }
|
|
201
|
+
h2 { font-size: 22px; margin: 36px 0 12px; }
|
|
202
|
+
h3 { font-size: 18px; margin: 28px 0 10px; }
|
|
203
|
+
a { color: #2563c4; text-decoration: none; }
|
|
204
|
+
a:hover { text-decoration: underline; }
|
|
205
|
+
@media (prefers-color-scheme: dark) { a { color: #7aa7e8; } }
|
|
206
|
+
code {
|
|
207
|
+
font: 13.5px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
208
|
+
background: rgba(128,128,128,.13); padding: 1.5px 5px; border-radius: 4px;
|
|
209
|
+
}
|
|
210
|
+
pre {
|
|
211
|
+
background: rgba(128,128,128,.09); border: 1px solid rgba(128,128,128,.18);
|
|
212
|
+
border-radius: 8px; padding: 14px 16px; overflow-x: auto;
|
|
213
|
+
}
|
|
214
|
+
pre code { background: none; padding: 0; }
|
|
215
|
+
blockquote {
|
|
216
|
+
margin: 16px 0; padding: 2px 18px; border-left: 3px solid rgba(128,128,128,.35);
|
|
217
|
+
color: #6e6e68;
|
|
218
|
+
}
|
|
219
|
+
table { border-collapse: collapse; margin: 18px 0; width: 100%; font-size: 14.5px; }
|
|
220
|
+
th, td { border: 1px solid rgba(128,128,128,.25); padding: 7px 12px; text-align: left; vertical-align: top; }
|
|
221
|
+
th { background: rgba(128,128,128,.08); }
|
|
222
|
+
img { max-width: 100%; border-radius: 6px; }
|
|
223
|
+
hr { border: none; border-top: 1px solid rgba(128,128,128,.25); margin: 32px 0; }
|
|
224
|
+
ul, ol { padding-left: 26px; }
|
|
225
|
+
li { margin: 3px 0; }
|
|
226
|
+
`;
|
|
227
|
+
/** Self-contained HTML document (inline CSS, no scripts, no external fetches). */
|
|
228
|
+
function renderFinalHtml(o) {
|
|
229
|
+
const title = /^#\s+(.+)$/m.exec(o.markdown)?.[1] ?? o.mission;
|
|
230
|
+
const date = new Date(o.finishedAt).toISOString().replace("T", " ").slice(0, 16) + " UTC";
|
|
231
|
+
return `<!doctype html>
|
|
232
|
+
<html lang="en">
|
|
233
|
+
<head>
|
|
234
|
+
<meta charset="utf-8">
|
|
235
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
236
|
+
<title>${esc(title.slice(0, 120))}</title>
|
|
237
|
+
<style>${CSS}</style>
|
|
238
|
+
</head>
|
|
239
|
+
<body>
|
|
240
|
+
<header class="run-meta">
|
|
241
|
+
<span class="badge ${o.status}">${o.status}</span>
|
|
242
|
+
<span>run ${esc(o.runId)}</span>
|
|
243
|
+
<span>${esc(date)}</span>
|
|
244
|
+
<span title="${esc(o.mission.slice(0, 600))}">mission: ${esc(o.mission.length > 90 ? o.mission.slice(0, 90) + "…" : o.mission)}</span>
|
|
245
|
+
</header>
|
|
246
|
+
<main>
|
|
247
|
+
${mdToHtml(o.markdown)}
|
|
248
|
+
</main>
|
|
249
|
+
</body>
|
|
250
|
+
</html>
|
|
251
|
+
`;
|
|
252
|
+
}
|
package/dist/run.js
CHANGED
|
@@ -126,8 +126,13 @@ const summaryCache = new Map();
|
|
|
126
126
|
*/
|
|
127
127
|
const liveCache = new Map();
|
|
128
128
|
const TERMINAL_STATUSES = ["done", "failed", "cancelled"];
|
|
129
|
-
/**
|
|
130
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Grace before a silent, pid-less run is presumed dead. The pid file is the
|
|
131
|
+
* primary live signal; this window only covers engine startup (before
|
|
132
|
+
* writePid) and filesystem lag — generous enough that slow disks and slow
|
|
133
|
+
* provider preflights never flag a healthy run as interrupted.
|
|
134
|
+
*/
|
|
135
|
+
const STALE_AFTER_MS = 45_000;
|
|
131
136
|
/**
|
|
132
137
|
* A run whose engine process vanished without writing a terminal status
|
|
133
138
|
* (kill -9, reboot) would otherwise show "running" forever. Presentation-level
|
|
@@ -0,0 +1,191 @@
|
|
|
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.canonicalizeUrl = canonicalizeUrl;
|
|
13
|
+
exports.classifySource = classifySource;
|
|
14
|
+
exports.detectDate = detectDate;
|
|
15
|
+
exports.selectPassages = selectPassages;
|
|
16
|
+
exports.scorePage = scorePage;
|
|
17
|
+
exports.resultQualityScore = resultQualityScore;
|
|
18
|
+
exports.mergeCandidates = mergeCandidates;
|
|
19
|
+
exports.passageBonus = passageBonus;
|
|
20
|
+
exports.rankBonus = rankBonus;
|
|
21
|
+
/** Lowercased alphanumeric query tokens, stopword-ish short tokens dropped. */
|
|
22
|
+
function queryTerms(query) {
|
|
23
|
+
const m = query.toLowerCase().match(/[a-z0-9]+/g) || [];
|
|
24
|
+
return [...new Set(m.filter((t) => t.length > 2))];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate a few complementary query phrasings to widen source coverage in
|
|
28
|
+
* one search: the original, a stopword-stripped keyword core (different
|
|
29
|
+
* recall on most engines), and a docs/guide angle for question-shaped
|
|
30
|
+
* queries. Deterministic and low-noise — capped at `max`.
|
|
31
|
+
*/
|
|
32
|
+
function expandQueries(query, max = 3) {
|
|
33
|
+
const base = query.trim();
|
|
34
|
+
const out = [base];
|
|
35
|
+
const terms = queryTerms(query);
|
|
36
|
+
const core = terms.join(" ");
|
|
37
|
+
if (core && core.length > 4 && core !== base.toLowerCase())
|
|
38
|
+
out.push(core);
|
|
39
|
+
if (/^(how|what|why|when|which|where|who|is|are|can|does|do)\b/i.test(base) && terms.length) {
|
|
40
|
+
out.push(`${core} guide`);
|
|
41
|
+
}
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
return out.map((q) => q.trim()).filter((q) => q && !seen.has(q.toLowerCase()) && seen.add(q.toLowerCase())).slice(0, max);
|
|
44
|
+
}
|
|
45
|
+
const TRACKING_KEYS = new Set(["fbclid", "gclid", "mc_cid", "mc_eid"]);
|
|
46
|
+
/** Stable canonical form for dedup: strip tracking params, www, trailing slash; sort the query. */
|
|
47
|
+
function canonicalizeUrl(url) {
|
|
48
|
+
let u;
|
|
49
|
+
try {
|
|
50
|
+
u = new URL(url);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return url.toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
const pairs = [...u.searchParams.entries()].filter(([k]) => !TRACKING_KEYS.has(k.toLowerCase()) && !k.toLowerCase().startsWith("utm_"));
|
|
56
|
+
pairs.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
57
|
+
const query = pairs.length ? "?" + pairs.map(([k, v]) => `${k}=${v}`).join("&") : "";
|
|
58
|
+
const host = u.hostname.toLowerCase().replace(/^www\./, "");
|
|
59
|
+
let path = u.pathname || "/";
|
|
60
|
+
if (path !== "/")
|
|
61
|
+
path = path.replace(/\/+$/, "");
|
|
62
|
+
return `${u.protocol.toLowerCase()}//${host}${path}${query}`;
|
|
63
|
+
}
|
|
64
|
+
function classifySource(domain) {
|
|
65
|
+
const d = domain.toLowerCase();
|
|
66
|
+
if (d.endsWith(".gov") || d.endsWith(".mil"))
|
|
67
|
+
return "government";
|
|
68
|
+
if (d.endsWith(".edu"))
|
|
69
|
+
return "academic";
|
|
70
|
+
if (["twitter.com", "x.com", "reddit.com", "facebook.com"].some((s) => d.includes(s)))
|
|
71
|
+
return "social";
|
|
72
|
+
if (d.includes("news") || d.includes("reuters.com") || d.includes("apnews.com") || d.includes("bbc."))
|
|
73
|
+
return "news";
|
|
74
|
+
return "secondary";
|
|
75
|
+
}
|
|
76
|
+
/** ISO date if present, else a bare year. */
|
|
77
|
+
function detectDate(text) {
|
|
78
|
+
const iso = /\b(20\d{2}-\d{2}-\d{2})\b/.exec(text);
|
|
79
|
+
if (iso)
|
|
80
|
+
return iso[1];
|
|
81
|
+
const year = /\b(20\d{2})\b/.exec(text);
|
|
82
|
+
return year?.[1];
|
|
83
|
+
}
|
|
84
|
+
const WINDOW_WORDS = 60;
|
|
85
|
+
const STRIDE = 30;
|
|
86
|
+
/**
|
|
87
|
+
* Quotable passages: slide a 60-word window (stride 30) over the text and
|
|
88
|
+
* score each window by the fraction of query terms it contains. Deterministic
|
|
89
|
+
* lexical matching — no embeddings. Falls back to the lead window so a hit
|
|
90
|
+
* always carries something quotable.
|
|
91
|
+
*/
|
|
92
|
+
function selectPassages(text, query, maxPassages = 3) {
|
|
93
|
+
const body = text.trim();
|
|
94
|
+
if (!body)
|
|
95
|
+
return [];
|
|
96
|
+
const terms = queryTerms(query);
|
|
97
|
+
const tokens = [...body.matchAll(/\S+/g)];
|
|
98
|
+
if (!tokens.length)
|
|
99
|
+
return [];
|
|
100
|
+
const windows = [];
|
|
101
|
+
for (let i = 0; i < tokens.length; i += STRIDE) {
|
|
102
|
+
const slice = tokens.slice(i, i + WINDOW_WORDS);
|
|
103
|
+
const start = slice[0].index;
|
|
104
|
+
const last = slice[slice.length - 1];
|
|
105
|
+
const chunk = body.slice(start, last.index + last[0].length);
|
|
106
|
+
windows.push({ text: chunk, score: scoreChunk(chunk, terms) });
|
|
107
|
+
if (i + WINDOW_WORDS >= tokens.length)
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const scored = windows.filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
111
|
+
const picked = (scored.length ? scored : windows).slice(0, maxPassages);
|
|
112
|
+
return picked.map((p) => ({ text: p.text, score: Math.round(p.score * 10_000) / 10_000 }));
|
|
113
|
+
}
|
|
114
|
+
function scoreChunk(chunk, terms) {
|
|
115
|
+
if (!terms.length)
|
|
116
|
+
return 0;
|
|
117
|
+
const lowered = chunk.toLowerCase();
|
|
118
|
+
let hits = 0;
|
|
119
|
+
for (const t of terms)
|
|
120
|
+
if (lowered.includes(t))
|
|
121
|
+
hits++;
|
|
122
|
+
return hits / terms.length;
|
|
123
|
+
}
|
|
124
|
+
/** Content-quality score for a fetched page (deep mode re-ranking). */
|
|
125
|
+
function scorePage(page, terms) {
|
|
126
|
+
let score = 0;
|
|
127
|
+
const domain = page.domain.toLowerCase();
|
|
128
|
+
const url = page.url.toLowerCase();
|
|
129
|
+
const title = page.title.toLowerCase();
|
|
130
|
+
const type = classifySource(domain);
|
|
131
|
+
if (type === "primary" || type === "government" || type === "academic")
|
|
132
|
+
score += 5;
|
|
133
|
+
if (domain.includes("docs") || url.includes("docs") || title.includes("documentation"))
|
|
134
|
+
score += 5;
|
|
135
|
+
if (domain === "github.com" || domain === "gitlab.com")
|
|
136
|
+
score += 4;
|
|
137
|
+
if (["pypi.org", "npmjs.com", "rubygems.org"].includes(domain))
|
|
138
|
+
score -= 2;
|
|
139
|
+
if (page.date)
|
|
140
|
+
score += 1;
|
|
141
|
+
const lowered = page.text.toLowerCase();
|
|
142
|
+
for (const t of terms)
|
|
143
|
+
if (lowered.includes(t))
|
|
144
|
+
score += 1;
|
|
145
|
+
score += Math.min(page.text.length / 4000, 1);
|
|
146
|
+
return score;
|
|
147
|
+
}
|
|
148
|
+
const LOW_VALUE_SNIPPET = ["copy a direct link", "file metadata"];
|
|
149
|
+
/** Pre-fetch quality score for one engine result (snippet-level signals only). */
|
|
150
|
+
function resultQualityScore(c) {
|
|
151
|
+
const url = c.url.toLowerCase();
|
|
152
|
+
const title = c.title.toLowerCase();
|
|
153
|
+
const snippet = c.snippet.toLowerCase();
|
|
154
|
+
let score = Math.max(0, 20 - c.rank);
|
|
155
|
+
if (title.includes("official") || snippet.includes("official"))
|
|
156
|
+
score += 4;
|
|
157
|
+
if (title.includes("documentation") || snippet.includes("documentation") || url.includes("docs"))
|
|
158
|
+
score += 4;
|
|
159
|
+
if (url.includes("github.com") || url.includes("gitlab.com"))
|
|
160
|
+
score += 3;
|
|
161
|
+
if (LOW_VALUE_SNIPPET.some((t) => snippet.includes(t)))
|
|
162
|
+
score -= 10;
|
|
163
|
+
return score;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Merge results from several engines: quality-rank, dedupe by canonical URL
|
|
167
|
+
* (first/best occurrence wins), cap at maxResults.
|
|
168
|
+
*/
|
|
169
|
+
function mergeCandidates(candidates, maxResults) {
|
|
170
|
+
const ranked = [...candidates].sort((a, b) => resultQualityScore(b) - resultQualityScore(a));
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const c of ranked) {
|
|
174
|
+
const key = canonicalizeUrl(c.url);
|
|
175
|
+
if (seen.has(key))
|
|
176
|
+
continue;
|
|
177
|
+
seen.add(key);
|
|
178
|
+
out.push(c);
|
|
179
|
+
if (out.length >= maxResults)
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
/** Best-passage bonus used in deep-mode composite scoring. */
|
|
185
|
+
function passageBonus(passages) {
|
|
186
|
+
return passages.length ? passages[0].score * 3 : 0;
|
|
187
|
+
}
|
|
188
|
+
/** Engine-rank decay bonus used in composite scoring. */
|
|
189
|
+
function rankBonus(rank, ceiling) {
|
|
190
|
+
return Math.max(0, ceiling - rank) * 0.2;
|
|
191
|
+
}
|
package/dist/state.js
CHANGED
|
@@ -14,6 +14,8 @@ 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();
|
|
@@ -29,10 +31,33 @@ class RunState {
|
|
|
29
31
|
constructor(pricing = {}) {
|
|
30
32
|
this.pricing = pricing;
|
|
31
33
|
}
|
|
34
|
+
/** Sub-states for hierarchical teams, keyed by the owning task id. */
|
|
35
|
+
teams = new Map();
|
|
32
36
|
apply(ev) {
|
|
33
37
|
this.lastSeq = ev.seq;
|
|
34
38
|
this.lastT = ev.t;
|
|
35
39
|
this.updatedAt = ev.t;
|
|
40
|
+
// Team-stamped events reduce into their team's sub-state so a sub-swarm's
|
|
41
|
+
// hundred tasks never pollute the root task list. Usage still rolls up
|
|
42
|
+
// here — the run's budget/cost is one number.
|
|
43
|
+
const teamId = typeof ev.teamId === "string" ? ev.teamId : undefined;
|
|
44
|
+
if (teamId) {
|
|
45
|
+
let team = this.teams.get(teamId);
|
|
46
|
+
if (!team) {
|
|
47
|
+
team = new RunState(this.pricing);
|
|
48
|
+
this.teams.set(teamId, team);
|
|
49
|
+
}
|
|
50
|
+
const { teamId: _omit, ...rest } = ev;
|
|
51
|
+
team.apply(rest);
|
|
52
|
+
if (ev.type === "usage") {
|
|
53
|
+
const u = ev.usage;
|
|
54
|
+
const model = ev.model ?? "unknown";
|
|
55
|
+
this.usageByModel.set(model, (0, types_1.addUsage)(this.usageByModel.get(model) ?? { ...types_1.ZERO_USAGE }, u));
|
|
56
|
+
this.totalUsage = (0, types_1.addUsage)(this.totalUsage, u);
|
|
57
|
+
this.cost += (0, types_1.usageCost)(u, this.pricing[model]);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
36
61
|
switch (ev.type) {
|
|
37
62
|
case "run.created": {
|
|
38
63
|
this.meta = ev.meta;
|
|
@@ -96,9 +121,21 @@ class RunState {
|
|
|
96
121
|
t.report = ev.report;
|
|
97
122
|
t.reportStatus = ev.status;
|
|
98
123
|
t.artifacts = ev.artifacts ?? t.artifacts;
|
|
124
|
+
if (Array.isArray(ev.keyFacts))
|
|
125
|
+
t.keyFacts = ev.keyFacts;
|
|
126
|
+
if (Array.isArray(ev.openQuestions))
|
|
127
|
+
t.openQuestions = ev.openQuestions;
|
|
128
|
+
if (Array.isArray(ev.filesTouched))
|
|
129
|
+
t.filesTouched = ev.filesTouched;
|
|
99
130
|
}
|
|
100
131
|
break;
|
|
101
132
|
}
|
|
133
|
+
case "task.checkpoint": {
|
|
134
|
+
const t = this.tasks.get(ev.taskId);
|
|
135
|
+
if (t)
|
|
136
|
+
t.lastCheckpoint = ev.summary;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
102
139
|
case "verify.result": {
|
|
103
140
|
const t = this.tasks.get(ev.taskId);
|
|
104
141
|
if (t)
|
|
@@ -146,18 +183,35 @@ class RunState {
|
|
|
146
183
|
}
|
|
147
184
|
break;
|
|
148
185
|
}
|
|
186
|
+
case "plan.updated":
|
|
187
|
+
this.planExcerpt = String(ev.excerpt ?? "");
|
|
188
|
+
break;
|
|
189
|
+
case "phase.set":
|
|
190
|
+
this.phases.push({
|
|
191
|
+
t: ev.t,
|
|
192
|
+
name: String(ev.name ?? ""),
|
|
193
|
+
goal: ev.goal,
|
|
194
|
+
exitCriteria: ev.exit_criteria,
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
149
197
|
case "note.added":
|
|
150
198
|
this.notes.push({
|
|
151
199
|
t: ev.t,
|
|
152
200
|
taskId: ev.taskId,
|
|
153
201
|
agentId: ev.agentId,
|
|
154
202
|
key: ev.key,
|
|
203
|
+
kind: ev.kind,
|
|
155
204
|
text: ev.text,
|
|
156
205
|
});
|
|
157
206
|
// Reduced state is held live by the hub and the resume seed — keep
|
|
158
|
-
// only the tail that digests/views actually use.
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
// only the tail that digests/views actually use. Decisions are never
|
|
208
|
+
// dropped: they anchor the conductor's long-horizon coherence.
|
|
209
|
+
if (this.notes.length > 1000) {
|
|
210
|
+
const decisions = this.notes.filter((n) => n.kind === "decision");
|
|
211
|
+
const rest = this.notes.filter((n) => n.kind !== "decision");
|
|
212
|
+
rest.splice(0, rest.length - Math.max(0, 1000 - decisions.length));
|
|
213
|
+
this.notes = [...decisions, ...rest].sort((a, b) => a.t - b.t);
|
|
214
|
+
}
|
|
161
215
|
break;
|
|
162
216
|
case "conductor.say":
|
|
163
217
|
this.conductorLog.push({ t: ev.t, text: ev.text });
|