@sesamespace/hivemind 0.5.17 → 0.6.1
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-OPOXV53N.js → chunk-7YHRRM5B.js} +622 -218
- package/dist/chunk-7YHRRM5B.js.map +1 -0
- package/dist/{chunk-KTOAREXT.js → chunk-D3P3TJX4.js} +2 -2
- package/dist/{chunk-OTEMHDRU.js → chunk-EL4FSJBK.js} +2 -2
- package/dist/{chunk-Z2FXPFKE.js → chunk-F4C7TIEX.js} +2 -2
- package/dist/{chunk-2PCF2ADI.js → chunk-HCA2NYWH.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-KTOAREXT.js.map → chunk-D3P3TJX4.js.map} +0 -0
- /package/dist/{chunk-OTEMHDRU.js.map → chunk-EL4FSJBK.js.map} +0 -0
- /package/dist/{chunk-Z2FXPFKE.js.map → chunk-F4C7TIEX.js.map} +0 -0
- /package/dist/{chunk-2PCF2ADI.js.map → chunk-HCA2NYWH.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 {
|
|
@@ -751,192 +802,6 @@ var Agent = class {
|
|
|
751
802
|
let stripped = message.replace(/^\[.+?\s+in\s+group\s+chat\]:\s*/, "");
|
|
752
803
|
stripped = stripped.replace(/^\[.+?\]:\s*/, "");
|
|
753
804
|
const cmdText = stripped;
|
|
754
|
-
if (/^hm[:\s]status\b/i.test(cmdText)) {
|
|
755
|
-
const memoryOk = await this.memory.healthCheck();
|
|
756
|
-
const contexts = this.contextManager.listContexts();
|
|
757
|
-
let contextStats = "";
|
|
758
|
-
try {
|
|
759
|
-
const daemonContexts = await this.memory.listContexts();
|
|
760
|
-
contextStats = daemonContexts.map((c) => ` - ${c.name}: ${c.episode_count} episodes`).join("\n");
|
|
761
|
-
} catch {
|
|
762
|
-
contextStats = " (unable to query memory daemon)";
|
|
763
|
-
}
|
|
764
|
-
const response = [
|
|
765
|
-
`## Agent Status`,
|
|
766
|
-
``,
|
|
767
|
-
`**Name:** ${this.config.agent.name}`,
|
|
768
|
-
`**Active Context:** ${activeCtx}`,
|
|
769
|
-
`**Memory Daemon:** ${memoryOk ? "\u2705 connected" : "\u274C unreachable"} (${this.config.memory.daemon_url})`,
|
|
770
|
-
`**Model:** ${this.config.llm.model}`,
|
|
771
|
-
`**Temperature:** ${this.config.llm.temperature}`,
|
|
772
|
-
`**Max Tokens:** ${this.config.llm.max_tokens}`,
|
|
773
|
-
`**Memory Top-K:** ${this.config.memory.top_k}`,
|
|
774
|
-
`**Messages Processed:** ${this.messageCount}`,
|
|
775
|
-
`**L1 Contexts in Memory:** ${this.conversationHistories.size}`,
|
|
776
|
-
``,
|
|
777
|
-
`### Contexts`,
|
|
778
|
-
contextStats
|
|
779
|
-
].join("\n");
|
|
780
|
-
return { content: response, model: "system", context: activeCtx };
|
|
781
|
-
}
|
|
782
|
-
if (/^hm[:\s]memory\s+stats\b/i.test(cmdText)) {
|
|
783
|
-
try {
|
|
784
|
-
const contexts = await this.memory.listContexts();
|
|
785
|
-
let totalEpisodes = 0;
|
|
786
|
-
let totalL3 = 0;
|
|
787
|
-
let lines = [];
|
|
788
|
-
for (const ctx of contexts) {
|
|
789
|
-
totalEpisodes += ctx.episode_count;
|
|
790
|
-
let l3Count = 0;
|
|
791
|
-
try {
|
|
792
|
-
const l3 = await this.memory.getL3Knowledge(ctx.name);
|
|
793
|
-
l3Count = l3.length;
|
|
794
|
-
totalL3 += l3Count;
|
|
795
|
-
} catch {
|
|
796
|
-
}
|
|
797
|
-
lines.push(` - **${ctx.name}**: ${ctx.episode_count} L2 episodes, ${l3Count} L3 entries`);
|
|
798
|
-
}
|
|
799
|
-
const response = [
|
|
800
|
-
`## Memory Stats`,
|
|
801
|
-
``,
|
|
802
|
-
`**Total L2 Episodes:** ${totalEpisodes}`,
|
|
803
|
-
`**Total L3 Knowledge:** ${totalL3}`,
|
|
804
|
-
`**Contexts:** ${contexts.length}`,
|
|
805
|
-
``,
|
|
806
|
-
`### Per Context`,
|
|
807
|
-
...lines
|
|
808
|
-
].join("\n");
|
|
809
|
-
return { content: response, model: "system", context: activeCtx };
|
|
810
|
-
} catch (err) {
|
|
811
|
-
return { content: `Memory stats failed: ${err.message}`, model: "system", context: activeCtx };
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
const memSearchMatch = cmdText.match(/^hm[:\s]memory\s+search\s+(.+)/i);
|
|
815
|
-
if (memSearchMatch) {
|
|
816
|
-
const query = memSearchMatch[1].trim();
|
|
817
|
-
try {
|
|
818
|
-
const results = await this.memory.search(query, activeCtx, this.config.memory.top_k);
|
|
819
|
-
if (results.length === 0) {
|
|
820
|
-
return { content: `No memories found for "${query}" in context "${activeCtx}".`, model: "system", context: activeCtx };
|
|
821
|
-
}
|
|
822
|
-
let response = `## Memory Search: "${query}"
|
|
823
|
-
|
|
824
|
-
`;
|
|
825
|
-
for (const ep of results) {
|
|
826
|
-
const ctxLabel = ep.context_name !== activeCtx ? ` [from: ${ep.context_name}]` : "";
|
|
827
|
-
response += `- **[${ep.role}]** (score: ${ep.score.toFixed(3)})${ctxLabel} ${ep.content.slice(0, 300)}${ep.content.length > 300 ? "..." : ""}
|
|
828
|
-
`;
|
|
829
|
-
}
|
|
830
|
-
return { content: response, model: "system", context: activeCtx };
|
|
831
|
-
} catch (err) {
|
|
832
|
-
return { content: `Memory search failed: ${err.message}`, model: "system", context: activeCtx };
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
const l3Match = cmdText.match(/^hm[:\s]memory\s+l3(?:\s+(\S+))?/i);
|
|
836
|
-
if (l3Match) {
|
|
837
|
-
const targetCtx = l3Match[1] || activeCtx;
|
|
838
|
-
try {
|
|
839
|
-
const entries = await this.memory.getL3Knowledge(targetCtx);
|
|
840
|
-
if (entries.length === 0) {
|
|
841
|
-
return { content: `No L3 knowledge in context "${targetCtx}".`, model: "system", context: activeCtx };
|
|
842
|
-
}
|
|
843
|
-
let response = `## L3 Knowledge: ${targetCtx}
|
|
844
|
-
|
|
845
|
-
`;
|
|
846
|
-
for (const entry of entries) {
|
|
847
|
-
response += `- **[${entry.id.slice(0, 8)}]** (density: ${entry.connection_density}, accesses: ${entry.access_count})
|
|
848
|
-
${entry.content}
|
|
849
|
-
`;
|
|
850
|
-
}
|
|
851
|
-
return { content: response, model: "system", context: activeCtx };
|
|
852
|
-
} catch (err) {
|
|
853
|
-
return { content: `L3 query failed: ${err.message}`, model: "system", context: activeCtx };
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (/^hm[:\s]config\b/i.test(cmdText)) {
|
|
857
|
-
const response = [
|
|
858
|
-
`## Configuration`,
|
|
859
|
-
``,
|
|
860
|
-
`**Agent:** ${this.config.agent.name}`,
|
|
861
|
-
`**Personality:** ${this.config.agent.personality}`,
|
|
862
|
-
`**Workspace:** ${this.config.agent.workspace || "(none)"}`,
|
|
863
|
-
``,
|
|
864
|
-
`### LLM`,
|
|
865
|
-
`- Model: ${this.config.llm.model}`,
|
|
866
|
-
`- Base URL: ${this.config.llm.base_url}`,
|
|
867
|
-
`- Max Tokens: ${this.config.llm.max_tokens}`,
|
|
868
|
-
`- Temperature: ${this.config.llm.temperature}`,
|
|
869
|
-
``,
|
|
870
|
-
`### Memory`,
|
|
871
|
-
`- Daemon URL: ${this.config.memory.daemon_url}`,
|
|
872
|
-
`- Top-K: ${this.config.memory.top_k}`,
|
|
873
|
-
`- Embedding Model: ${this.config.memory.embedding_model}`,
|
|
874
|
-
``,
|
|
875
|
-
`### Sesame`,
|
|
876
|
-
`- API: ${this.config.sesame.api_url}`,
|
|
877
|
-
`- WebSocket: ${this.config.sesame.ws_url}`,
|
|
878
|
-
`- API Key: ${this.config.sesame.api_key ? "***" + this.config.sesame.api_key.slice(-4) : "(not set)"}`
|
|
879
|
-
].join("\n");
|
|
880
|
-
return { content: response, model: "system", context: activeCtx };
|
|
881
|
-
}
|
|
882
|
-
if (/^hm[:\s]contexts\b/i.test(cmdText)) {
|
|
883
|
-
const localContexts = this.contextManager.listContexts();
|
|
884
|
-
let daemonInfo = "";
|
|
885
|
-
try {
|
|
886
|
-
const daemonContexts = await this.memory.listContexts();
|
|
887
|
-
daemonInfo = "\n### Memory Daemon Contexts\n" + daemonContexts.map(
|
|
888
|
-
(c) => ` - **${c.name}**: ${c.episode_count} episodes (created: ${c.created_at})`
|
|
889
|
-
).join("\n");
|
|
890
|
-
} catch {
|
|
891
|
-
daemonInfo = "\n(Memory daemon unreachable)";
|
|
892
|
-
}
|
|
893
|
-
const localInfo = localContexts.map((c) => {
|
|
894
|
-
const active = c.name === activeCtx ? " \u2190 active" : "";
|
|
895
|
-
const historyLen = this.conversationHistories.get(c.name)?.length || 0;
|
|
896
|
-
return ` - **${c.name}**${active}: ${historyLen / 2} turns in L1`;
|
|
897
|
-
}).join("\n");
|
|
898
|
-
const response = [
|
|
899
|
-
`## Contexts`,
|
|
900
|
-
``,
|
|
901
|
-
`**Active:** ${activeCtx}`,
|
|
902
|
-
``,
|
|
903
|
-
`### Local (L1 Working Memory)`,
|
|
904
|
-
localInfo,
|
|
905
|
-
daemonInfo
|
|
906
|
-
].join("\n");
|
|
907
|
-
return { content: response, model: "system", context: activeCtx };
|
|
908
|
-
}
|
|
909
|
-
if (/^hm[:\s]help\b/i.test(cmdText)) {
|
|
910
|
-
const response = [
|
|
911
|
-
`## Available Commands`,
|
|
912
|
-
``,
|
|
913
|
-
`### Introspection`,
|
|
914
|
-
`- \`hm:status\` \u2014 Agent health, memory status, config summary`,
|
|
915
|
-
`- \`hm:config\` \u2014 Full configuration details`,
|
|
916
|
-
`- \`hm:contexts\` \u2014 List all contexts with L1/L2 info`,
|
|
917
|
-
`- \`hm:memory stats\` \u2014 Episode counts, L3 entries per context`,
|
|
918
|
-
`- \`hm:memory search <query>\` \u2014 Search L2 memories with scores`,
|
|
919
|
-
`- \`hm:memory l3 [context]\` \u2014 Show L3 knowledge entries`,
|
|
920
|
-
`- \`hm:help\` \u2014 This help message`,
|
|
921
|
-
``,
|
|
922
|
-
`### Context Management`,
|
|
923
|
-
`- \`switch to <name>\` \u2014 Switch active context`,
|
|
924
|
-
`- \`create context <name>\` \u2014 Create a new context`,
|
|
925
|
-
`- \`list contexts\` \u2014 List contexts (legacy)`,
|
|
926
|
-
``,
|
|
927
|
-
`### Memory`,
|
|
928
|
-
`- \`search all: <query>\` \u2014 Cross-context search`,
|
|
929
|
-
`- \`share <episode_id> with <context>\` \u2014 Share episode across contexts`,
|
|
930
|
-
``,
|
|
931
|
-
`### Tasks`,
|
|
932
|
-
`- \`task add <title>\` \u2014 Create a task`,
|
|
933
|
-
`- \`task list [status]\` \u2014 List tasks`,
|
|
934
|
-
`- \`task start <id>\` \u2014 Start a task`,
|
|
935
|
-
`- \`task complete <id>\` \u2014 Complete a task`,
|
|
936
|
-
`- \`task next\` \u2014 Pick up next available task`
|
|
937
|
-
].join("\n");
|
|
938
|
-
return { content: response, model: "system", context: activeCtx };
|
|
939
|
-
}
|
|
940
805
|
const searchAllMatch = cmdText.match(/^(?:search\s+all|cross-context\s+search)[:\s]+(.+)/i);
|
|
941
806
|
if (searchAllMatch) {
|
|
942
807
|
const query = searchAllMatch[1].trim();
|
|
@@ -1896,12 +1761,7 @@ var SesameClient2 = class {
|
|
|
1896
1761
|
this.sdk.on("message", (event) => {
|
|
1897
1762
|
const msg = event.data || event.message || event;
|
|
1898
1763
|
const senderId = msg.senderId || msg.sender?.id;
|
|
1899
|
-
if (senderId === this.agentId)
|
|
1900
|
-
const content = msg.content?.trim() || "";
|
|
1901
|
-
const isHmCommand = /^hm[:\s]/i.test(content);
|
|
1902
|
-
const isTextCommand = /^(?:list contexts|switch to|create context|search all:|task )/i.test(content);
|
|
1903
|
-
if (!isHmCommand && !isTextCommand) return;
|
|
1904
|
-
}
|
|
1764
|
+
if (senderId === this.agentId) return;
|
|
1905
1765
|
if (!this.messageHandler || !msg.content) return;
|
|
1906
1766
|
const channelInfo = this.channels.get(msg.channelId);
|
|
1907
1767
|
this.messageHandler({
|
|
@@ -2247,14 +2107,550 @@ function startDashboardServer(requestLogger, memoryConfig) {
|
|
|
2247
2107
|
});
|
|
2248
2108
|
}
|
|
2249
2109
|
|
|
2110
|
+
// packages/runtime/src/tool-registry.ts
|
|
2111
|
+
var ToolRegistry = class {
|
|
2112
|
+
tools = /* @__PURE__ */ new Map();
|
|
2113
|
+
register(name, description, parameters, executor) {
|
|
2114
|
+
this.tools.set(name, {
|
|
2115
|
+
definition: {
|
|
2116
|
+
type: "function",
|
|
2117
|
+
function: { name, description, parameters }
|
|
2118
|
+
},
|
|
2119
|
+
executor
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
getDefinitions() {
|
|
2123
|
+
return Array.from(this.tools.values()).map((t) => t.definition);
|
|
2124
|
+
}
|
|
2125
|
+
has(name) {
|
|
2126
|
+
return this.tools.has(name);
|
|
2127
|
+
}
|
|
2128
|
+
async execute(name, params) {
|
|
2129
|
+
const tool = this.tools.get(name);
|
|
2130
|
+
if (!tool) {
|
|
2131
|
+
return `Error: Unknown tool "${name}"`;
|
|
2132
|
+
}
|
|
2133
|
+
try {
|
|
2134
|
+
return await tool.executor(params);
|
|
2135
|
+
} catch (err) {
|
|
2136
|
+
return `Error executing ${name}: ${err.message}`;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
async executeCall(call) {
|
|
2140
|
+
let params = {};
|
|
2141
|
+
try {
|
|
2142
|
+
params = JSON.parse(call.function.arguments);
|
|
2143
|
+
} catch {
|
|
2144
|
+
return {
|
|
2145
|
+
tool_call_id: call.id,
|
|
2146
|
+
role: "tool",
|
|
2147
|
+
content: `Error: Invalid JSON arguments for ${call.function.name}`
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
const result = await this.execute(call.function.name, params);
|
|
2151
|
+
return {
|
|
2152
|
+
tool_call_id: call.id,
|
|
2153
|
+
role: "tool",
|
|
2154
|
+
content: result
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
// packages/runtime/src/tools/shell.ts
|
|
2160
|
+
import { execSync } from "child_process";
|
|
2161
|
+
import { resolve as resolve5 } from "path";
|
|
2162
|
+
var MAX_OUTPUT = 5e4;
|
|
2163
|
+
function registerShellTool(registry, workspaceDir) {
|
|
2164
|
+
registry.register(
|
|
2165
|
+
"shell",
|
|
2166
|
+
"Execute a shell command and return stdout/stderr. Use for running programs, checking status, installing packages, git operations, etc.",
|
|
2167
|
+
{
|
|
2168
|
+
type: "object",
|
|
2169
|
+
properties: {
|
|
2170
|
+
command: {
|
|
2171
|
+
type: "string",
|
|
2172
|
+
description: "The shell command to execute"
|
|
2173
|
+
},
|
|
2174
|
+
workdir: {
|
|
2175
|
+
type: "string",
|
|
2176
|
+
description: "Working directory (relative to workspace). Defaults to workspace root."
|
|
2177
|
+
},
|
|
2178
|
+
timeout: {
|
|
2179
|
+
type: "number",
|
|
2180
|
+
description: "Timeout in seconds. Default: 30"
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
required: ["command"]
|
|
2184
|
+
},
|
|
2185
|
+
async (params) => {
|
|
2186
|
+
const command = params.command;
|
|
2187
|
+
const timeout = (params.timeout || 30) * 1e3;
|
|
2188
|
+
const cwd = params.workdir ? resolve5(workspaceDir, params.workdir) : workspaceDir;
|
|
2189
|
+
try {
|
|
2190
|
+
const output = execSync(command, {
|
|
2191
|
+
cwd,
|
|
2192
|
+
timeout,
|
|
2193
|
+
encoding: "utf-8",
|
|
2194
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2195
|
+
// 10MB
|
|
2196
|
+
shell: "/bin/sh",
|
|
2197
|
+
env: { ...process.env, PATH: `/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${process.env.PATH}` }
|
|
2198
|
+
});
|
|
2199
|
+
const trimmed = output.length > MAX_OUTPUT ? output.slice(0, MAX_OUTPUT) + `
|
|
2200
|
+
... (truncated, ${output.length} total chars)` : output;
|
|
2201
|
+
return trimmed || "(no output)";
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
const stderr = err.stderr?.toString() || "";
|
|
2204
|
+
const stdout = err.stdout?.toString() || "";
|
|
2205
|
+
const output = (stdout + "\n" + stderr).trim();
|
|
2206
|
+
const code = err.status ?? "unknown";
|
|
2207
|
+
return `Command failed (exit code ${code}):
|
|
2208
|
+
${output || err.message}`;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
// packages/runtime/src/tools/files.ts
|
|
2215
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
2216
|
+
import { resolve as resolve6, dirname as dirname5 } from "path";
|
|
2217
|
+
var MAX_READ = 1e5;
|
|
2218
|
+
function registerFileTools(registry, workspaceDir) {
|
|
2219
|
+
registry.register(
|
|
2220
|
+
"read_file",
|
|
2221
|
+
"Read the contents of a file. Returns the text content. Use offset/limit for large files.",
|
|
2222
|
+
{
|
|
2223
|
+
type: "object",
|
|
2224
|
+
properties: {
|
|
2225
|
+
path: {
|
|
2226
|
+
type: "string",
|
|
2227
|
+
description: "File path (relative to workspace or absolute)"
|
|
2228
|
+
},
|
|
2229
|
+
offset: {
|
|
2230
|
+
type: "number",
|
|
2231
|
+
description: "Line number to start reading from (1-indexed)"
|
|
2232
|
+
},
|
|
2233
|
+
limit: {
|
|
2234
|
+
type: "number",
|
|
2235
|
+
description: "Maximum number of lines to read"
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2238
|
+
required: ["path"]
|
|
2239
|
+
},
|
|
2240
|
+
async (params) => {
|
|
2241
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2242
|
+
if (!existsSync4(filePath)) {
|
|
2243
|
+
return `Error: File not found: ${filePath}`;
|
|
2244
|
+
}
|
|
2245
|
+
try {
|
|
2246
|
+
let content = readFileSync6(filePath, "utf-8");
|
|
2247
|
+
if (params.offset || params.limit) {
|
|
2248
|
+
const lines = content.split("\n");
|
|
2249
|
+
const start = (params.offset || 1) - 1;
|
|
2250
|
+
const end = params.limit ? start + params.limit : lines.length;
|
|
2251
|
+
content = lines.slice(start, end).join("\n");
|
|
2252
|
+
}
|
|
2253
|
+
if (content.length > MAX_READ) {
|
|
2254
|
+
content = content.slice(0, MAX_READ) + `
|
|
2255
|
+
... (truncated, ${content.length} total chars)`;
|
|
2256
|
+
}
|
|
2257
|
+
return content || "(empty file)";
|
|
2258
|
+
} catch (err) {
|
|
2259
|
+
return `Error reading file: ${err.message}`;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
);
|
|
2263
|
+
registry.register(
|
|
2264
|
+
"write_file",
|
|
2265
|
+
"Write content to a file. Creates the file and parent directories if they don't exist. Overwrites existing content.",
|
|
2266
|
+
{
|
|
2267
|
+
type: "object",
|
|
2268
|
+
properties: {
|
|
2269
|
+
path: {
|
|
2270
|
+
type: "string",
|
|
2271
|
+
description: "File path (relative to workspace or absolute)"
|
|
2272
|
+
},
|
|
2273
|
+
content: {
|
|
2274
|
+
type: "string",
|
|
2275
|
+
description: "Content to write to the file"
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
required: ["path", "content"]
|
|
2279
|
+
},
|
|
2280
|
+
async (params) => {
|
|
2281
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2282
|
+
try {
|
|
2283
|
+
const dir = dirname5(filePath);
|
|
2284
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
2285
|
+
writeFileSync2(filePath, params.content);
|
|
2286
|
+
return `Written ${params.content.length} bytes to ${filePath}`;
|
|
2287
|
+
} catch (err) {
|
|
2288
|
+
return `Error writing file: ${err.message}`;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
);
|
|
2292
|
+
registry.register(
|
|
2293
|
+
"edit_file",
|
|
2294
|
+
"Edit a file by finding and replacing exact text. The old text must match exactly (including whitespace).",
|
|
2295
|
+
{
|
|
2296
|
+
type: "object",
|
|
2297
|
+
properties: {
|
|
2298
|
+
path: {
|
|
2299
|
+
type: "string",
|
|
2300
|
+
description: "File path (relative to workspace or absolute)"
|
|
2301
|
+
},
|
|
2302
|
+
old_text: {
|
|
2303
|
+
type: "string",
|
|
2304
|
+
description: "Exact text to find and replace"
|
|
2305
|
+
},
|
|
2306
|
+
new_text: {
|
|
2307
|
+
type: "string",
|
|
2308
|
+
description: "New text to replace with"
|
|
2309
|
+
}
|
|
2310
|
+
},
|
|
2311
|
+
required: ["path", "old_text", "new_text"]
|
|
2312
|
+
},
|
|
2313
|
+
async (params) => {
|
|
2314
|
+
const filePath = resolvePath(workspaceDir, params.path);
|
|
2315
|
+
if (!existsSync4(filePath)) {
|
|
2316
|
+
return `Error: File not found: ${filePath}`;
|
|
2317
|
+
}
|
|
2318
|
+
try {
|
|
2319
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
2320
|
+
const oldText = params.old_text;
|
|
2321
|
+
const newText = params.new_text;
|
|
2322
|
+
if (!content.includes(oldText)) {
|
|
2323
|
+
return `Error: Could not find the exact text to replace in ${filePath}`;
|
|
2324
|
+
}
|
|
2325
|
+
const updated = content.replace(oldText, newText);
|
|
2326
|
+
writeFileSync2(filePath, updated);
|
|
2327
|
+
return `Edited ${filePath}: replaced ${oldText.length} chars with ${newText.length} chars`;
|
|
2328
|
+
} catch (err) {
|
|
2329
|
+
return `Error editing file: ${err.message}`;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
);
|
|
2333
|
+
registry.register(
|
|
2334
|
+
"list_files",
|
|
2335
|
+
"List files and directories in a path.",
|
|
2336
|
+
{
|
|
2337
|
+
type: "object",
|
|
2338
|
+
properties: {
|
|
2339
|
+
path: {
|
|
2340
|
+
type: "string",
|
|
2341
|
+
description: "Directory path (relative to workspace or absolute). Defaults to workspace root."
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
required: []
|
|
2345
|
+
},
|
|
2346
|
+
async (params) => {
|
|
2347
|
+
const { readdirSync: readdirSync2, statSync } = await import("fs");
|
|
2348
|
+
const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
|
|
2349
|
+
if (!existsSync4(dirPath)) {
|
|
2350
|
+
return `Error: Directory not found: ${dirPath}`;
|
|
2351
|
+
}
|
|
2352
|
+
try {
|
|
2353
|
+
const entries = readdirSync2(dirPath);
|
|
2354
|
+
const results = [];
|
|
2355
|
+
for (const entry of entries) {
|
|
2356
|
+
if (entry.startsWith(".")) continue;
|
|
2357
|
+
try {
|
|
2358
|
+
const stat = statSync(resolve6(dirPath, entry));
|
|
2359
|
+
results.push(stat.isDirectory() ? `${entry}/` : entry);
|
|
2360
|
+
} catch {
|
|
2361
|
+
results.push(entry);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
return results.join("\n") || "(empty directory)";
|
|
2365
|
+
} catch (err) {
|
|
2366
|
+
return `Error listing directory: ${err.message}`;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
function resolvePath(workspace, path) {
|
|
2372
|
+
if (path.startsWith("/") || path.startsWith("~")) {
|
|
2373
|
+
return path.replace(/^~/, process.env.HOME || "/root");
|
|
2374
|
+
}
|
|
2375
|
+
return resolve6(workspace, path);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// packages/runtime/src/tools/web.ts
|
|
2379
|
+
function registerWebTools(registry, config) {
|
|
2380
|
+
registry.register(
|
|
2381
|
+
"web_search",
|
|
2382
|
+
"Search the web using Brave Search API. Returns titles, URLs, and snippets.",
|
|
2383
|
+
{
|
|
2384
|
+
type: "object",
|
|
2385
|
+
properties: {
|
|
2386
|
+
query: {
|
|
2387
|
+
type: "string",
|
|
2388
|
+
description: "Search query string"
|
|
2389
|
+
},
|
|
2390
|
+
count: {
|
|
2391
|
+
type: "number",
|
|
2392
|
+
description: "Number of results (1-10, default 5)"
|
|
2393
|
+
}
|
|
2394
|
+
},
|
|
2395
|
+
required: ["query"]
|
|
2396
|
+
},
|
|
2397
|
+
async (params) => {
|
|
2398
|
+
const apiKey = config?.braveApiKey || process.env.BRAVE_API_KEY;
|
|
2399
|
+
if (!apiKey) {
|
|
2400
|
+
return "Error: No Brave Search API key configured. Set BRAVE_API_KEY env var.";
|
|
2401
|
+
}
|
|
2402
|
+
const query = params.query;
|
|
2403
|
+
const count = Math.min(Math.max(params.count || 5, 1), 10);
|
|
2404
|
+
try {
|
|
2405
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`;
|
|
2406
|
+
const resp = await fetch(url, {
|
|
2407
|
+
headers: { "X-Subscription-Token": apiKey, Accept: "application/json" }
|
|
2408
|
+
});
|
|
2409
|
+
if (!resp.ok) {
|
|
2410
|
+
return `Search failed: ${resp.status} ${await resp.text()}`;
|
|
2411
|
+
}
|
|
2412
|
+
const data = await resp.json();
|
|
2413
|
+
const results = data.web?.results || [];
|
|
2414
|
+
if (results.length === 0) return "No results found.";
|
|
2415
|
+
return results.map((r, i) => `${i + 1}. **${r.title}**
|
|
2416
|
+
${r.url}
|
|
2417
|
+
${r.description}`).join("\n\n");
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
return `Search error: ${err.message}`;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
);
|
|
2423
|
+
registry.register(
|
|
2424
|
+
"web_fetch",
|
|
2425
|
+
"Fetch a URL and extract readable content as markdown/text. Good for reading articles, docs, and web pages.",
|
|
2426
|
+
{
|
|
2427
|
+
type: "object",
|
|
2428
|
+
properties: {
|
|
2429
|
+
url: {
|
|
2430
|
+
type: "string",
|
|
2431
|
+
description: "HTTP or HTTPS URL to fetch"
|
|
2432
|
+
},
|
|
2433
|
+
max_chars: {
|
|
2434
|
+
type: "number",
|
|
2435
|
+
description: "Maximum characters to return (default 20000)"
|
|
2436
|
+
}
|
|
2437
|
+
},
|
|
2438
|
+
required: ["url"]
|
|
2439
|
+
},
|
|
2440
|
+
async (params) => {
|
|
2441
|
+
const url = params.url;
|
|
2442
|
+
const maxChars = params.max_chars || 2e4;
|
|
2443
|
+
try {
|
|
2444
|
+
const resp = await fetch(url, {
|
|
2445
|
+
headers: {
|
|
2446
|
+
"User-Agent": "Hivemind/1.0 (Agent Web Fetcher)",
|
|
2447
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json"
|
|
2448
|
+
},
|
|
2449
|
+
redirect: "follow"
|
|
2450
|
+
});
|
|
2451
|
+
if (!resp.ok) {
|
|
2452
|
+
return `Fetch failed: ${resp.status} ${resp.statusText}`;
|
|
2453
|
+
}
|
|
2454
|
+
let text = await resp.text();
|
|
2455
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
2456
|
+
if (contentType.includes("html")) {
|
|
2457
|
+
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
2458
|
+
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
2459
|
+
text = text.replace(/<[^>]+>/g, " ");
|
|
2460
|
+
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2461
|
+
text = text.replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ");
|
|
2462
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
2463
|
+
}
|
|
2464
|
+
if (text.length > maxChars) {
|
|
2465
|
+
text = text.slice(0, maxChars) + `
|
|
2466
|
+
... (truncated, ${text.length} total chars)`;
|
|
2467
|
+
}
|
|
2468
|
+
return text || "(empty response)";
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
return `Fetch error: ${err.message}`;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
// packages/runtime/src/tools/memory.ts
|
|
2477
|
+
function registerMemoryTools(registry, daemonUrl) {
|
|
2478
|
+
registry.register(
|
|
2479
|
+
"memory_search",
|
|
2480
|
+
"Search your episodic memory for relevant past conversations and experiences. Returns scored results from your memory system. Use this when you want to recall something from a previous conversation or check what you know about a topic.",
|
|
2481
|
+
{
|
|
2482
|
+
type: "object",
|
|
2483
|
+
properties: {
|
|
2484
|
+
query: {
|
|
2485
|
+
type: "string",
|
|
2486
|
+
description: "What to search for in memory"
|
|
2487
|
+
},
|
|
2488
|
+
context: {
|
|
2489
|
+
type: "string",
|
|
2490
|
+
description: "Context to search in (default: current active context)"
|
|
2491
|
+
},
|
|
2492
|
+
top_k: {
|
|
2493
|
+
type: "number",
|
|
2494
|
+
description: "Number of results to return (default: 10)"
|
|
2495
|
+
}
|
|
2496
|
+
},
|
|
2497
|
+
required: ["query"]
|
|
2498
|
+
},
|
|
2499
|
+
async (params) => {
|
|
2500
|
+
const query = params.query;
|
|
2501
|
+
const context = params.context || "global";
|
|
2502
|
+
const topK = params.top_k || 10;
|
|
2503
|
+
try {
|
|
2504
|
+
const resp = await fetch(`${daemonUrl}/api/search`, {
|
|
2505
|
+
method: "POST",
|
|
2506
|
+
headers: { "Content-Type": "application/json" },
|
|
2507
|
+
body: JSON.stringify({ query, context_name: context, top_k: topK })
|
|
2508
|
+
});
|
|
2509
|
+
if (!resp.ok) return `Memory search failed: ${resp.status}`;
|
|
2510
|
+
const data = await resp.json();
|
|
2511
|
+
if (!data.results?.length) return `No memories found for "${query}" in context "${context}".`;
|
|
2512
|
+
return data.results.map((r, i) => `${i + 1}. [score: ${r.score.toFixed(3)}] [${r.context_name}] [${r.role}] ${r.content.slice(0, 200)}`).join("\n");
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
return `Memory daemon unreachable: ${err.message}`;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
);
|
|
2518
|
+
registry.register(
|
|
2519
|
+
"memory_stats",
|
|
2520
|
+
"Get statistics about your memory system \u2014 episode counts, contexts, L3 knowledge entries. Use for self-diagnosis or understanding your memory state.",
|
|
2521
|
+
{
|
|
2522
|
+
type: "object",
|
|
2523
|
+
properties: {},
|
|
2524
|
+
required: []
|
|
2525
|
+
},
|
|
2526
|
+
async () => {
|
|
2527
|
+
try {
|
|
2528
|
+
const healthResp = await fetch(`${daemonUrl}/health`);
|
|
2529
|
+
const health = healthResp.ok ? await healthResp.json() : { status: "unreachable" };
|
|
2530
|
+
const ctxResp = await fetch(`${daemonUrl}/api/contexts`);
|
|
2531
|
+
const contexts = ctxResp.ok ? (await ctxResp.json()).contexts : [];
|
|
2532
|
+
const l3Counts = {};
|
|
2533
|
+
for (const ctx of contexts) {
|
|
2534
|
+
try {
|
|
2535
|
+
const l3Resp = await fetch(`${daemonUrl}/api/l3/${encodeURIComponent(ctx.name)}`);
|
|
2536
|
+
if (l3Resp.ok) {
|
|
2537
|
+
const l3Data = await l3Resp.json();
|
|
2538
|
+
l3Counts[ctx.name] = l3Data.entries?.length ?? 0;
|
|
2539
|
+
}
|
|
2540
|
+
} catch {
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
const totalEpisodes = contexts.reduce((sum, c) => sum + c.episode_count, 0);
|
|
2544
|
+
const totalL3 = Object.values(l3Counts).reduce((sum, c) => sum + c, 0);
|
|
2545
|
+
let output = `Memory Daemon: ${health.status || "unknown"}
|
|
2546
|
+
`;
|
|
2547
|
+
output += `Total Episodes (L2): ${totalEpisodes}
|
|
2548
|
+
`;
|
|
2549
|
+
output += `Total Knowledge (L3): ${totalL3}
|
|
2550
|
+
`;
|
|
2551
|
+
output += `
|
|
2552
|
+
Contexts:
|
|
2553
|
+
`;
|
|
2554
|
+
for (const ctx of contexts) {
|
|
2555
|
+
output += ` - ${ctx.name}: ${ctx.episode_count} episodes, ${l3Counts[ctx.name] ?? 0} L3 entries
|
|
2556
|
+
`;
|
|
2557
|
+
}
|
|
2558
|
+
return output;
|
|
2559
|
+
} catch (err) {
|
|
2560
|
+
return `Memory daemon unreachable: ${err.message}`;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
);
|
|
2564
|
+
registry.register(
|
|
2565
|
+
"memory_l3",
|
|
2566
|
+
"View your promoted L3 knowledge \u2014 high-level patterns, decisions, and insights that have been distilled from your episodic memories. This is your 'long-term wisdom'.",
|
|
2567
|
+
{
|
|
2568
|
+
type: "object",
|
|
2569
|
+
properties: {
|
|
2570
|
+
context: {
|
|
2571
|
+
type: "string",
|
|
2572
|
+
description: "Context to get L3 knowledge for (default: global)"
|
|
2573
|
+
}
|
|
2574
|
+
},
|
|
2575
|
+
required: []
|
|
2576
|
+
},
|
|
2577
|
+
async (params) => {
|
|
2578
|
+
const context = params.context || "global";
|
|
2579
|
+
try {
|
|
2580
|
+
const resp = await fetch(`${daemonUrl}/api/l3/${encodeURIComponent(context)}`);
|
|
2581
|
+
if (!resp.ok) return `L3 query failed: ${resp.status}`;
|
|
2582
|
+
const data = await resp.json();
|
|
2583
|
+
if (!data.entries?.length) return `No L3 knowledge in context "${context}".`;
|
|
2584
|
+
return data.entries.map((e, i) => `${i + 1}. ${e.content} (from ${e.source_episodes} episodes, ${e.created_at})`).join("\n");
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
return `Memory daemon unreachable: ${err.message}`;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
);
|
|
2590
|
+
registry.register(
|
|
2591
|
+
"memory_cross_search",
|
|
2592
|
+
"Search across ALL memory contexts at once. Useful when you're not sure which context a memory belongs to.",
|
|
2593
|
+
{
|
|
2594
|
+
type: "object",
|
|
2595
|
+
properties: {
|
|
2596
|
+
query: {
|
|
2597
|
+
type: "string",
|
|
2598
|
+
description: "What to search for across all contexts"
|
|
2599
|
+
},
|
|
2600
|
+
top_k: {
|
|
2601
|
+
type: "number",
|
|
2602
|
+
description: "Number of results (default: 10)"
|
|
2603
|
+
}
|
|
2604
|
+
},
|
|
2605
|
+
required: ["query"]
|
|
2606
|
+
},
|
|
2607
|
+
async (params) => {
|
|
2608
|
+
const query = params.query;
|
|
2609
|
+
const topK = params.top_k || 10;
|
|
2610
|
+
try {
|
|
2611
|
+
const resp = await fetch(`${daemonUrl}/api/search/cross-context`, {
|
|
2612
|
+
method: "POST",
|
|
2613
|
+
headers: { "Content-Type": "application/json" },
|
|
2614
|
+
body: JSON.stringify({ query, top_k: topK })
|
|
2615
|
+
});
|
|
2616
|
+
if (!resp.ok) return `Cross-context search failed: ${resp.status}`;
|
|
2617
|
+
const data = await resp.json();
|
|
2618
|
+
if (!data.results?.length) return `No memories found across any context for "${query}".`;
|
|
2619
|
+
return data.results.map((r, i) => `${i + 1}. [${r.score.toFixed(3)}] [${r.context_name}] ${r.content.slice(0, 200)}`).join("\n");
|
|
2620
|
+
} catch (err) {
|
|
2621
|
+
return `Memory daemon unreachable: ${err.message}`;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// packages/runtime/src/tools/register.ts
|
|
2628
|
+
import { resolve as resolve7 } from "path";
|
|
2629
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
2630
|
+
function registerAllTools(hivemindHome, config) {
|
|
2631
|
+
const registry = new ToolRegistry();
|
|
2632
|
+
if (config?.enabled === false) {
|
|
2633
|
+
return registry;
|
|
2634
|
+
}
|
|
2635
|
+
const workspaceDir = resolve7(hivemindHome, config?.workspace || "workspace");
|
|
2636
|
+
if (!existsSync5(workspaceDir)) {
|
|
2637
|
+
mkdirSync3(workspaceDir, { recursive: true });
|
|
2638
|
+
}
|
|
2639
|
+
registerShellTool(registry, workspaceDir);
|
|
2640
|
+
registerFileTools(registry, workspaceDir);
|
|
2641
|
+
registerWebTools(registry, { braveApiKey: config?.braveApiKey });
|
|
2642
|
+
registerMemoryTools(registry, config?.memoryDaemonUrl || "http://localhost:3434");
|
|
2643
|
+
return registry;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2250
2646
|
// packages/runtime/src/pipeline.ts
|
|
2251
|
-
import { readFileSync as
|
|
2252
|
-
import { resolve as
|
|
2647
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
2648
|
+
import { resolve as resolve8, dirname as dirname6 } from "path";
|
|
2253
2649
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2254
2650
|
var PACKAGE_VERSION = "unknown";
|
|
2255
2651
|
try {
|
|
2256
|
-
const __dirname2 =
|
|
2257
|
-
const pkg = JSON.parse(
|
|
2652
|
+
const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
|
|
2653
|
+
const pkg = JSON.parse(readFileSync7(resolve8(__dirname2, "../package.json"), "utf-8"));
|
|
2258
2654
|
PACKAGE_VERSION = pkg.version ?? "unknown";
|
|
2259
2655
|
} catch {
|
|
2260
2656
|
}
|
|
@@ -2285,7 +2681,7 @@ function startHealthServer(port) {
|
|
|
2285
2681
|
return server;
|
|
2286
2682
|
}
|
|
2287
2683
|
function writePidFile(path) {
|
|
2288
|
-
|
|
2684
|
+
writeFileSync3(path, String(process.pid));
|
|
2289
2685
|
console.log(`[hivemind] PID file written: ${path}`);
|
|
2290
2686
|
}
|
|
2291
2687
|
function cleanupPidFile(path) {
|
|
@@ -2316,10 +2712,18 @@ async function startPipeline(configPath) {
|
|
|
2316
2712
|
memoryConnected = true;
|
|
2317
2713
|
console.log("[hivemind] Memory daemon connected");
|
|
2318
2714
|
}
|
|
2319
|
-
const requestLogger = new RequestLogger(
|
|
2715
|
+
const requestLogger = new RequestLogger(resolve8(dirname6(configPath), "data", "dashboard.db"));
|
|
2320
2716
|
startDashboardServer(requestLogger, config.memory);
|
|
2321
2717
|
const agent = new Agent(config);
|
|
2322
2718
|
agent.setRequestLogger(requestLogger);
|
|
2719
|
+
const hivemindHome = process.env.HIVEMIND_HOME || resolve8(process.env.HOME || "/root", "hivemind");
|
|
2720
|
+
const toolRegistry = registerAllTools(hivemindHome, {
|
|
2721
|
+
enabled: true,
|
|
2722
|
+
workspace: config.agent.workspace || "workspace",
|
|
2723
|
+
braveApiKey: process.env.BRAVE_API_KEY,
|
|
2724
|
+
memoryDaemonUrl: config.memory.daemon_url
|
|
2725
|
+
});
|
|
2726
|
+
agent.setToolRegistry(toolRegistry);
|
|
2323
2727
|
console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
|
|
2324
2728
|
if (config.sesame.api_key) {
|
|
2325
2729
|
await startSesameLoop(config, agent);
|
|
@@ -2467,8 +2871,8 @@ ${response.content}
|
|
|
2467
2871
|
console.error("Error:", err.message);
|
|
2468
2872
|
}
|
|
2469
2873
|
});
|
|
2470
|
-
return new Promise((
|
|
2471
|
-
rl.on("close",
|
|
2874
|
+
return new Promise((resolve9) => {
|
|
2875
|
+
rl.on("close", resolve9);
|
|
2472
2876
|
});
|
|
2473
2877
|
}
|
|
2474
2878
|
|
|
@@ -2497,20 +2901,20 @@ var WorkerServer = class {
|
|
|
2497
2901
|
}
|
|
2498
2902
|
/** Start listening. */
|
|
2499
2903
|
async start() {
|
|
2500
|
-
return new Promise((
|
|
2904
|
+
return new Promise((resolve9, reject) => {
|
|
2501
2905
|
this.server = createServer3((req, res) => this.handleRequest(req, res));
|
|
2502
2906
|
this.server.on("error", reject);
|
|
2503
|
-
this.server.listen(this.port, () =>
|
|
2907
|
+
this.server.listen(this.port, () => resolve9());
|
|
2504
2908
|
});
|
|
2505
2909
|
}
|
|
2506
2910
|
/** Stop the server. */
|
|
2507
2911
|
async stop() {
|
|
2508
|
-
return new Promise((
|
|
2912
|
+
return new Promise((resolve9) => {
|
|
2509
2913
|
if (!this.server) {
|
|
2510
|
-
|
|
2914
|
+
resolve9();
|
|
2511
2915
|
return;
|
|
2512
2916
|
}
|
|
2513
|
-
this.server.close(() =>
|
|
2917
|
+
this.server.close(() => resolve9());
|
|
2514
2918
|
});
|
|
2515
2919
|
}
|
|
2516
2920
|
getPort() {
|
|
@@ -2633,10 +3037,10 @@ var WorkerServer = class {
|
|
|
2633
3037
|
}
|
|
2634
3038
|
};
|
|
2635
3039
|
function readBody(req) {
|
|
2636
|
-
return new Promise((
|
|
3040
|
+
return new Promise((resolve9, reject) => {
|
|
2637
3041
|
const chunks = [];
|
|
2638
3042
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
2639
|
-
req.on("end", () =>
|
|
3043
|
+
req.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
|
|
2640
3044
|
req.on("error", reject);
|
|
2641
3045
|
});
|
|
2642
3046
|
}
|
|
@@ -2963,4 +3367,4 @@ smol-toml/dist/index.js:
|
|
|
2963
3367
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
2964
3368
|
*)
|
|
2965
3369
|
*/
|
|
2966
|
-
//# sourceMappingURL=chunk-
|
|
3370
|
+
//# sourceMappingURL=chunk-7YHRRM5B.js.map
|