@sesamespace/hivemind 0.5.17 → 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.
- package/TOOL-USE-DESIGN.md +173 -0
- package/dist/{chunk-Z2FXPFKE.js → chunk-HPWW7KKB.js} +2 -2
- package/dist/{chunk-OTEMHDRU.js → chunk-I5DY3ULS.js} +2 -2
- package/dist/{chunk-OPOXV53N.js → chunk-LQ7CYHSQ.js} +468 -26
- package/dist/chunk-LQ7CYHSQ.js.map +1 -0
- package/dist/{chunk-KTOAREXT.js → chunk-OTIMWYTH.js} +2 -2
- package/dist/{chunk-2PCF2ADI.js → chunk-TBFM7HVN.js} +3 -3
- package/dist/commands/fleet.js +3 -3
- package/dist/commands/start.js +3 -3
- package/dist/commands/watchdog.js +3 -3
- package/dist/index.js +2 -2
- package/dist/main.js +5 -5
- package/dist/start.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OPOXV53N.js.map +0 -1
- /package/dist/{chunk-Z2FXPFKE.js.map → chunk-HPWW7KKB.js.map} +0 -0
- /package/dist/{chunk-OTEMHDRU.js.map → chunk-I5DY3ULS.js.map} +0 -0
- /package/dist/{chunk-KTOAREXT.js.map → chunk-OTIMWYTH.js.map} +0 -0
- /package/dist/{chunk-2PCF2ADI.js.map → chunk-TBFM7HVN.js.map} +0 -0
|
@@ -16,28 +16,46 @@ var LLMClient = class {
|
|
|
16
16
|
this.temperature = config.temperature;
|
|
17
17
|
this.apiKey = config.api_key ?? "";
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Simple chat completion (no tools). Backwards compatible.
|
|
21
|
+
*/
|
|
19
22
|
async chat(messages) {
|
|
23
|
+
return this.chatWithTools(messages);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Chat completion with optional tool definitions.
|
|
27
|
+
* Returns tool_calls if the model wants to use tools.
|
|
28
|
+
*/
|
|
29
|
+
async chatWithTools(messages, tools) {
|
|
30
|
+
const body = {
|
|
31
|
+
model: this.model,
|
|
32
|
+
messages,
|
|
33
|
+
max_tokens: this.maxTokens,
|
|
34
|
+
temperature: this.temperature
|
|
35
|
+
};
|
|
36
|
+
if (tools && tools.length > 0) {
|
|
37
|
+
body.tools = tools;
|
|
38
|
+
body.tool_choice = "auto";
|
|
39
|
+
}
|
|
20
40
|
const resp = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
21
41
|
method: "POST",
|
|
22
42
|
headers: {
|
|
23
43
|
"Content-Type": "application/json",
|
|
24
44
|
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
25
45
|
},
|
|
26
|
-
body: JSON.stringify(
|
|
27
|
-
model: this.model,
|
|
28
|
-
messages,
|
|
29
|
-
max_tokens: this.maxTokens,
|
|
30
|
-
temperature: this.temperature
|
|
31
|
-
})
|
|
46
|
+
body: JSON.stringify(body)
|
|
32
47
|
});
|
|
33
48
|
if (!resp.ok) {
|
|
34
|
-
const
|
|
35
|
-
throw new Error(`LLM request failed: ${resp.status} ${
|
|
49
|
+
const text = await resp.text();
|
|
50
|
+
throw new Error(`LLM request failed: ${resp.status} ${text}`);
|
|
36
51
|
}
|
|
37
52
|
const data = await resp.json();
|
|
53
|
+
const choice = data.choices[0];
|
|
38
54
|
return {
|
|
39
|
-
content:
|
|
55
|
+
content: choice.message.content ?? "",
|
|
40
56
|
model: data.model,
|
|
57
|
+
tool_calls: choice.message.tool_calls,
|
|
58
|
+
finish_reason: choice.finish_reason,
|
|
41
59
|
usage: data.usage
|
|
42
60
|
};
|
|
43
61
|
}
|
|
@@ -618,11 +636,13 @@ var Agent = class {
|
|
|
618
636
|
llm;
|
|
619
637
|
memory;
|
|
620
638
|
contextManager;
|
|
639
|
+
toolRegistry = null;
|
|
621
640
|
// Per-context conversation histories
|
|
622
641
|
conversationHistories = /* @__PURE__ */ new Map();
|
|
623
642
|
messageCount = 0;
|
|
624
643
|
PROMOTION_INTERVAL = 10;
|
|
625
644
|
// Run promotion every N messages
|
|
645
|
+
MAX_TOOL_ITERATIONS = 25;
|
|
626
646
|
requestLogger = null;
|
|
627
647
|
constructor(config, contextName = "global") {
|
|
628
648
|
this.config = config;
|
|
@@ -633,6 +653,13 @@ var Agent = class {
|
|
|
633
653
|
this.contextManager.switchContext(contextName);
|
|
634
654
|
}
|
|
635
655
|
}
|
|
656
|
+
setToolRegistry(registry) {
|
|
657
|
+
this.toolRegistry = registry;
|
|
658
|
+
const toolCount = registry.getDefinitions().length;
|
|
659
|
+
if (toolCount > 0) {
|
|
660
|
+
console.log(`[hivemind] Tool use enabled: ${toolCount} tools registered`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
636
663
|
setRequestLogger(logger) {
|
|
637
664
|
this.requestLogger = logger;
|
|
638
665
|
}
|
|
@@ -668,7 +695,31 @@ var Agent = class {
|
|
|
668
695
|
const systemPromptResult = buildSystemPrompt(this.config.agent, relevantEpisodes, contextName, l3Knowledge);
|
|
669
696
|
const messages = buildMessages(systemPromptResult.text, conversationHistory, userMessage);
|
|
670
697
|
const llmStart = Date.now();
|
|
671
|
-
const
|
|
698
|
+
const toolDefs = this.toolRegistry?.getDefinitions() ?? [];
|
|
699
|
+
let response = await this.llm.chatWithTools(messages, toolDefs.length > 0 ? toolDefs : void 0);
|
|
700
|
+
let iterations = 0;
|
|
701
|
+
while (response.tool_calls && response.tool_calls.length > 0 && iterations < this.MAX_TOOL_ITERATIONS) {
|
|
702
|
+
iterations++;
|
|
703
|
+
messages.push({
|
|
704
|
+
role: "assistant",
|
|
705
|
+
content: response.content,
|
|
706
|
+
tool_calls: response.tool_calls
|
|
707
|
+
});
|
|
708
|
+
for (const call of response.tool_calls) {
|
|
709
|
+
console.log(`[tools] ${call.function.name}(${call.function.arguments.slice(0, 100)})`);
|
|
710
|
+
const result = await this.toolRegistry.executeCall(call);
|
|
711
|
+
messages.push({
|
|
712
|
+
role: "tool",
|
|
713
|
+
content: result.content,
|
|
714
|
+
tool_call_id: result.tool_call_id
|
|
715
|
+
});
|
|
716
|
+
console.log(`[tools] ${call.function.name} \u2192 ${result.content.slice(0, 100)}${result.content.length > 100 ? "..." : ""}`);
|
|
717
|
+
}
|
|
718
|
+
response = await this.llm.chatWithTools(messages, toolDefs);
|
|
719
|
+
}
|
|
720
|
+
if (iterations > 0) {
|
|
721
|
+
console.log(`[tools] Completed after ${iterations} tool iteration(s)`);
|
|
722
|
+
}
|
|
672
723
|
const latencyMs = Date.now() - llmStart;
|
|
673
724
|
if (this.requestLogger) {
|
|
674
725
|
try {
|
|
@@ -2247,14 +2298,398 @@ function startDashboardServer(requestLogger, memoryConfig) {
|
|
|
2247
2298
|
});
|
|
2248
2299
|
}
|
|
2249
2300
|
|
|
2301
|
+
// packages/runtime/src/tool-registry.ts
|
|
2302
|
+
var ToolRegistry = class {
|
|
2303
|
+
tools = /* @__PURE__ */ new Map();
|
|
2304
|
+
register(name, description, parameters, executor) {
|
|
2305
|
+
this.tools.set(name, {
|
|
2306
|
+
definition: {
|
|
2307
|
+
type: "function",
|
|
2308
|
+
function: { name, description, parameters }
|
|
2309
|
+
},
|
|
2310
|
+
executor
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
getDefinitions() {
|
|
2314
|
+
return Array.from(this.tools.values()).map((t) => t.definition);
|
|
2315
|
+
}
|
|
2316
|
+
has(name) {
|
|
2317
|
+
return this.tools.has(name);
|
|
2318
|
+
}
|
|
2319
|
+
async execute(name, params) {
|
|
2320
|
+
const tool = this.tools.get(name);
|
|
2321
|
+
if (!tool) {
|
|
2322
|
+
return `Error: Unknown tool "${name}"`;
|
|
2323
|
+
}
|
|
2324
|
+
try {
|
|
2325
|
+
return await tool.executor(params);
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
return `Error executing ${name}: ${err.message}`;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
async executeCall(call) {
|
|
2331
|
+
let params = {};
|
|
2332
|
+
try {
|
|
2333
|
+
params = JSON.parse(call.function.arguments);
|
|
2334
|
+
} catch {
|
|
2335
|
+
return {
|
|
2336
|
+
tool_call_id: call.id,
|
|
2337
|
+
role: "tool",
|
|
2338
|
+
content: `Error: Invalid JSON arguments for ${call.function.name}`
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
const result = await this.execute(call.function.name, params);
|
|
2342
|
+
return {
|
|
2343
|
+
tool_call_id: call.id,
|
|
2344
|
+
role: "tool",
|
|
2345
|
+
content: result
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
// packages/runtime/src/tools/shell.ts
|
|
2351
|
+
import { execSync } from "child_process";
|
|
2352
|
+
import { resolve as resolve5 } from "path";
|
|
2353
|
+
var MAX_OUTPUT = 5e4;
|
|
2354
|
+
function registerShellTool(registry, workspaceDir) {
|
|
2355
|
+
registry.register(
|
|
2356
|
+
"shell",
|
|
2357
|
+
"Execute a shell command and return stdout/stderr. Use for running programs, checking status, installing packages, git operations, etc.",
|
|
2358
|
+
{
|
|
2359
|
+
type: "object",
|
|
2360
|
+
properties: {
|
|
2361
|
+
command: {
|
|
2362
|
+
type: "string",
|
|
2363
|
+
description: "The shell command to execute"
|
|
2364
|
+
},
|
|
2365
|
+
workdir: {
|
|
2366
|
+
type: "string",
|
|
2367
|
+
description: "Working directory (relative to workspace). Defaults to workspace root."
|
|
2368
|
+
},
|
|
2369
|
+
timeout: {
|
|
2370
|
+
type: "number",
|
|
2371
|
+
description: "Timeout in seconds. Default: 30"
|
|
2372
|
+
}
|
|
2373
|
+
},
|
|
2374
|
+
required: ["command"]
|
|
2375
|
+
},
|
|
2376
|
+
async (params) => {
|
|
2377
|
+
const command = params.command;
|
|
2378
|
+
const timeout = (params.timeout || 30) * 1e3;
|
|
2379
|
+
const cwd = params.workdir ? resolve5(workspaceDir, params.workdir) : workspaceDir;
|
|
2380
|
+
try {
|
|
2381
|
+
const output = execSync(command, {
|
|
2382
|
+
cwd,
|
|
2383
|
+
timeout,
|
|
2384
|
+
encoding: "utf-8",
|
|
2385
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2386
|
+
// 10MB
|
|
2387
|
+
shell: "/bin/sh",
|
|
2388
|
+
env: { ...process.env, PATH: `/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${process.env.PATH}` }
|
|
2389
|
+
});
|
|
2390
|
+
const trimmed = output.length > MAX_OUTPUT ? output.slice(0, MAX_OUTPUT) + `
|
|
2391
|
+
... (truncated, ${output.length} total chars)` : output;
|
|
2392
|
+
return trimmed || "(no output)";
|
|
2393
|
+
} catch (err) {
|
|
2394
|
+
const stderr = err.stderr?.toString() || "";
|
|
2395
|
+
const stdout = err.stdout?.toString() || "";
|
|
2396
|
+
const output = (stdout + "\n" + stderr).trim();
|
|
2397
|
+
const code = err.status ?? "unknown";
|
|
2398
|
+
return `Command failed (exit code ${code}):
|
|
2399
|
+
${output || err.message}`;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// packages/runtime/src/tools/files.ts
|
|
2406
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
2407
|
+
import { resolve as resolve6, dirname as dirname5 } from "path";
|
|
2408
|
+
var MAX_READ = 1e5;
|
|
2409
|
+
function registerFileTools(registry, workspaceDir) {
|
|
2410
|
+
registry.register(
|
|
2411
|
+
"read_file",
|
|
2412
|
+
"Read the contents of a file. Returns the text content. Use offset/limit for large files.",
|
|
2413
|
+
{
|
|
2414
|
+
type: "object",
|
|
2415
|
+
properties: {
|
|
2416
|
+
path: {
|
|
2417
|
+
type: "string",
|
|
2418
|
+
description: "File path (relative to workspace or absolute)"
|
|
2419
|
+
},
|
|
2420
|
+
offset: {
|
|
2421
|
+
type: "number",
|
|
2422
|
+
description: "Line number to start reading from (1-indexed)"
|
|
2423
|
+
},
|
|
2424
|
+
limit: {
|
|
2425
|
+
type: "number",
|
|
2426
|
+
description: "Maximum number of lines to read"
|
|
2427
|
+
}
|
|
2428
|
+
},
|
|
2429
|
+
required: ["path"]
|
|
2430
|
+
},
|
|
2431
|
+
async (params) => {
|
|
2432
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2433
|
+
if (!existsSync4(filePath)) {
|
|
2434
|
+
return `Error: File not found: ${filePath}`;
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
let content = readFileSync6(filePath, "utf-8");
|
|
2438
|
+
if (params.offset || params.limit) {
|
|
2439
|
+
const lines = content.split("\n");
|
|
2440
|
+
const start = (params.offset || 1) - 1;
|
|
2441
|
+
const end = params.limit ? start + params.limit : lines.length;
|
|
2442
|
+
content = lines.slice(start, end).join("\n");
|
|
2443
|
+
}
|
|
2444
|
+
if (content.length > MAX_READ) {
|
|
2445
|
+
content = content.slice(0, MAX_READ) + `
|
|
2446
|
+
... (truncated, ${content.length} total chars)`;
|
|
2447
|
+
}
|
|
2448
|
+
return content || "(empty file)";
|
|
2449
|
+
} catch (err) {
|
|
2450
|
+
return `Error reading file: ${err.message}`;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
);
|
|
2454
|
+
registry.register(
|
|
2455
|
+
"write_file",
|
|
2456
|
+
"Write content to a file. Creates the file and parent directories if they don't exist. Overwrites existing content.",
|
|
2457
|
+
{
|
|
2458
|
+
type: "object",
|
|
2459
|
+
properties: {
|
|
2460
|
+
path: {
|
|
2461
|
+
type: "string",
|
|
2462
|
+
description: "File path (relative to workspace or absolute)"
|
|
2463
|
+
},
|
|
2464
|
+
content: {
|
|
2465
|
+
type: "string",
|
|
2466
|
+
description: "Content to write to the file"
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
required: ["path", "content"]
|
|
2470
|
+
},
|
|
2471
|
+
async (params) => {
|
|
2472
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2473
|
+
try {
|
|
2474
|
+
const dir = dirname5(filePath);
|
|
2475
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
2476
|
+
writeFileSync2(filePath, params.content);
|
|
2477
|
+
return `Written ${params.content.length} bytes to ${filePath}`;
|
|
2478
|
+
} catch (err) {
|
|
2479
|
+
return `Error writing file: ${err.message}`;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
);
|
|
2483
|
+
registry.register(
|
|
2484
|
+
"edit_file",
|
|
2485
|
+
"Edit a file by finding and replacing exact text. The old text must match exactly (including whitespace).",
|
|
2486
|
+
{
|
|
2487
|
+
type: "object",
|
|
2488
|
+
properties: {
|
|
2489
|
+
path: {
|
|
2490
|
+
type: "string",
|
|
2491
|
+
description: "File path (relative to workspace or absolute)"
|
|
2492
|
+
},
|
|
2493
|
+
old_text: {
|
|
2494
|
+
type: "string",
|
|
2495
|
+
description: "Exact text to find and replace"
|
|
2496
|
+
},
|
|
2497
|
+
new_text: {
|
|
2498
|
+
type: "string",
|
|
2499
|
+
description: "New text to replace with"
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
required: ["path", "old_text", "new_text"]
|
|
2503
|
+
},
|
|
2504
|
+
async (params) => {
|
|
2505
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2506
|
+
if (!existsSync4(filePath)) {
|
|
2507
|
+
return `Error: File not found: ${filePath}`;
|
|
2508
|
+
}
|
|
2509
|
+
try {
|
|
2510
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2511
|
+
const oldText = params.old_text;
|
|
2512
|
+
const newText = params.new_text;
|
|
2513
|
+
if (!content.includes(oldText)) {
|
|
2514
|
+
return `Error: Could not find the exact text to replace in ${filePath}`;
|
|
2515
|
+
}
|
|
2516
|
+
const updated = content.replace(oldText, newText);
|
|
2517
|
+
writeFileSync2(filePath, updated);
|
|
2518
|
+
return `Edited ${filePath}: replaced ${oldText.length} chars with ${newText.length} chars`;
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
return `Error editing file: ${err.message}`;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
);
|
|
2524
|
+
registry.register(
|
|
2525
|
+
"list_files",
|
|
2526
|
+
"List files and directories in a path.",
|
|
2527
|
+
{
|
|
2528
|
+
type: "object",
|
|
2529
|
+
properties: {
|
|
2530
|
+
path: {
|
|
2531
|
+
type: "string",
|
|
2532
|
+
description: "Directory path (relative to workspace or absolute). Defaults to workspace root."
|
|
2533
|
+
}
|
|
2534
|
+
},
|
|
2535
|
+
required: []
|
|
2536
|
+
},
|
|
2537
|
+
async (params) => {
|
|
2538
|
+
const { readdirSync: readdirSync2, statSync } = await import("fs");
|
|
2539
|
+
const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
|
|
2540
|
+
if (!existsSync4(dirPath)) {
|
|
2541
|
+
return `Error: Directory not found: ${dirPath}`;
|
|
2542
|
+
}
|
|
2543
|
+
try {
|
|
2544
|
+
const entries = readdirSync2(dirPath);
|
|
2545
|
+
const results = [];
|
|
2546
|
+
for (const entry of entries) {
|
|
2547
|
+
if (entry.startsWith(".")) continue;
|
|
2548
|
+
try {
|
|
2549
|
+
const stat = statSync(resolve6(dirPath, entry));
|
|
2550
|
+
results.push(stat.isDirectory() ? `${entry}/` : entry);
|
|
2551
|
+
} catch {
|
|
2552
|
+
results.push(entry);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return results.join("\n") || "(empty directory)";
|
|
2556
|
+
} catch (err) {
|
|
2557
|
+
return `Error listing directory: ${err.message}`;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
function resolvePath(workspace, path) {
|
|
2563
|
+
if (path.startsWith("/") || path.startsWith("~")) {
|
|
2564
|
+
return path.replace(/^~/, process.env.HOME || "/root");
|
|
2565
|
+
}
|
|
2566
|
+
return resolve6(workspace, path);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// packages/runtime/src/tools/web.ts
|
|
2570
|
+
function registerWebTools(registry, config) {
|
|
2571
|
+
registry.register(
|
|
2572
|
+
"web_search",
|
|
2573
|
+
"Search the web using Brave Search API. Returns titles, URLs, and snippets.",
|
|
2574
|
+
{
|
|
2575
|
+
type: "object",
|
|
2576
|
+
properties: {
|
|
2577
|
+
query: {
|
|
2578
|
+
type: "string",
|
|
2579
|
+
description: "Search query string"
|
|
2580
|
+
},
|
|
2581
|
+
count: {
|
|
2582
|
+
type: "number",
|
|
2583
|
+
description: "Number of results (1-10, default 5)"
|
|
2584
|
+
}
|
|
2585
|
+
},
|
|
2586
|
+
required: ["query"]
|
|
2587
|
+
},
|
|
2588
|
+
async (params) => {
|
|
2589
|
+
const apiKey = config?.braveApiKey || process.env.BRAVE_API_KEY;
|
|
2590
|
+
if (!apiKey) {
|
|
2591
|
+
return "Error: No Brave Search API key configured. Set BRAVE_API_KEY env var.";
|
|
2592
|
+
}
|
|
2593
|
+
const query = params.query;
|
|
2594
|
+
const count = Math.min(Math.max(params.count || 5, 1), 10);
|
|
2595
|
+
try {
|
|
2596
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`;
|
|
2597
|
+
const resp = await fetch(url, {
|
|
2598
|
+
headers: { "X-Subscription-Token": apiKey, Accept: "application/json" }
|
|
2599
|
+
});
|
|
2600
|
+
if (!resp.ok) {
|
|
2601
|
+
return `Search failed: ${resp.status} ${await resp.text()}`;
|
|
2602
|
+
}
|
|
2603
|
+
const data = await resp.json();
|
|
2604
|
+
const results = data.web?.results || [];
|
|
2605
|
+
if (results.length === 0) return "No results found.";
|
|
2606
|
+
return results.map((r, i) => `${i + 1}. **${r.title}**
|
|
2607
|
+
${r.url}
|
|
2608
|
+
${r.description}`).join("\n\n");
|
|
2609
|
+
} catch (err) {
|
|
2610
|
+
return `Search error: ${err.message}`;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
);
|
|
2614
|
+
registry.register(
|
|
2615
|
+
"web_fetch",
|
|
2616
|
+
"Fetch a URL and extract readable content as markdown/text. Good for reading articles, docs, and web pages.",
|
|
2617
|
+
{
|
|
2618
|
+
type: "object",
|
|
2619
|
+
properties: {
|
|
2620
|
+
url: {
|
|
2621
|
+
type: "string",
|
|
2622
|
+
description: "HTTP or HTTPS URL to fetch"
|
|
2623
|
+
},
|
|
2624
|
+
max_chars: {
|
|
2625
|
+
type: "number",
|
|
2626
|
+
description: "Maximum characters to return (default 20000)"
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2629
|
+
required: ["url"]
|
|
2630
|
+
},
|
|
2631
|
+
async (params) => {
|
|
2632
|
+
const url = params.url;
|
|
2633
|
+
const maxChars = params.max_chars || 2e4;
|
|
2634
|
+
try {
|
|
2635
|
+
const resp = await fetch(url, {
|
|
2636
|
+
headers: {
|
|
2637
|
+
"User-Agent": "Hivemind/1.0 (Agent Web Fetcher)",
|
|
2638
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json"
|
|
2639
|
+
},
|
|
2640
|
+
redirect: "follow"
|
|
2641
|
+
});
|
|
2642
|
+
if (!resp.ok) {
|
|
2643
|
+
return `Fetch failed: ${resp.status} ${resp.statusText}`;
|
|
2644
|
+
}
|
|
2645
|
+
let text = await resp.text();
|
|
2646
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
2647
|
+
if (contentType.includes("html")) {
|
|
2648
|
+
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
2649
|
+
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
2650
|
+
text = text.replace(/<[^>]+>/g, " ");
|
|
2651
|
+
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2652
|
+
text = text.replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ");
|
|
2653
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
2654
|
+
}
|
|
2655
|
+
if (text.length > maxChars) {
|
|
2656
|
+
text = text.slice(0, maxChars) + `
|
|
2657
|
+
... (truncated, ${text.length} total chars)`;
|
|
2658
|
+
}
|
|
2659
|
+
return text || "(empty response)";
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
return `Fetch error: ${err.message}`;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// packages/runtime/src/tools/register.ts
|
|
2668
|
+
import { resolve as resolve7 } from "path";
|
|
2669
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
2670
|
+
function registerAllTools(hivemindHome, config) {
|
|
2671
|
+
const registry = new ToolRegistry();
|
|
2672
|
+
if (config?.enabled === false) {
|
|
2673
|
+
return registry;
|
|
2674
|
+
}
|
|
2675
|
+
const workspaceDir = resolve7(hivemindHome, config?.workspace || "workspace");
|
|
2676
|
+
if (!existsSync5(workspaceDir)) {
|
|
2677
|
+
mkdirSync3(workspaceDir, { recursive: true });
|
|
2678
|
+
}
|
|
2679
|
+
registerShellTool(registry, workspaceDir);
|
|
2680
|
+
registerFileTools(registry, workspaceDir);
|
|
2681
|
+
registerWebTools(registry, { braveApiKey: config?.braveApiKey });
|
|
2682
|
+
return registry;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2250
2685
|
// packages/runtime/src/pipeline.ts
|
|
2251
|
-
import { readFileSync as
|
|
2252
|
-
import { resolve as
|
|
2686
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
2687
|
+
import { resolve as resolve8, dirname as dirname6 } from "path";
|
|
2253
2688
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2254
2689
|
var PACKAGE_VERSION = "unknown";
|
|
2255
2690
|
try {
|
|
2256
|
-
const __dirname2 =
|
|
2257
|
-
const pkg = JSON.parse(
|
|
2691
|
+
const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
|
|
2692
|
+
const pkg = JSON.parse(readFileSync7(resolve8(__dirname2, "../package.json"), "utf-8"));
|
|
2258
2693
|
PACKAGE_VERSION = pkg.version ?? "unknown";
|
|
2259
2694
|
} catch {
|
|
2260
2695
|
}
|
|
@@ -2285,7 +2720,7 @@ function startHealthServer(port) {
|
|
|
2285
2720
|
return server;
|
|
2286
2721
|
}
|
|
2287
2722
|
function writePidFile(path) {
|
|
2288
|
-
|
|
2723
|
+
writeFileSync3(path, String(process.pid));
|
|
2289
2724
|
console.log(`[hivemind] PID file written: ${path}`);
|
|
2290
2725
|
}
|
|
2291
2726
|
function cleanupPidFile(path) {
|
|
@@ -2316,10 +2751,17 @@ async function startPipeline(configPath) {
|
|
|
2316
2751
|
memoryConnected = true;
|
|
2317
2752
|
console.log("[hivemind] Memory daemon connected");
|
|
2318
2753
|
}
|
|
2319
|
-
const requestLogger = new RequestLogger(
|
|
2754
|
+
const requestLogger = new RequestLogger(resolve8(dirname6(configPath), "data", "dashboard.db"));
|
|
2320
2755
|
startDashboardServer(requestLogger, config.memory);
|
|
2321
2756
|
const agent = new Agent(config);
|
|
2322
2757
|
agent.setRequestLogger(requestLogger);
|
|
2758
|
+
const hivemindHome = process.env.HIVEMIND_HOME || resolve8(process.env.HOME || "/root", "hivemind");
|
|
2759
|
+
const toolRegistry = registerAllTools(hivemindHome, {
|
|
2760
|
+
enabled: true,
|
|
2761
|
+
workspace: config.agent.workspace || "workspace",
|
|
2762
|
+
braveApiKey: process.env.BRAVE_API_KEY
|
|
2763
|
+
});
|
|
2764
|
+
agent.setToolRegistry(toolRegistry);
|
|
2323
2765
|
console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
|
|
2324
2766
|
if (config.sesame.api_key) {
|
|
2325
2767
|
await startSesameLoop(config, agent);
|
|
@@ -2467,8 +2909,8 @@ ${response.content}
|
|
|
2467
2909
|
console.error("Error:", err.message);
|
|
2468
2910
|
}
|
|
2469
2911
|
});
|
|
2470
|
-
return new Promise((
|
|
2471
|
-
rl.on("close",
|
|
2912
|
+
return new Promise((resolve9) => {
|
|
2913
|
+
rl.on("close", resolve9);
|
|
2472
2914
|
});
|
|
2473
2915
|
}
|
|
2474
2916
|
|
|
@@ -2497,20 +2939,20 @@ var WorkerServer = class {
|
|
|
2497
2939
|
}
|
|
2498
2940
|
/** Start listening. */
|
|
2499
2941
|
async start() {
|
|
2500
|
-
return new Promise((
|
|
2942
|
+
return new Promise((resolve9, reject) => {
|
|
2501
2943
|
this.server = createServer3((req, res) => this.handleRequest(req, res));
|
|
2502
2944
|
this.server.on("error", reject);
|
|
2503
|
-
this.server.listen(this.port, () =>
|
|
2945
|
+
this.server.listen(this.port, () => resolve9());
|
|
2504
2946
|
});
|
|
2505
2947
|
}
|
|
2506
2948
|
/** Stop the server. */
|
|
2507
2949
|
async stop() {
|
|
2508
|
-
return new Promise((
|
|
2950
|
+
return new Promise((resolve9) => {
|
|
2509
2951
|
if (!this.server) {
|
|
2510
|
-
|
|
2952
|
+
resolve9();
|
|
2511
2953
|
return;
|
|
2512
2954
|
}
|
|
2513
|
-
this.server.close(() =>
|
|
2955
|
+
this.server.close(() => resolve9());
|
|
2514
2956
|
});
|
|
2515
2957
|
}
|
|
2516
2958
|
getPort() {
|
|
@@ -2633,10 +3075,10 @@ var WorkerServer = class {
|
|
|
2633
3075
|
}
|
|
2634
3076
|
};
|
|
2635
3077
|
function readBody(req) {
|
|
2636
|
-
return new Promise((
|
|
3078
|
+
return new Promise((resolve9, reject) => {
|
|
2637
3079
|
const chunks = [];
|
|
2638
3080
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
2639
|
-
req.on("end", () =>
|
|
3081
|
+
req.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
|
|
2640
3082
|
req.on("error", reject);
|
|
2641
3083
|
});
|
|
2642
3084
|
}
|
|
@@ -2963,4 +3405,4 @@ smol-toml/dist/index.js:
|
|
|
2963
3405
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
2964
3406
|
*)
|
|
2965
3407
|
*/
|
|
2966
|
-
//# sourceMappingURL=chunk-
|
|
3408
|
+
//# sourceMappingURL=chunk-LQ7CYHSQ.js.map
|