@sesamespace/hivemind 0.8.13 → 0.11.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/.pnpmrc.json +1 -1
- package/AUTOMATIC-MEMORY-MANAGEMENT.md +109 -0
- package/README.md +2 -1
- package/dist/{chunk-HTLHMXAL.js → chunk-4C6B2AMB.js} +2 -2
- package/dist/{chunk-NSTTILSN.js → chunk-4YXOQGQC.js} +79 -2
- package/dist/chunk-4YXOQGQC.js.map +1 -0
- package/dist/{chunk-LJHJGDKY.js → chunk-ICSJNKI6.js} +62 -2
- package/dist/chunk-ICSJNKI6.js.map +1 -0
- package/dist/{chunk-4Y7A25UG.js → chunk-K6KL2VD6.js} +2 -2
- package/dist/{chunk-MLY4VFOO.js → chunk-LYL5GG2F.js} +3 -3
- package/dist/{chunk-ZM7RK5YV.js → chunk-OB6OXLPC.js} +989 -38
- package/dist/chunk-OB6OXLPC.js.map +1 -0
- package/dist/{chunk-PFZO67E2.js → chunk-ZA4NWNS6.js} +2 -2
- package/dist/commands/fleet.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/start.js +3 -3
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/watchdog.js +3 -3
- package/dist/dashboard.html +913 -131
- package/dist/index.js +2 -2
- package/dist/main.js +375 -7
- package/dist/main.js.map +1 -1
- package/dist/start.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LJHJGDKY.js.map +0 -1
- package/dist/chunk-NSTTILSN.js.map +0 -1
- package/dist/chunk-ZM7RK5YV.js.map +0 -1
- /package/dist/{chunk-HTLHMXAL.js.map → chunk-4C6B2AMB.js.map} +0 -0
- /package/dist/{chunk-4Y7A25UG.js.map → chunk-K6KL2VD6.js.map} +0 -0
- /package/dist/{chunk-MLY4VFOO.js.map → chunk-LYL5GG2F.js.map} +0 -0
- /package/dist/{chunk-PFZO67E2.js.map → chunk-ZA4NWNS6.js.map} +0 -0
|
@@ -1343,14 +1343,15 @@ var Agent = class {
|
|
|
1343
1343
|
});
|
|
1344
1344
|
if (relevantEpisodes.length > 0) {
|
|
1345
1345
|
const episodeIds = relevantEpisodes.map((e) => e.id);
|
|
1346
|
-
this.memory.recordCoAccess(episodeIds).catch(() =>
|
|
1347
|
-
});
|
|
1346
|
+
this.memory.recordCoAccess(episodeIds).catch((err) => console.warn("[memory] recordCoAccess failed:", err.message));
|
|
1348
1347
|
for (const ep of relevantEpisodes) {
|
|
1349
|
-
this.memory.recordAccess(ep.id).catch(() =>
|
|
1350
|
-
});
|
|
1348
|
+
this.memory.recordAccess(ep.id).catch((err) => console.warn("[memory] recordAccess failed:", err.message));
|
|
1351
1349
|
}
|
|
1352
1350
|
}
|
|
1353
|
-
const l3Knowledge = await this.memory.getL3Knowledge(contextName).catch(() =>
|
|
1351
|
+
const l3Knowledge = await this.memory.getL3Knowledge(contextName).catch((err) => {
|
|
1352
|
+
console.warn("[memory] getL3Knowledge failed:", err.message);
|
|
1353
|
+
return [];
|
|
1354
|
+
});
|
|
1354
1355
|
const systemPromptResult = buildSystemPrompt({
|
|
1355
1356
|
config: this.config.agent,
|
|
1356
1357
|
episodes: relevantEpisodes,
|
|
@@ -1587,6 +1588,9 @@ var Agent = class {
|
|
|
1587
1588
|
getActiveContext() {
|
|
1588
1589
|
return this.contextManager.getActiveContext();
|
|
1589
1590
|
}
|
|
1591
|
+
getConversationHistories() {
|
|
1592
|
+
return this.conversationHistories;
|
|
1593
|
+
}
|
|
1590
1594
|
};
|
|
1591
1595
|
|
|
1592
1596
|
// packages/runtime/src/events.ts
|
|
@@ -4745,29 +4749,43 @@ function parseQuery(url) {
|
|
|
4745
4749
|
}
|
|
4746
4750
|
return params;
|
|
4747
4751
|
}
|
|
4748
|
-
|
|
4752
|
+
function readBody(req) {
|
|
4753
|
+
return new Promise((resolve21, reject) => {
|
|
4754
|
+
const chunks = [];
|
|
4755
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
4756
|
+
req.on("end", () => resolve21(Buffer.concat(chunks).toString()));
|
|
4757
|
+
req.on("error", reject);
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
async function proxyMemory(memoryUrl, path, method, res, body) {
|
|
4749
4761
|
try {
|
|
4750
|
-
const
|
|
4751
|
-
|
|
4762
|
+
const opts = { method };
|
|
4763
|
+
if (body && (method === "POST" || method === "PATCH" || method === "PUT")) {
|
|
4764
|
+
opts.body = body;
|
|
4765
|
+
opts.headers = { "Content-Type": "application/json" };
|
|
4766
|
+
}
|
|
4767
|
+
const resp = await fetch(`${memoryUrl}${path}`, opts);
|
|
4768
|
+
const respBody = await resp.text();
|
|
4752
4769
|
res.writeHead(resp.status, {
|
|
4753
4770
|
"Content-Type": resp.headers.get("content-type") ?? "application/json",
|
|
4754
4771
|
"Access-Control-Allow-Origin": "*"
|
|
4755
4772
|
});
|
|
4756
|
-
res.end(
|
|
4773
|
+
res.end(respBody);
|
|
4757
4774
|
} catch (err) {
|
|
4758
4775
|
json(res, { error: err.message }, 502);
|
|
4759
4776
|
}
|
|
4760
4777
|
}
|
|
4761
|
-
function startDashboardServer(requestLogger, memoryConfig) {
|
|
4778
|
+
function startDashboardServer(requestLogger, memoryConfig, getL1) {
|
|
4762
4779
|
const memoryUrl = memoryConfig.daemon_url;
|
|
4763
4780
|
const server = createServer(async (req, res) => {
|
|
4764
4781
|
const method = req.method ?? "GET";
|
|
4765
4782
|
const rawUrl = req.url ?? "/";
|
|
4766
4783
|
const urlPath = rawUrl.split("?")[0];
|
|
4784
|
+
const queryStr = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
4767
4785
|
if (method === "OPTIONS") {
|
|
4768
4786
|
res.writeHead(204, {
|
|
4769
4787
|
"Access-Control-Allow-Origin": "*",
|
|
4770
|
-
"Access-Control-Allow-Methods": "GET, DELETE, OPTIONS",
|
|
4788
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
4771
4789
|
"Access-Control-Allow-Headers": "Content-Type"
|
|
4772
4790
|
});
|
|
4773
4791
|
res.end();
|
|
@@ -4800,10 +4818,32 @@ function startDashboardServer(requestLogger, memoryConfig) {
|
|
|
4800
4818
|
}
|
|
4801
4819
|
return;
|
|
4802
4820
|
}
|
|
4821
|
+
if (method === "GET" && urlPath === "/api/health") {
|
|
4822
|
+
await proxyMemory(memoryUrl, "/health", "GET", res);
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
if (method === "GET" && urlPath === "/api/stats") {
|
|
4826
|
+
await proxyMemory(memoryUrl, "/stats", "GET", res);
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4829
|
+
if (method === "GET" && urlPath === "/api/search") {
|
|
4830
|
+
await proxyMemory(memoryUrl, `/search${queryStr}`, "GET", res);
|
|
4831
|
+
return;
|
|
4832
|
+
}
|
|
4833
|
+
if (method === "GET" && urlPath === "/api/search/cross-context") {
|
|
4834
|
+
await proxyMemory(memoryUrl, `/search/cross-context${queryStr}`, "GET", res);
|
|
4835
|
+
return;
|
|
4836
|
+
}
|
|
4803
4837
|
if (method === "GET" && urlPath === "/api/contexts") {
|
|
4804
4838
|
await proxyMemory(memoryUrl, "/contexts", "GET", res);
|
|
4805
4839
|
return;
|
|
4806
4840
|
}
|
|
4841
|
+
const ctxDeleteMatch = urlPath.match(/^\/api\/contexts\/([^/]+)$/);
|
|
4842
|
+
if (method === "DELETE" && ctxDeleteMatch) {
|
|
4843
|
+
const name = decodeURIComponent(ctxDeleteMatch[1]);
|
|
4844
|
+
await proxyMemory(memoryUrl, `/contexts/${encodeURIComponent(name)}`, "DELETE", res);
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4807
4847
|
const episodesMatch = urlPath.match(/^\/api\/contexts\/([^/]+)\/episodes$/);
|
|
4808
4848
|
if (method === "GET" && episodesMatch) {
|
|
4809
4849
|
const name = decodeURIComponent(episodesMatch[1]);
|
|
@@ -4821,12 +4861,69 @@ function startDashboardServer(requestLogger, memoryConfig) {
|
|
|
4821
4861
|
);
|
|
4822
4862
|
return;
|
|
4823
4863
|
}
|
|
4824
|
-
const
|
|
4825
|
-
if (method === "
|
|
4826
|
-
const
|
|
4864
|
+
const scoringGetMatch = urlPath.match(/^\/api\/contexts\/([^/]+)\/scoring$/);
|
|
4865
|
+
if (method === "GET" && scoringGetMatch) {
|
|
4866
|
+
const name = decodeURIComponent(scoringGetMatch[1]);
|
|
4867
|
+
await proxyMemory(memoryUrl, `/scoring/${encodeURIComponent(name)}`, "GET", res);
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
if (method === "POST" && scoringGetMatch) {
|
|
4871
|
+
const name = decodeURIComponent(scoringGetMatch[1]);
|
|
4872
|
+
const body = await readBody(req);
|
|
4873
|
+
await proxyMemory(memoryUrl, `/contexts/${encodeURIComponent(name)}/scoring`, "POST", res, body);
|
|
4874
|
+
return;
|
|
4875
|
+
}
|
|
4876
|
+
const l3IdMatch = urlPath.match(/^\/api\/l3\/([^/]+)$/);
|
|
4877
|
+
if (method === "DELETE" && l3IdMatch) {
|
|
4878
|
+
const id = decodeURIComponent(l3IdMatch[1]);
|
|
4827
4879
|
await proxyMemory(memoryUrl, `/promotion/l3/${encodeURIComponent(id)}`, "DELETE", res);
|
|
4828
4880
|
return;
|
|
4829
4881
|
}
|
|
4882
|
+
if (method === "PATCH" && l3IdMatch) {
|
|
4883
|
+
const id = decodeURIComponent(l3IdMatch[1]);
|
|
4884
|
+
const body = await readBody(req);
|
|
4885
|
+
await proxyMemory(memoryUrl, `/promotion/l3/${encodeURIComponent(id)}`, "PATCH", res, body);
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4888
|
+
if (method === "POST" && urlPath === "/api/promotion/run") {
|
|
4889
|
+
await proxyMemory(memoryUrl, `/promotion/run${queryStr}`, "POST", res);
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4892
|
+
if (method === "GET" && urlPath === "/api/access/top") {
|
|
4893
|
+
await proxyMemory(memoryUrl, `/access/top${queryStr}`, "GET", res);
|
|
4894
|
+
return;
|
|
4895
|
+
}
|
|
4896
|
+
const accessMatch = urlPath.match(/^\/api\/episodes\/([^/]+)\/access$/);
|
|
4897
|
+
if (method === "GET" && accessMatch) {
|
|
4898
|
+
const id = decodeURIComponent(accessMatch[1]);
|
|
4899
|
+
await proxyMemory(memoryUrl, `/access/${encodeURIComponent(id)}`, "GET", res);
|
|
4900
|
+
return;
|
|
4901
|
+
}
|
|
4902
|
+
if (method === "GET" && urlPath === "/api/l1") {
|
|
4903
|
+
if (!getL1) {
|
|
4904
|
+
json(res, { contexts: {} });
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
const histories = getL1();
|
|
4908
|
+
const result = {};
|
|
4909
|
+
for (const [ctx, msgs] of histories) {
|
|
4910
|
+
result[ctx] = { count: msgs.length };
|
|
4911
|
+
}
|
|
4912
|
+
json(res, { contexts: result });
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
const l1CtxMatch = urlPath.match(/^\/api\/l1\/([^/]+)$/);
|
|
4916
|
+
if (method === "GET" && l1CtxMatch) {
|
|
4917
|
+
if (!getL1) {
|
|
4918
|
+
json(res, { messages: [] });
|
|
4919
|
+
return;
|
|
4920
|
+
}
|
|
4921
|
+
const ctx = decodeURIComponent(l1CtxMatch[1]);
|
|
4922
|
+
const histories = getL1();
|
|
4923
|
+
const messages = histories.get(ctx) || [];
|
|
4924
|
+
json(res, { messages: messages.map((m) => ({ role: m.role, content: m.content ?? "" })) });
|
|
4925
|
+
return;
|
|
4926
|
+
}
|
|
4830
4927
|
json(res, { error: "Not found" }, 404);
|
|
4831
4928
|
} catch (err) {
|
|
4832
4929
|
console.error("[dashboard] Request error:", err.message);
|
|
@@ -6039,6 +6136,7 @@ import { mkdirSync as mkdirSync9, existsSync as existsSync13 } from "fs";
|
|
|
6039
6136
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6040
6137
|
var MAX_OUTPUT2 = 5e4;
|
|
6041
6138
|
var browserInstance = null;
|
|
6139
|
+
var contextInstances = /* @__PURE__ */ new Map();
|
|
6042
6140
|
var lastUsed = 0;
|
|
6043
6141
|
var idleTimer = null;
|
|
6044
6142
|
var IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -6047,7 +6145,16 @@ async function getBrowser() {
|
|
|
6047
6145
|
const modName = "playwright";
|
|
6048
6146
|
const pw = await Function("m", "return import(m)")(modName);
|
|
6049
6147
|
if (!browserInstance) {
|
|
6050
|
-
browserInstance = await pw.chromium.launch({
|
|
6148
|
+
browserInstance = await pw.chromium.launch({
|
|
6149
|
+
headless: true,
|
|
6150
|
+
args: [
|
|
6151
|
+
"--disable-dev-shm-usage",
|
|
6152
|
+
"--disable-setuid-sandbox",
|
|
6153
|
+
"--no-sandbox",
|
|
6154
|
+
"--disable-web-security",
|
|
6155
|
+
"--disable-features=VizDisplayCompositor"
|
|
6156
|
+
]
|
|
6157
|
+
});
|
|
6051
6158
|
if (!idleTimer) {
|
|
6052
6159
|
idleTimer = setInterval(async () => {
|
|
6053
6160
|
if (browserInstance && Date.now() - lastUsed > IDLE_TIMEOUT_MS) {
|
|
@@ -6064,7 +6171,43 @@ async function getBrowser() {
|
|
|
6064
6171
|
);
|
|
6065
6172
|
}
|
|
6066
6173
|
}
|
|
6174
|
+
async function getBrowserContext(sessionId = "default", options = {}) {
|
|
6175
|
+
const browser = await getBrowser();
|
|
6176
|
+
if (!contextInstances.has(sessionId)) {
|
|
6177
|
+
const contextOptions = {
|
|
6178
|
+
userAgent: options.userAgent || "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
6179
|
+
viewport: options.viewport || { width: 1280, height: 720 },
|
|
6180
|
+
locale: options.locale || "en-US",
|
|
6181
|
+
timezoneId: options.timezone || "America/New_York",
|
|
6182
|
+
deviceScaleFactor: options.deviceScaleFactor || 1,
|
|
6183
|
+
isMobile: options.isMobile || false,
|
|
6184
|
+
hasTouch: options.hasTouch || false,
|
|
6185
|
+
colorScheme: options.colorScheme || "light",
|
|
6186
|
+
reducedMotion: options.reducedMotion || "no-preference",
|
|
6187
|
+
permissions: options.permissions || [],
|
|
6188
|
+
geolocation: options.geolocation,
|
|
6189
|
+
offline: options.offline || false,
|
|
6190
|
+
acceptDownloads: options.downloadBehavior !== "deny"
|
|
6191
|
+
};
|
|
6192
|
+
const context = await browser.newContext(contextOptions);
|
|
6193
|
+
context.on("request", (request) => {
|
|
6194
|
+
console.log(`\u2192 ${request.method()} ${request.url()}`);
|
|
6195
|
+
});
|
|
6196
|
+
context.on("response", (response) => {
|
|
6197
|
+
console.log(`\u2190 ${response.status()} ${response.url()}`);
|
|
6198
|
+
});
|
|
6199
|
+
contextInstances.set(sessionId, context);
|
|
6200
|
+
}
|
|
6201
|
+
return contextInstances.get(sessionId);
|
|
6202
|
+
}
|
|
6067
6203
|
async function closeBrowser() {
|
|
6204
|
+
for (const [sessionId, context] of contextInstances) {
|
|
6205
|
+
try {
|
|
6206
|
+
await context.close();
|
|
6207
|
+
} catch {
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
contextInstances.clear();
|
|
6068
6211
|
if (browserInstance) {
|
|
6069
6212
|
try {
|
|
6070
6213
|
await browserInstance.close();
|
|
@@ -6082,14 +6225,26 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6082
6225
|
registry.register(
|
|
6083
6226
|
"browse",
|
|
6084
6227
|
[
|
|
6085
|
-
"Navigate to a URL and interact with the page using
|
|
6228
|
+
"Navigate to a URL and interact with the page using an enhanced headless browser with session persistence.",
|
|
6086
6229
|
"Actions:",
|
|
6087
6230
|
" extract (default) \u2014 get the readable text content of the page",
|
|
6088
6231
|
" screenshot \u2014 take a PNG screenshot and return the file path",
|
|
6089
6232
|
" click \u2014 click an element matching a CSS selector",
|
|
6090
6233
|
" type \u2014 type text into an element matching a CSS selector",
|
|
6091
6234
|
" evaluate \u2014 run arbitrary JavaScript in the page and return the result",
|
|
6092
|
-
"
|
|
6235
|
+
" form_fill \u2014 fill out a form with multiple fields at once",
|
|
6236
|
+
" scroll \u2014 scroll the page (up, down, to element, or to coordinates)",
|
|
6237
|
+
" hover \u2014 hover over an element",
|
|
6238
|
+
" select \u2014 select an option from a dropdown",
|
|
6239
|
+
" upload \u2014 upload a file to a file input",
|
|
6240
|
+
" download \u2014 download a file and return the path",
|
|
6241
|
+
" pdf \u2014 generate a PDF of the page",
|
|
6242
|
+
" wait_for \u2014 wait for various conditions (selector, text, url, timeout)",
|
|
6243
|
+
" network_logs \u2014 get network request/response logs",
|
|
6244
|
+
" console_logs \u2014 get browser console logs",
|
|
6245
|
+
" cookies \u2014 get/set/clear cookies",
|
|
6246
|
+
" storage \u2014 get/set/clear localStorage/sessionStorage",
|
|
6247
|
+
"Supports session persistence, mobile simulation, and advanced browser features."
|
|
6093
6248
|
].join("\n"),
|
|
6094
6249
|
{
|
|
6095
6250
|
type: "object",
|
|
@@ -6100,21 +6255,101 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6100
6255
|
},
|
|
6101
6256
|
action: {
|
|
6102
6257
|
type: "string",
|
|
6103
|
-
enum: [
|
|
6258
|
+
enum: [
|
|
6259
|
+
"extract",
|
|
6260
|
+
"screenshot",
|
|
6261
|
+
"click",
|
|
6262
|
+
"type",
|
|
6263
|
+
"evaluate",
|
|
6264
|
+
"form_fill",
|
|
6265
|
+
"scroll",
|
|
6266
|
+
"hover",
|
|
6267
|
+
"select",
|
|
6268
|
+
"upload",
|
|
6269
|
+
"download",
|
|
6270
|
+
"pdf",
|
|
6271
|
+
"wait_for",
|
|
6272
|
+
"network_logs",
|
|
6273
|
+
"console_logs",
|
|
6274
|
+
"cookies",
|
|
6275
|
+
"storage"
|
|
6276
|
+
],
|
|
6104
6277
|
description: "Action to perform (default: extract)."
|
|
6105
6278
|
},
|
|
6106
6279
|
selector: {
|
|
6107
6280
|
type: "string",
|
|
6108
|
-
description: "CSS selector for
|
|
6281
|
+
description: "CSS selector for element-based actions."
|
|
6109
6282
|
},
|
|
6110
6283
|
text: {
|
|
6111
6284
|
type: "string",
|
|
6112
|
-
description: "Text to type
|
|
6285
|
+
description: "Text to type or search for."
|
|
6113
6286
|
},
|
|
6114
6287
|
javascript: {
|
|
6115
6288
|
type: "string",
|
|
6116
6289
|
description: "JavaScript to evaluate in the page (for 'evaluate' action)."
|
|
6117
6290
|
},
|
|
6291
|
+
formData: {
|
|
6292
|
+
type: "object",
|
|
6293
|
+
description: "Key-value pairs for form filling (selector: value)."
|
|
6294
|
+
},
|
|
6295
|
+
scrollDirection: {
|
|
6296
|
+
type: "string",
|
|
6297
|
+
enum: ["up", "down", "left", "right", "top", "bottom"],
|
|
6298
|
+
description: "Scroll direction or position."
|
|
6299
|
+
},
|
|
6300
|
+
scrollDistance: {
|
|
6301
|
+
type: "number",
|
|
6302
|
+
description: "Pixels to scroll (default: 500)."
|
|
6303
|
+
},
|
|
6304
|
+
coordinates: {
|
|
6305
|
+
type: "object",
|
|
6306
|
+
properties: {
|
|
6307
|
+
x: { type: "number" },
|
|
6308
|
+
y: { type: "number" }
|
|
6309
|
+
},
|
|
6310
|
+
description: "X,Y coordinates for scrolling or clicking."
|
|
6311
|
+
},
|
|
6312
|
+
filePath: {
|
|
6313
|
+
type: "string",
|
|
6314
|
+
description: "Path to file for upload action."
|
|
6315
|
+
},
|
|
6316
|
+
waitCondition: {
|
|
6317
|
+
type: "string",
|
|
6318
|
+
enum: ["selector", "text", "url", "timeout", "networkidle"],
|
|
6319
|
+
description: "What to wait for."
|
|
6320
|
+
},
|
|
6321
|
+
waitValue: {
|
|
6322
|
+
type: "string",
|
|
6323
|
+
description: "Value to wait for (selector, text, URL pattern)."
|
|
6324
|
+
},
|
|
6325
|
+
cookieData: {
|
|
6326
|
+
type: "object",
|
|
6327
|
+
description: "Cookie data for cookie operations."
|
|
6328
|
+
},
|
|
6329
|
+
storageData: {
|
|
6330
|
+
type: "object",
|
|
6331
|
+
description: "Storage data for localStorage/sessionStorage operations."
|
|
6332
|
+
},
|
|
6333
|
+
session: {
|
|
6334
|
+
type: "string",
|
|
6335
|
+
description: "Session ID for persistent browser context (default: 'default')."
|
|
6336
|
+
},
|
|
6337
|
+
viewport: {
|
|
6338
|
+
type: "object",
|
|
6339
|
+
properties: {
|
|
6340
|
+
width: { type: "number" },
|
|
6341
|
+
height: { type: "number" }
|
|
6342
|
+
},
|
|
6343
|
+
description: "Browser viewport size."
|
|
6344
|
+
},
|
|
6345
|
+
userAgent: {
|
|
6346
|
+
type: "string",
|
|
6347
|
+
description: "Custom user agent string."
|
|
6348
|
+
},
|
|
6349
|
+
mobile: {
|
|
6350
|
+
type: "boolean",
|
|
6351
|
+
description: "Simulate mobile device."
|
|
6352
|
+
},
|
|
6118
6353
|
waitForSelector: {
|
|
6119
6354
|
type: "string",
|
|
6120
6355
|
description: "CSS selector to wait for before performing the action."
|
|
@@ -6132,19 +6367,33 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6132
6367
|
const selector = params.selector;
|
|
6133
6368
|
const text = params.text;
|
|
6134
6369
|
const javascript = params.javascript;
|
|
6370
|
+
const formData = params.formData;
|
|
6371
|
+
const scrollDirection = params.scrollDirection;
|
|
6372
|
+
const scrollDistance = params.scrollDistance || 500;
|
|
6373
|
+
const coordinates = params.coordinates;
|
|
6374
|
+
const filePath = params.filePath;
|
|
6375
|
+
const waitCondition = params.waitCondition;
|
|
6376
|
+
const waitValue = params.waitValue;
|
|
6377
|
+
const cookieData = params.cookieData;
|
|
6378
|
+
const storageData = params.storageData;
|
|
6379
|
+
const sessionId = params.session || "default";
|
|
6380
|
+
const viewport = params.viewport;
|
|
6381
|
+
const userAgent = params.userAgent;
|
|
6382
|
+
const mobile = params.mobile;
|
|
6135
6383
|
const waitForSelector = params.waitForSelector;
|
|
6136
6384
|
const timeout = params.timeout || 3e4;
|
|
6137
|
-
let
|
|
6138
|
-
try {
|
|
6139
|
-
browser = await getBrowser();
|
|
6140
|
-
} catch (err) {
|
|
6141
|
-
return err.message;
|
|
6142
|
-
}
|
|
6385
|
+
let context;
|
|
6143
6386
|
let page;
|
|
6144
6387
|
try {
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6388
|
+
const browserOptions = {
|
|
6389
|
+
session: sessionId,
|
|
6390
|
+
viewport,
|
|
6391
|
+
userAgent,
|
|
6392
|
+
isMobile: mobile,
|
|
6393
|
+
hasTouch: mobile
|
|
6394
|
+
};
|
|
6395
|
+
context = await getBrowserContext(sessionId, browserOptions);
|
|
6396
|
+
page = await context.newPage();
|
|
6148
6397
|
page.setDefaultTimeout(timeout);
|
|
6149
6398
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout });
|
|
6150
6399
|
if (waitForSelector) {
|
|
@@ -6170,12 +6419,31 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6170
6419
|
await page.screenshot({ path: filepath, fullPage: true });
|
|
6171
6420
|
return `Screenshot saved: ${filepath}`;
|
|
6172
6421
|
}
|
|
6422
|
+
case "pdf": {
|
|
6423
|
+
if (!existsSync13(screenshotDir)) {
|
|
6424
|
+
mkdirSync9(screenshotDir, { recursive: true });
|
|
6425
|
+
}
|
|
6426
|
+
const filename = `${randomUUID5()}.pdf`;
|
|
6427
|
+
const filepath = resolve12(screenshotDir, filename);
|
|
6428
|
+
await page.pdf({
|
|
6429
|
+
path: filepath,
|
|
6430
|
+
format: "A4",
|
|
6431
|
+
printBackground: true,
|
|
6432
|
+
margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" }
|
|
6433
|
+
});
|
|
6434
|
+
return `PDF saved: ${filepath}`;
|
|
6435
|
+
}
|
|
6173
6436
|
case "click": {
|
|
6174
6437
|
if (!selector) {
|
|
6175
6438
|
return "Error: 'selector' parameter is required for click action.";
|
|
6176
6439
|
}
|
|
6177
|
-
|
|
6178
|
-
|
|
6440
|
+
if (coordinates) {
|
|
6441
|
+
await page.mouse.click(coordinates.x, coordinates.y);
|
|
6442
|
+
return `Clicked at coordinates: (${coordinates.x}, ${coordinates.y})`;
|
|
6443
|
+
} else {
|
|
6444
|
+
await page.click(selector);
|
|
6445
|
+
return `Clicked: ${selector}`;
|
|
6446
|
+
}
|
|
6179
6447
|
}
|
|
6180
6448
|
case "type": {
|
|
6181
6449
|
if (!selector) {
|
|
@@ -6187,6 +6455,169 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6187
6455
|
await page.fill(selector, text);
|
|
6188
6456
|
return `Typed into ${selector}: "${text}"`;
|
|
6189
6457
|
}
|
|
6458
|
+
case "form_fill": {
|
|
6459
|
+
if (!formData) {
|
|
6460
|
+
return "Error: 'formData' parameter is required for form_fill action.";
|
|
6461
|
+
}
|
|
6462
|
+
const results = [];
|
|
6463
|
+
for (const [sel, value] of Object.entries(formData)) {
|
|
6464
|
+
await page.fill(sel, value);
|
|
6465
|
+
results.push(`${sel}: "${value}"`);
|
|
6466
|
+
}
|
|
6467
|
+
return `Form filled:
|
|
6468
|
+
${results.join("\n")}`;
|
|
6469
|
+
}
|
|
6470
|
+
case "scroll": {
|
|
6471
|
+
if (coordinates) {
|
|
6472
|
+
await page.evaluate(({ x, y }) => window.scrollTo(x, y), coordinates);
|
|
6473
|
+
return `Scrolled to coordinates: (${coordinates.x}, ${coordinates.y})`;
|
|
6474
|
+
} else if (selector) {
|
|
6475
|
+
await page.locator(selector).scrollIntoViewIfNeeded();
|
|
6476
|
+
return `Scrolled to element: ${selector}`;
|
|
6477
|
+
} else if (scrollDirection) {
|
|
6478
|
+
switch (scrollDirection) {
|
|
6479
|
+
case "up":
|
|
6480
|
+
await page.keyboard.press("PageUp");
|
|
6481
|
+
break;
|
|
6482
|
+
case "down":
|
|
6483
|
+
await page.keyboard.press("PageDown");
|
|
6484
|
+
break;
|
|
6485
|
+
case "top":
|
|
6486
|
+
await page.keyboard.press("Home");
|
|
6487
|
+
break;
|
|
6488
|
+
case "bottom":
|
|
6489
|
+
await page.keyboard.press("End");
|
|
6490
|
+
break;
|
|
6491
|
+
default:
|
|
6492
|
+
await page.mouse.wheel(0, scrollDirection === "down" ? scrollDistance : -scrollDistance);
|
|
6493
|
+
}
|
|
6494
|
+
return `Scrolled ${scrollDirection}`;
|
|
6495
|
+
}
|
|
6496
|
+
return "Error: scroll requires coordinates, selector, or scrollDirection";
|
|
6497
|
+
}
|
|
6498
|
+
case "hover": {
|
|
6499
|
+
if (!selector) {
|
|
6500
|
+
return "Error: 'selector' parameter is required for hover action.";
|
|
6501
|
+
}
|
|
6502
|
+
await page.hover(selector);
|
|
6503
|
+
return `Hovered over: ${selector}`;
|
|
6504
|
+
}
|
|
6505
|
+
case "select": {
|
|
6506
|
+
if (!selector || !text) {
|
|
6507
|
+
return "Error: 'selector' and 'text' parameters are required for select action.";
|
|
6508
|
+
}
|
|
6509
|
+
await page.selectOption(selector, text);
|
|
6510
|
+
return `Selected "${text}" in ${selector}`;
|
|
6511
|
+
}
|
|
6512
|
+
case "upload": {
|
|
6513
|
+
if (!selector || !filePath) {
|
|
6514
|
+
return "Error: 'selector' and 'filePath' parameters are required for upload action.";
|
|
6515
|
+
}
|
|
6516
|
+
const absolutePath = resolve12(workspaceDir, filePath);
|
|
6517
|
+
if (!existsSync13(absolutePath)) {
|
|
6518
|
+
return `Error: File not found: ${absolutePath}`;
|
|
6519
|
+
}
|
|
6520
|
+
await page.setInputFiles(selector, absolutePath);
|
|
6521
|
+
return `Uploaded file ${filePath} to ${selector}`;
|
|
6522
|
+
}
|
|
6523
|
+
case "download": {
|
|
6524
|
+
const downloadPromise = page.waitForEvent("download");
|
|
6525
|
+
if (selector) {
|
|
6526
|
+
await page.click(selector);
|
|
6527
|
+
}
|
|
6528
|
+
const download = await downloadPromise;
|
|
6529
|
+
const filename = download.suggestedFilename() || `download_${randomUUID5()}`;
|
|
6530
|
+
const downloadPath = resolve12(workspaceDir, "downloads", filename);
|
|
6531
|
+
if (!existsSync13(resolve12(workspaceDir, "downloads"))) {
|
|
6532
|
+
mkdirSync9(resolve12(workspaceDir, "downloads"), { recursive: true });
|
|
6533
|
+
}
|
|
6534
|
+
await download.saveAs(downloadPath);
|
|
6535
|
+
return `Downloaded: ${downloadPath}`;
|
|
6536
|
+
}
|
|
6537
|
+
case "wait_for": {
|
|
6538
|
+
if (!waitCondition || !waitValue) {
|
|
6539
|
+
return "Error: 'waitCondition' and 'waitValue' parameters are required for wait_for action.";
|
|
6540
|
+
}
|
|
6541
|
+
switch (waitCondition) {
|
|
6542
|
+
case "selector":
|
|
6543
|
+
await page.waitForSelector(waitValue, { timeout });
|
|
6544
|
+
return `Waited for selector: ${waitValue}`;
|
|
6545
|
+
case "text":
|
|
6546
|
+
await page.waitForFunction(
|
|
6547
|
+
(text2) => document.body.innerText.includes(text2),
|
|
6548
|
+
waitValue,
|
|
6549
|
+
{ timeout }
|
|
6550
|
+
);
|
|
6551
|
+
return `Waited for text: ${waitValue}`;
|
|
6552
|
+
case "url":
|
|
6553
|
+
await page.waitForURL(waitValue, { timeout });
|
|
6554
|
+
return `Waited for URL: ${waitValue}`;
|
|
6555
|
+
case "networkidle":
|
|
6556
|
+
await page.waitForLoadState("networkidle", { timeout });
|
|
6557
|
+
return "Waited for network idle";
|
|
6558
|
+
case "timeout":
|
|
6559
|
+
await page.waitForTimeout(parseInt(waitValue));
|
|
6560
|
+
return `Waited for ${waitValue}ms`;
|
|
6561
|
+
default:
|
|
6562
|
+
return `Unknown wait condition: ${waitCondition}`;
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
case "network_logs": {
|
|
6566
|
+
const logs = await page.evaluate(() => {
|
|
6567
|
+
return "Network logging not yet implemented - use browser dev tools";
|
|
6568
|
+
});
|
|
6569
|
+
return logs;
|
|
6570
|
+
}
|
|
6571
|
+
case "console_logs": {
|
|
6572
|
+
const logs = [];
|
|
6573
|
+
page.on("console", (msg) => {
|
|
6574
|
+
logs.push(`[${msg.type()}] ${msg.text()}`);
|
|
6575
|
+
});
|
|
6576
|
+
await page.waitForTimeout(1e3);
|
|
6577
|
+
return logs.length > 0 ? logs.join("\n") : "No console logs";
|
|
6578
|
+
}
|
|
6579
|
+
case "cookies": {
|
|
6580
|
+
if (cookieData) {
|
|
6581
|
+
if (cookieData.action === "set") {
|
|
6582
|
+
await context.addCookies([cookieData.cookie]);
|
|
6583
|
+
return `Cookie set: ${cookieData.cookie.name}`;
|
|
6584
|
+
} else if (cookieData.action === "clear") {
|
|
6585
|
+
await context.clearCookies();
|
|
6586
|
+
return "Cookies cleared";
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
const cookies = await context.cookies();
|
|
6590
|
+
return JSON.stringify(cookies, null, 2);
|
|
6591
|
+
}
|
|
6592
|
+
case "storage": {
|
|
6593
|
+
if (storageData) {
|
|
6594
|
+
const storageType = storageData.type || "localStorage";
|
|
6595
|
+
if (storageData.action === "set") {
|
|
6596
|
+
await page.evaluate(({ type, key, value }) => {
|
|
6597
|
+
if (type === "localStorage") {
|
|
6598
|
+
localStorage.setItem(key, value);
|
|
6599
|
+
} else {
|
|
6600
|
+
sessionStorage.setItem(key, value);
|
|
6601
|
+
}
|
|
6602
|
+
}, { type: storageType, key: storageData.key, value: storageData.value });
|
|
6603
|
+
return `${storageType} set: ${storageData.key}`;
|
|
6604
|
+
} else if (storageData.action === "clear") {
|
|
6605
|
+
await page.evaluate((type) => {
|
|
6606
|
+
if (type === "localStorage") {
|
|
6607
|
+
localStorage.clear();
|
|
6608
|
+
} else {
|
|
6609
|
+
sessionStorage.clear();
|
|
6610
|
+
}
|
|
6611
|
+
}, storageType);
|
|
6612
|
+
return `${storageType} cleared`;
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
const storage = await page.evaluate(() => ({
|
|
6616
|
+
localStorage: Object.fromEntries(Object.entries(localStorage)),
|
|
6617
|
+
sessionStorage: Object.fromEntries(Object.entries(sessionStorage))
|
|
6618
|
+
}));
|
|
6619
|
+
return JSON.stringify(storage, null, 2);
|
|
6620
|
+
}
|
|
6190
6621
|
case "evaluate": {
|
|
6191
6622
|
if (!javascript) {
|
|
6192
6623
|
return "Error: 'javascript' parameter is required for evaluate action.";
|
|
@@ -6200,7 +6631,7 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6200
6631
|
return str ?? "(no result)";
|
|
6201
6632
|
}
|
|
6202
6633
|
default:
|
|
6203
|
-
return `Unknown action: ${action}.
|
|
6634
|
+
return `Unknown action: ${action}. Available actions: extract, screenshot, click, type, evaluate, form_fill, scroll, hover, select, upload, download, pdf, wait_for, network_logs, console_logs, cookies, storage.`;
|
|
6204
6635
|
}
|
|
6205
6636
|
} catch (err) {
|
|
6206
6637
|
return `browse error: ${err.message}`;
|
|
@@ -6214,6 +6645,98 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
6214
6645
|
}
|
|
6215
6646
|
}
|
|
6216
6647
|
);
|
|
6648
|
+
registry.register(
|
|
6649
|
+
"browser_session",
|
|
6650
|
+
[
|
|
6651
|
+
"Manage browser sessions for persistent contexts across multiple browse operations.",
|
|
6652
|
+
"Actions:",
|
|
6653
|
+
" list \u2014 list all active browser sessions",
|
|
6654
|
+
" create \u2014 create a new browser session with custom options",
|
|
6655
|
+
" close \u2014 close a specific browser session",
|
|
6656
|
+
" close_all \u2014 close all browser sessions",
|
|
6657
|
+
" info \u2014 get information about a specific session"
|
|
6658
|
+
].join("\n"),
|
|
6659
|
+
{
|
|
6660
|
+
type: "object",
|
|
6661
|
+
properties: {
|
|
6662
|
+
action: {
|
|
6663
|
+
type: "string",
|
|
6664
|
+
enum: ["list", "create", "close", "close_all", "info"],
|
|
6665
|
+
description: "Session management action."
|
|
6666
|
+
},
|
|
6667
|
+
sessionId: {
|
|
6668
|
+
type: "string",
|
|
6669
|
+
description: "Session ID for create, close, or info actions."
|
|
6670
|
+
},
|
|
6671
|
+
options: {
|
|
6672
|
+
type: "object",
|
|
6673
|
+
description: "Browser options for create action (viewport, userAgent, mobile, etc.)."
|
|
6674
|
+
}
|
|
6675
|
+
},
|
|
6676
|
+
required: ["action"]
|
|
6677
|
+
},
|
|
6678
|
+
async (params) => {
|
|
6679
|
+
const action = params.action;
|
|
6680
|
+
const sessionId = params.sessionId;
|
|
6681
|
+
const options = params.options;
|
|
6682
|
+
try {
|
|
6683
|
+
switch (action) {
|
|
6684
|
+
case "list": {
|
|
6685
|
+
const sessions = Array.from(contextInstances.keys());
|
|
6686
|
+
return sessions.length > 0 ? `Active sessions: ${sessions.join(", ")}` : "No active browser sessions";
|
|
6687
|
+
}
|
|
6688
|
+
case "create": {
|
|
6689
|
+
if (!sessionId) {
|
|
6690
|
+
return "Error: 'sessionId' parameter is required for create action.";
|
|
6691
|
+
}
|
|
6692
|
+
if (contextInstances.has(sessionId)) {
|
|
6693
|
+
return `Error: Session '${sessionId}' already exists.`;
|
|
6694
|
+
}
|
|
6695
|
+
await getBrowserContext(sessionId, options || {});
|
|
6696
|
+
return `Created browser session: ${sessionId}`;
|
|
6697
|
+
}
|
|
6698
|
+
case "close": {
|
|
6699
|
+
if (!sessionId) {
|
|
6700
|
+
return "Error: 'sessionId' parameter is required for close action.";
|
|
6701
|
+
}
|
|
6702
|
+
const context = contextInstances.get(sessionId);
|
|
6703
|
+
if (!context) {
|
|
6704
|
+
return `Error: Session '${sessionId}' not found.`;
|
|
6705
|
+
}
|
|
6706
|
+
await context.close();
|
|
6707
|
+
contextInstances.delete(sessionId);
|
|
6708
|
+
return `Closed browser session: ${sessionId}`;
|
|
6709
|
+
}
|
|
6710
|
+
case "close_all": {
|
|
6711
|
+
const sessionCount = contextInstances.size;
|
|
6712
|
+
for (const [id, context] of contextInstances) {
|
|
6713
|
+
try {
|
|
6714
|
+
await context.close();
|
|
6715
|
+
} catch {
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
contextInstances.clear();
|
|
6719
|
+
return `Closed ${sessionCount} browser sessions`;
|
|
6720
|
+
}
|
|
6721
|
+
case "info": {
|
|
6722
|
+
if (!sessionId) {
|
|
6723
|
+
return "Error: 'sessionId' parameter is required for info action.";
|
|
6724
|
+
}
|
|
6725
|
+
const context = contextInstances.get(sessionId);
|
|
6726
|
+
if (!context) {
|
|
6727
|
+
return `Error: Session '${sessionId}' not found.`;
|
|
6728
|
+
}
|
|
6729
|
+
const pages = context.pages();
|
|
6730
|
+
return `Session '${sessionId}': ${pages.length} active pages`;
|
|
6731
|
+
}
|
|
6732
|
+
default:
|
|
6733
|
+
return `Unknown action: ${action}. Available actions: list, create, close, close_all, info.`;
|
|
6734
|
+
}
|
|
6735
|
+
} catch (err) {
|
|
6736
|
+
return `browser_session error: ${err.message}`;
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
);
|
|
6217
6740
|
}
|
|
6218
6741
|
|
|
6219
6742
|
// packages/runtime/src/tools/system.ts
|
|
@@ -7351,6 +7874,428 @@ ${output || err.message}`;
|
|
|
7351
7874
|
);
|
|
7352
7875
|
}
|
|
7353
7876
|
|
|
7877
|
+
// packages/runtime/src/tools/learn.ts
|
|
7878
|
+
function slugify(topic) {
|
|
7879
|
+
return topic.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
7880
|
+
}
|
|
7881
|
+
async function discoverResearch(topic, depth, registry) {
|
|
7882
|
+
if (!registry.has("web_search")) {
|
|
7883
|
+
throw new Error("web_search tool not available \u2014 is BRAVE_API_KEY set?");
|
|
7884
|
+
}
|
|
7885
|
+
const sources = [];
|
|
7886
|
+
const primary = await registry.execute("web_search", { query: topic, count: 8 });
|
|
7887
|
+
if (primary.startsWith("Error:")) throw new Error(primary);
|
|
7888
|
+
sources.push(...parseSearchResults(primary));
|
|
7889
|
+
if (depth === "deep") {
|
|
7890
|
+
const refinedQueries = [
|
|
7891
|
+
`"${topic}" tutorial`,
|
|
7892
|
+
`"${topic}" best practices`
|
|
7893
|
+
];
|
|
7894
|
+
for (const q of refinedQueries) {
|
|
7895
|
+
const result = await registry.execute("web_search", { query: q, count: 5 });
|
|
7896
|
+
if (!result.startsWith("Error:")) {
|
|
7897
|
+
sources.push(...parseSearchResults(result));
|
|
7898
|
+
}
|
|
7899
|
+
}
|
|
7900
|
+
}
|
|
7901
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7902
|
+
return sources.filter((s) => {
|
|
7903
|
+
if (seen.has(s.url)) return false;
|
|
7904
|
+
seen.add(s.url);
|
|
7905
|
+
return true;
|
|
7906
|
+
});
|
|
7907
|
+
}
|
|
7908
|
+
function parseSearchResults(raw) {
|
|
7909
|
+
const entries = [];
|
|
7910
|
+
const blocks = raw.split(/\n\n(?=\d+\.\s)/);
|
|
7911
|
+
for (const block of blocks) {
|
|
7912
|
+
const titleMatch = block.match(/\*\*(.+?)\*\*/);
|
|
7913
|
+
const urlMatch = block.match(/\s+(https?:\/\/\S+)/);
|
|
7914
|
+
const lines = block.split("\n");
|
|
7915
|
+
const snippet = lines.length >= 3 ? lines.slice(2).join(" ").trim() : "";
|
|
7916
|
+
if (titleMatch && urlMatch) {
|
|
7917
|
+
entries.push({
|
|
7918
|
+
title: titleMatch[1],
|
|
7919
|
+
url: urlMatch[1],
|
|
7920
|
+
snippet
|
|
7921
|
+
});
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
return entries;
|
|
7925
|
+
}
|
|
7926
|
+
async function exploreResearch(topic, sources, depth, focusAreas, registry, llm) {
|
|
7927
|
+
if (!registry.has("web_fetch")) {
|
|
7928
|
+
throw new Error("web_fetch tool not available");
|
|
7929
|
+
}
|
|
7930
|
+
const maxSources = depth === "deep" ? 6 : 3;
|
|
7931
|
+
const toFetch = sources.slice(0, maxSources);
|
|
7932
|
+
const findings = [];
|
|
7933
|
+
for (const source of toFetch) {
|
|
7934
|
+
try {
|
|
7935
|
+
const content = await registry.execute("web_fetch", {
|
|
7936
|
+
url: source.url,
|
|
7937
|
+
max_chars: 15e3
|
|
7938
|
+
});
|
|
7939
|
+
if (content.startsWith("Fetch failed:") || content.startsWith("Fetch error:")) {
|
|
7940
|
+
continue;
|
|
7941
|
+
}
|
|
7942
|
+
const focusClause = focusAreas.length > 0 ? `Focus especially on: ${focusAreas.join(", ")}.` : "";
|
|
7943
|
+
const messages = [
|
|
7944
|
+
{
|
|
7945
|
+
role: "system",
|
|
7946
|
+
content: "You are a research assistant. Extract key facts concisely. Return a numbered list of 5-10 key points, one per line."
|
|
7947
|
+
},
|
|
7948
|
+
{
|
|
7949
|
+
role: "user",
|
|
7950
|
+
content: `Extract 5-10 key facts about "${topic}" from this text. ${focusClause}
|
|
7951
|
+
|
|
7952
|
+
Source: ${source.title} (${source.url})
|
|
7953
|
+
|
|
7954
|
+
${content.slice(0, 12e3)}`
|
|
7955
|
+
}
|
|
7956
|
+
];
|
|
7957
|
+
const response = await llm.chat(messages);
|
|
7958
|
+
const keyPoints = response.content.split("\n").map((l) => l.replace(/^\d+[\.\)]\s*/, "").trim()).filter((l) => l.length > 0);
|
|
7959
|
+
findings.push({ source: `${source.title} \u2014 ${source.url}`, keyPoints });
|
|
7960
|
+
} catch {
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
return findings;
|
|
7964
|
+
}
|
|
7965
|
+
async function discoverCodebase(topic, registry) {
|
|
7966
|
+
if (!registry.has("shell")) {
|
|
7967
|
+
throw new Error("shell tool not available");
|
|
7968
|
+
}
|
|
7969
|
+
const sources = [];
|
|
7970
|
+
let targetDir = topic;
|
|
7971
|
+
if (topic.startsWith("http://") || topic.startsWith("https://") || topic.startsWith("git@")) {
|
|
7972
|
+
const slug = slugify(topic.split("/").pop()?.replace(".git", "") || "repo");
|
|
7973
|
+
const cloneDir = `/tmp/learn-${slug}`;
|
|
7974
|
+
const cloneResult = await registry.execute("shell", {
|
|
7975
|
+
command: `rm -rf ${cloneDir} && git clone --depth 1 ${topic} ${cloneDir} 2>&1 | tail -1`
|
|
7976
|
+
});
|
|
7977
|
+
if (cloneResult.toLowerCase().includes("fatal")) {
|
|
7978
|
+
throw new Error(`Failed to clone: ${cloneResult}`);
|
|
7979
|
+
}
|
|
7980
|
+
targetDir = cloneDir;
|
|
7981
|
+
}
|
|
7982
|
+
const fileList = await registry.execute("shell", {
|
|
7983
|
+
command: `find ${targetDir} -type f \\( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.rs' -o -name '*.go' -o -name '*.java' \\) ! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' ! -path '*/build/*' | head -50`
|
|
7984
|
+
});
|
|
7985
|
+
for (const line of fileList.split("\n").filter(Boolean)) {
|
|
7986
|
+
sources.push({ title: line.split("/").pop() || line, url: line, snippet: "" });
|
|
7987
|
+
}
|
|
7988
|
+
if (registry.has("read_file")) {
|
|
7989
|
+
const readmeResult = await registry.execute("read_file", { path: `${targetDir}/README.md` });
|
|
7990
|
+
if (!readmeResult.startsWith("Error:")) {
|
|
7991
|
+
sources.unshift({ title: "README.md", url: `${targetDir}/README.md`, snippet: readmeResult.slice(0, 200) });
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
const manifestCmd = `cat ${targetDir}/package.json 2>/dev/null || cat ${targetDir}/Cargo.toml 2>/dev/null || cat ${targetDir}/go.mod 2>/dev/null || cat ${targetDir}/pyproject.toml 2>/dev/null || echo ""`;
|
|
7995
|
+
const manifestResult = await registry.execute("shell", { command: manifestCmd });
|
|
7996
|
+
if (manifestResult.trim()) {
|
|
7997
|
+
sources.unshift({ title: "manifest", url: `${targetDir}/package.json`, snippet: manifestResult.slice(0, 200) });
|
|
7998
|
+
}
|
|
7999
|
+
return sources;
|
|
8000
|
+
}
|
|
8001
|
+
async function exploreCodebase(topic, sources, depth, focusAreas, registry, llm) {
|
|
8002
|
+
const maxFiles = depth === "deep" ? 12 : 5;
|
|
8003
|
+
const toRead = sources.slice(0, maxFiles);
|
|
8004
|
+
const findings = [];
|
|
8005
|
+
for (const source of toRead) {
|
|
8006
|
+
try {
|
|
8007
|
+
let content;
|
|
8008
|
+
if (registry.has("read_file")) {
|
|
8009
|
+
content = await registry.execute("read_file", { path: source.url });
|
|
8010
|
+
} else {
|
|
8011
|
+
content = await registry.execute("shell", { command: `cat "${source.url}" 2>/dev/null | head -200` });
|
|
8012
|
+
}
|
|
8013
|
+
if (!content || content.startsWith("Error:")) continue;
|
|
8014
|
+
const focusClause = focusAreas.length > 0 ? `Pay special attention to: ${focusAreas.join(", ")}.` : "";
|
|
8015
|
+
const messages = [
|
|
8016
|
+
{
|
|
8017
|
+
role: "system",
|
|
8018
|
+
content: "You are a code analyst. Analyze the given code file concisely. Return a numbered list of key observations."
|
|
8019
|
+
},
|
|
8020
|
+
{
|
|
8021
|
+
role: "user",
|
|
8022
|
+
content: `Analyze this code file from a project about "${topic}". Extract: purpose, key exports, patterns used, dependencies. ${focusClause}
|
|
8023
|
+
|
|
8024
|
+
File: ${source.title}
|
|
8025
|
+
|
|
8026
|
+
${content.slice(0, 1e4)}`
|
|
8027
|
+
}
|
|
8028
|
+
];
|
|
8029
|
+
const response = await llm.chat(messages);
|
|
8030
|
+
const keyPoints = response.content.split("\n").map((l) => l.replace(/^\d+[\.\)]\s*/, "").trim()).filter((l) => l.length > 0);
|
|
8031
|
+
findings.push({ source: source.title, keyPoints });
|
|
8032
|
+
} catch {
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
return findings;
|
|
8036
|
+
}
|
|
8037
|
+
async function synthesize(topic, findings, focusAreas, llm) {
|
|
8038
|
+
const findingText = findings.map((f) => `### ${f.source}
|
|
8039
|
+
${f.keyPoints.map((p) => `- ${p}`).join("\n")}`).join("\n\n");
|
|
8040
|
+
const focusClause = focusAreas.length > 0 ? `
|
|
8041
|
+
Focus areas: ${focusAreas.join(", ")}` : "";
|
|
8042
|
+
const messages = [
|
|
8043
|
+
{
|
|
8044
|
+
role: "system",
|
|
8045
|
+
content: [
|
|
8046
|
+
"You are a knowledge synthesizer. Distill research findings into structured, actionable knowledge.",
|
|
8047
|
+
"Output format:",
|
|
8048
|
+
"## Core Concepts",
|
|
8049
|
+
"(key ideas and definitions)",
|
|
8050
|
+
"## Key Relationships",
|
|
8051
|
+
"(how concepts connect)",
|
|
8052
|
+
"## Important Details",
|
|
8053
|
+
"(specifics worth remembering)",
|
|
8054
|
+
"## Practical Implications",
|
|
8055
|
+
"(how to apply this knowledge)",
|
|
8056
|
+
"",
|
|
8057
|
+
"Be concise but thorough. Maximum 8000 characters."
|
|
8058
|
+
].join("\n")
|
|
8059
|
+
},
|
|
8060
|
+
{
|
|
8061
|
+
role: "user",
|
|
8062
|
+
content: `Synthesize all findings about "${topic}" into structured knowledge.${focusClause}
|
|
8063
|
+
|
|
8064
|
+
${findingText}`
|
|
8065
|
+
}
|
|
8066
|
+
];
|
|
8067
|
+
const response = await llm.chat(messages);
|
|
8068
|
+
return response.content.slice(0, 8e3);
|
|
8069
|
+
}
|
|
8070
|
+
async function storeInMemory(contextName, topic, findings, synthesis, daemonUrl) {
|
|
8071
|
+
const storedIds = [];
|
|
8072
|
+
try {
|
|
8073
|
+
await fetch(`${daemonUrl}/api/contexts`, {
|
|
8074
|
+
method: "POST",
|
|
8075
|
+
headers: { "Content-Type": "application/json" },
|
|
8076
|
+
body: JSON.stringify({
|
|
8077
|
+
name: contextName,
|
|
8078
|
+
description: `Learned knowledge: ${topic}`
|
|
8079
|
+
})
|
|
8080
|
+
});
|
|
8081
|
+
} catch {
|
|
8082
|
+
}
|
|
8083
|
+
for (const finding of findings) {
|
|
8084
|
+
const content = `[Source: ${finding.source}]
|
|
8085
|
+
${finding.keyPoints.join("\n")}`;
|
|
8086
|
+
try {
|
|
8087
|
+
const resp = await fetch(`${daemonUrl}/api/episodes`, {
|
|
8088
|
+
method: "POST",
|
|
8089
|
+
headers: { "Content-Type": "application/json" },
|
|
8090
|
+
body: JSON.stringify({
|
|
8091
|
+
context_name: contextName,
|
|
8092
|
+
role: "system",
|
|
8093
|
+
content
|
|
8094
|
+
})
|
|
8095
|
+
});
|
|
8096
|
+
if (resp.ok) {
|
|
8097
|
+
const data = await resp.json();
|
|
8098
|
+
if (data.id) storedIds.push(data.id);
|
|
8099
|
+
}
|
|
8100
|
+
} catch {
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
try {
|
|
8104
|
+
const resp = await fetch(`${daemonUrl}/api/episodes`, {
|
|
8105
|
+
method: "POST",
|
|
8106
|
+
headers: { "Content-Type": "application/json" },
|
|
8107
|
+
body: JSON.stringify({
|
|
8108
|
+
context_name: contextName,
|
|
8109
|
+
role: "assistant",
|
|
8110
|
+
content: `[SYNTHESIS: ${topic}]
|
|
8111
|
+
${synthesis}`
|
|
8112
|
+
})
|
|
8113
|
+
});
|
|
8114
|
+
if (resp.ok) {
|
|
8115
|
+
const data = await resp.json();
|
|
8116
|
+
if (data.id) storedIds.push(data.id);
|
|
8117
|
+
}
|
|
8118
|
+
} catch {
|
|
8119
|
+
}
|
|
8120
|
+
try {
|
|
8121
|
+
await fetch(`${daemonUrl}/api/promotion/run?context=${encodeURIComponent(contextName)}`, {
|
|
8122
|
+
method: "POST"
|
|
8123
|
+
});
|
|
8124
|
+
} catch {
|
|
8125
|
+
}
|
|
8126
|
+
return storedIds;
|
|
8127
|
+
}
|
|
8128
|
+
function registerLearnTools(registry, config) {
|
|
8129
|
+
let llm = null;
|
|
8130
|
+
if (config.llmConfig) {
|
|
8131
|
+
try {
|
|
8132
|
+
llm = new LLMClient({
|
|
8133
|
+
...config.llmConfig,
|
|
8134
|
+
max_tokens: 2048
|
|
8135
|
+
});
|
|
8136
|
+
} catch {
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
let synthesisLlm = null;
|
|
8140
|
+
if (config.llmConfig) {
|
|
8141
|
+
try {
|
|
8142
|
+
synthesisLlm = new LLMClient({
|
|
8143
|
+
...config.llmConfig,
|
|
8144
|
+
max_tokens: 4096
|
|
8145
|
+
});
|
|
8146
|
+
} catch {
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
registry.register(
|
|
8150
|
+
"learn",
|
|
8151
|
+
[
|
|
8152
|
+
"Deeply research a topic and store organized knowledge in memory.",
|
|
8153
|
+
"Supports two modes: 'research' (web search + fetch) and 'codebase' (local/remote repo analysis).",
|
|
8154
|
+
"Findings are synthesized via LLM and stored as L2 episodes in a dedicated memory context,",
|
|
8155
|
+
"eligible for L3 promotion. Use this when you need thorough understanding of a topic."
|
|
8156
|
+
].join(" "),
|
|
8157
|
+
{
|
|
8158
|
+
type: "object",
|
|
8159
|
+
properties: {
|
|
8160
|
+
topic: {
|
|
8161
|
+
type: "string",
|
|
8162
|
+
description: "What to learn about (e.g., 'WebSocket protocol', '/path/to/repo', 'https://github.com/user/repo')"
|
|
8163
|
+
},
|
|
8164
|
+
type: {
|
|
8165
|
+
type: "string",
|
|
8166
|
+
enum: ["research", "codebase"],
|
|
8167
|
+
description: "Learning type: 'research' for web-based topics, 'codebase' for analyzing code (default: research)"
|
|
8168
|
+
},
|
|
8169
|
+
depth: {
|
|
8170
|
+
type: "string",
|
|
8171
|
+
enum: ["shallow", "deep"],
|
|
8172
|
+
description: "How many sources to explore: shallow (3 sources) or deep (6+ sources) (default: shallow)"
|
|
8173
|
+
},
|
|
8174
|
+
context: {
|
|
8175
|
+
type: "string",
|
|
8176
|
+
description: "Custom memory context name (default: auto-generated learn-{slug})"
|
|
8177
|
+
},
|
|
8178
|
+
focus_areas: {
|
|
8179
|
+
type: "array",
|
|
8180
|
+
items: { type: "string" },
|
|
8181
|
+
description: "Specific aspects to focus on (e.g., ['performance', 'security'])"
|
|
8182
|
+
},
|
|
8183
|
+
store: {
|
|
8184
|
+
type: "boolean",
|
|
8185
|
+
description: "Whether to persist findings in memory (default: true)"
|
|
8186
|
+
}
|
|
8187
|
+
},
|
|
8188
|
+
required: ["topic"]
|
|
8189
|
+
},
|
|
8190
|
+
async (params) => {
|
|
8191
|
+
const topic = params.topic;
|
|
8192
|
+
const type = params.type || "research";
|
|
8193
|
+
const depth = params.depth || "shallow";
|
|
8194
|
+
const contextName = params.context || `learn-${slugify(topic)}`;
|
|
8195
|
+
const focusAreas = params.focus_areas || [];
|
|
8196
|
+
const shouldStore = params.store !== false;
|
|
8197
|
+
if (!llm || !synthesisLlm) {
|
|
8198
|
+
return "Error: LLM not configured \u2014 learn tool requires an LLM for synthesis. Check your llm config.";
|
|
8199
|
+
}
|
|
8200
|
+
try {
|
|
8201
|
+
let sources;
|
|
8202
|
+
if (type === "codebase") {
|
|
8203
|
+
sources = await discoverCodebase(topic, registry);
|
|
8204
|
+
} else {
|
|
8205
|
+
sources = await discoverResearch(topic, depth, registry);
|
|
8206
|
+
}
|
|
8207
|
+
if (sources.length === 0) {
|
|
8208
|
+
return `No sources found for "${topic}". Try a different query or check connectivity.`;
|
|
8209
|
+
}
|
|
8210
|
+
let findings;
|
|
8211
|
+
if (type === "codebase") {
|
|
8212
|
+
findings = await exploreCodebase(topic, sources, depth, focusAreas, registry, llm);
|
|
8213
|
+
} else {
|
|
8214
|
+
findings = await exploreResearch(topic, sources, depth, focusAreas, registry, llm);
|
|
8215
|
+
}
|
|
8216
|
+
if (findings.length === 0) {
|
|
8217
|
+
return `Found ${sources.length} sources but could not extract meaningful findings for "${topic}".`;
|
|
8218
|
+
}
|
|
8219
|
+
const synthesis = await synthesize(topic, findings, focusAreas, synthesisLlm);
|
|
8220
|
+
let storageNote = "";
|
|
8221
|
+
if (shouldStore) {
|
|
8222
|
+
const storedIds = await storeInMemory(
|
|
8223
|
+
contextName,
|
|
8224
|
+
topic,
|
|
8225
|
+
findings,
|
|
8226
|
+
synthesis,
|
|
8227
|
+
config.memoryDaemonUrl
|
|
8228
|
+
);
|
|
8229
|
+
storageNote = storedIds.length > 0 ? `
|
|
8230
|
+
|
|
8231
|
+
---
|
|
8232
|
+
Stored ${storedIds.length} episodes in memory context "${contextName}". Use learn_recall to retrieve later.` : "\n\n---\nNote: Could not store in memory (daemon may be offline). Knowledge is shown above but not persisted.";
|
|
8233
|
+
}
|
|
8234
|
+
return `# Learned: ${topic}
|
|
8235
|
+
|
|
8236
|
+
Sources explored: ${findings.length}
|
|
8237
|
+
Type: ${type} | Depth: ${depth}
|
|
8238
|
+
|
|
8239
|
+
${synthesis}${storageNote}`;
|
|
8240
|
+
} catch (err) {
|
|
8241
|
+
return `Learn failed: ${err.message}`;
|
|
8242
|
+
}
|
|
8243
|
+
}
|
|
8244
|
+
);
|
|
8245
|
+
registry.register(
|
|
8246
|
+
"learn_recall",
|
|
8247
|
+
"Retrieve knowledge from a previous learn session. Searches across learn-* memory contexts for stored findings and synthesis.",
|
|
8248
|
+
{
|
|
8249
|
+
type: "object",
|
|
8250
|
+
properties: {
|
|
8251
|
+
topic: {
|
|
8252
|
+
type: "string",
|
|
8253
|
+
description: "Search query to find previously learned knowledge"
|
|
8254
|
+
},
|
|
8255
|
+
context: {
|
|
8256
|
+
type: "string",
|
|
8257
|
+
description: "Specific learn context to search (e.g., 'learn-websocket-protocol'). If omitted, searches all learn contexts."
|
|
8258
|
+
}
|
|
8259
|
+
},
|
|
8260
|
+
required: ["topic"]
|
|
8261
|
+
},
|
|
8262
|
+
async (params) => {
|
|
8263
|
+
const topic = params.topic;
|
|
8264
|
+
const context = params.context;
|
|
8265
|
+
const daemonUrl = config.memoryDaemonUrl;
|
|
8266
|
+
try {
|
|
8267
|
+
if (context) {
|
|
8268
|
+
const resp2 = await fetch(`${daemonUrl}/api/search`, {
|
|
8269
|
+
method: "POST",
|
|
8270
|
+
headers: { "Content-Type": "application/json" },
|
|
8271
|
+
body: JSON.stringify({ query: topic, context_name: context, top_k: 10 })
|
|
8272
|
+
});
|
|
8273
|
+
if (!resp2.ok) return `Memory search failed: ${resp2.status}`;
|
|
8274
|
+
const data2 = await resp2.json();
|
|
8275
|
+
if (!data2.results?.length) return `No learned knowledge found for "${topic}" in context "${context}".`;
|
|
8276
|
+
return data2.results.map((r, i) => `${i + 1}. [score: ${r.score.toFixed(3)}] [${r.role}]
|
|
8277
|
+
${r.content.slice(0, 500)}`).join("\n\n");
|
|
8278
|
+
}
|
|
8279
|
+
const resp = await fetch(`${daemonUrl}/api/search/cross-context`, {
|
|
8280
|
+
method: "POST",
|
|
8281
|
+
headers: { "Content-Type": "application/json" },
|
|
8282
|
+
body: JSON.stringify({ query: topic, top_k: 15 })
|
|
8283
|
+
});
|
|
8284
|
+
if (!resp.ok) return `Memory cross-search failed: ${resp.status}`;
|
|
8285
|
+
const data = await resp.json();
|
|
8286
|
+
const learnResults = data.results?.filter((r) => r.context_name.startsWith("learn-")) || [];
|
|
8287
|
+
if (learnResults.length === 0) {
|
|
8288
|
+
return `No previously learned knowledge found for "${topic}". Use the learn tool first.`;
|
|
8289
|
+
}
|
|
8290
|
+
return learnResults.map((r, i) => `${i + 1}. [score: ${r.score.toFixed(3)}] [${r.context_name}] [${r.role}]
|
|
8291
|
+
${r.content.slice(0, 500)}`).join("\n\n");
|
|
8292
|
+
} catch (err) {
|
|
8293
|
+
return `Memory daemon unreachable: ${err.message}`;
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
);
|
|
8297
|
+
}
|
|
8298
|
+
|
|
7354
8299
|
// packages/runtime/src/tools/register.ts
|
|
7355
8300
|
import { resolve as resolve18 } from "path";
|
|
7356
8301
|
import { mkdirSync as mkdirSync13, existsSync as existsSync18 } from "fs";
|
|
@@ -7381,6 +8326,11 @@ function registerAllTools(hivemindHome, config) {
|
|
|
7381
8326
|
registerMacOSTools(registry, workspaceDir);
|
|
7382
8327
|
registerDataTools(registry, workspaceDir);
|
|
7383
8328
|
registerCodingAgentTools(registry, workspaceDir);
|
|
8329
|
+
registerLearnTools(registry, {
|
|
8330
|
+
llmConfig: config?.llmConfig,
|
|
8331
|
+
memoryDaemonUrl: config?.memoryDaemonUrl || "http://localhost:3434",
|
|
8332
|
+
workspaceDir
|
|
8333
|
+
});
|
|
7384
8334
|
return registry;
|
|
7385
8335
|
}
|
|
7386
8336
|
|
|
@@ -7674,8 +8624,8 @@ async function startPipeline(configPath) {
|
|
|
7674
8624
|
}
|
|
7675
8625
|
}
|
|
7676
8626
|
const requestLogger = new RequestLogger(resolve20(dirname8(configPath), "data", "dashboard.db"));
|
|
7677
|
-
startDashboardServer(requestLogger, config.memory);
|
|
7678
8627
|
const agent = new Agent(config);
|
|
8628
|
+
startDashboardServer(requestLogger, config.memory, () => agent.getConversationHistories());
|
|
7679
8629
|
agent.setRequestLogger(requestLogger);
|
|
7680
8630
|
const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
|
|
7681
8631
|
const toolRegistry = registerAllTools(hivemindHome, {
|
|
@@ -7683,7 +8633,8 @@ async function startPipeline(configPath) {
|
|
|
7683
8633
|
workspace: config.agent.workspace || "workspace",
|
|
7684
8634
|
braveApiKey: process.env.BRAVE_API_KEY,
|
|
7685
8635
|
memoryDaemonUrl: config.memory.daemon_url,
|
|
7686
|
-
configPath
|
|
8636
|
+
configPath,
|
|
8637
|
+
llmConfig: config.llm
|
|
7687
8638
|
});
|
|
7688
8639
|
const workspaceDir = resolve20(hivemindHome, config.agent.workspace || "workspace");
|
|
7689
8640
|
const skillsEngine = new SkillsEngine(workspaceDir, toolRegistry);
|
|
@@ -8029,7 +8980,7 @@ var WorkerServer = class {
|
|
|
8029
8980
|
return this.handleHealth(res);
|
|
8030
8981
|
}
|
|
8031
8982
|
if (method === "POST" && path === "/assign") {
|
|
8032
|
-
const body = await
|
|
8983
|
+
const body = await readBody2(req);
|
|
8033
8984
|
return this.handleAssign(body, res);
|
|
8034
8985
|
}
|
|
8035
8986
|
if (method === "DELETE" && path.startsWith("/assign/")) {
|
|
@@ -8040,7 +8991,7 @@ var WorkerServer = class {
|
|
|
8040
8991
|
return this.handleStatus(res);
|
|
8041
8992
|
}
|
|
8042
8993
|
if (method === "POST" && path === "/sync/push") {
|
|
8043
|
-
const body = await
|
|
8994
|
+
const body = await readBody2(req);
|
|
8044
8995
|
return this.handleSyncPush(body, res);
|
|
8045
8996
|
}
|
|
8046
8997
|
sendJson(res, 404, { error: "Not found" });
|
|
@@ -8118,7 +9069,7 @@ var WorkerServer = class {
|
|
|
8118
9069
|
sendJson(res, 200, result);
|
|
8119
9070
|
}
|
|
8120
9071
|
};
|
|
8121
|
-
function
|
|
9072
|
+
function readBody2(req) {
|
|
8122
9073
|
return new Promise((resolve21, reject) => {
|
|
8123
9074
|
const chunks = [];
|
|
8124
9075
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -8464,4 +9415,4 @@ smol-toml/dist/index.js:
|
|
|
8464
9415
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
8465
9416
|
*)
|
|
8466
9417
|
*/
|
|
8467
|
-
//# sourceMappingURL=chunk-
|
|
9418
|
+
//# sourceMappingURL=chunk-OB6OXLPC.js.map
|