@johpaz/hive-core 1.0.8 → 1.0.10
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/package.json +10 -9
- package/src/agent/ethics.ts +70 -68
- package/src/agent/index.ts +48 -17
- package/src/agent/providers/index.ts +11 -5
- package/src/agent/soul.ts +19 -15
- package/src/agent/user.ts +19 -15
- package/src/agent/workspace.ts +6 -6
- package/src/agents/index.ts +4 -0
- package/src/agents/inter-agent-bus.test.ts +264 -0
- package/src/agents/inter-agent-bus.ts +279 -0
- package/src/agents/registry.test.ts +275 -0
- package/src/agents/registry.ts +273 -0
- package/src/agents/router.test.ts +229 -0
- package/src/agents/router.ts +251 -0
- package/src/agents/team-coordinator.test.ts +401 -0
- package/src/agents/team-coordinator.ts +480 -0
- package/src/canvas/canvas-manager.test.ts +159 -0
- package/src/canvas/canvas-manager.ts +219 -0
- package/src/canvas/canvas-tools.ts +189 -0
- package/src/canvas/index.ts +2 -0
- package/src/channels/whatsapp.ts +12 -12
- package/src/config/loader.ts +12 -9
- package/src/events/event-bus.test.ts +98 -0
- package/src/events/event-bus.ts +171 -0
- package/src/gateway/server.ts +131 -35
- package/src/index.ts +9 -1
- package/src/multi-agent/manager.ts +12 -12
- package/src/plugins/api.ts +129 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/loader.test.ts +285 -0
- package/src/plugins/loader.ts +363 -0
- package/src/resilience/circuit-breaker.test.ts +129 -0
- package/src/resilience/circuit-breaker.ts +223 -0
- package/src/security/google-chat.test.ts +219 -0
- package/src/security/google-chat.ts +269 -0
- package/src/security/index.ts +5 -0
- package/src/security/pairing.test.ts +302 -0
- package/src/security/pairing.ts +250 -0
- package/src/security/rate-limit.test.ts +239 -0
- package/src/security/rate-limit.ts +270 -0
- package/src/security/signal.test.ts +92 -0
- package/src/security/signal.ts +321 -0
- package/src/state/store.test.ts +190 -0
- package/src/state/store.ts +310 -0
- package/src/storage/sqlite.ts +3 -3
- package/src/tools/cron.ts +42 -2
- package/src/tools/dynamic-registry.test.ts +226 -0
- package/src/tools/dynamic-registry.ts +258 -0
- package/src/tools/fs.test.ts +127 -0
- package/src/tools/fs.ts +364 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/read.ts +23 -19
- package/src/utils/logger.ts +112 -33
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
|
|
3
|
+
export interface EventMap {
|
|
4
|
+
"message:received": {
|
|
5
|
+
channel: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
content: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
};
|
|
11
|
+
"message:sent": {
|
|
12
|
+
channel: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
content: string;
|
|
15
|
+
messageId: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
};
|
|
18
|
+
"agent:thinking": {
|
|
19
|
+
agentId: string;
|
|
20
|
+
sessionId: string;
|
|
21
|
+
stage: "planning" | "executing" | "responding";
|
|
22
|
+
};
|
|
23
|
+
"agent:response": {
|
|
24
|
+
agentId: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
content: string;
|
|
27
|
+
toolsUsed: string[];
|
|
28
|
+
duration: number;
|
|
29
|
+
};
|
|
30
|
+
"tool:executing": {
|
|
31
|
+
toolName: string;
|
|
32
|
+
args: Record<string, unknown>;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
};
|
|
35
|
+
"tool:completed": {
|
|
36
|
+
toolName: string;
|
|
37
|
+
result: unknown;
|
|
38
|
+
duration: number;
|
|
39
|
+
success: boolean;
|
|
40
|
+
};
|
|
41
|
+
"tool:error": {
|
|
42
|
+
toolName: string;
|
|
43
|
+
error: Error;
|
|
44
|
+
args: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
"error": {
|
|
47
|
+
source: string;
|
|
48
|
+
error: Error;
|
|
49
|
+
context: Record<string, unknown>;
|
|
50
|
+
recoverable: boolean;
|
|
51
|
+
};
|
|
52
|
+
"session:started": {
|
|
53
|
+
sessionId: string;
|
|
54
|
+
agentId: string;
|
|
55
|
+
channel: string;
|
|
56
|
+
userId: string;
|
|
57
|
+
};
|
|
58
|
+
"session:ended": {
|
|
59
|
+
sessionId: string;
|
|
60
|
+
duration: number;
|
|
61
|
+
messageCount: number;
|
|
62
|
+
reason: "completed" | "cancelled" | "error" | "timeout";
|
|
63
|
+
};
|
|
64
|
+
"mcp:connected": {
|
|
65
|
+
serverName: string;
|
|
66
|
+
toolsCount: number;
|
|
67
|
+
resourcesCount: number;
|
|
68
|
+
};
|
|
69
|
+
"mcp:disconnected": {
|
|
70
|
+
serverName: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
};
|
|
73
|
+
"mcp:error": {
|
|
74
|
+
serverName: string;
|
|
75
|
+
error: Error;
|
|
76
|
+
};
|
|
77
|
+
"channel:started": {
|
|
78
|
+
channel: string;
|
|
79
|
+
accountId: string;
|
|
80
|
+
};
|
|
81
|
+
"channel:stopped": {
|
|
82
|
+
channel: string;
|
|
83
|
+
accountId: string;
|
|
84
|
+
reason: string;
|
|
85
|
+
};
|
|
86
|
+
"gateway:started": {
|
|
87
|
+
host: string;
|
|
88
|
+
port: number;
|
|
89
|
+
};
|
|
90
|
+
"gateway:stopped": {
|
|
91
|
+
reason: string;
|
|
92
|
+
};
|
|
93
|
+
"pairing:requested": {
|
|
94
|
+
channel: string;
|
|
95
|
+
userId: string;
|
|
96
|
+
code: string;
|
|
97
|
+
expiresAt: number;
|
|
98
|
+
};
|
|
99
|
+
"pairing:approved": {
|
|
100
|
+
channel: string;
|
|
101
|
+
userId: string;
|
|
102
|
+
};
|
|
103
|
+
"pairing:rejected": {
|
|
104
|
+
channel: string;
|
|
105
|
+
userId: string;
|
|
106
|
+
reason: string;
|
|
107
|
+
};
|
|
108
|
+
"pairing:expired": {
|
|
109
|
+
code: string;
|
|
110
|
+
channel: string;
|
|
111
|
+
userId: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type EventKey = keyof EventMap;
|
|
116
|
+
|
|
117
|
+
export interface EventHandler<K extends EventKey> {
|
|
118
|
+
(data: EventMap[K]): void | Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class TypedEventBusImpl {
|
|
122
|
+
private emitter = new EventEmitter();
|
|
123
|
+
private logPrefix = "[events]";
|
|
124
|
+
|
|
125
|
+
emit<K extends EventKey>(event: K, data: EventMap[K]): void {
|
|
126
|
+
const enrichedData = {
|
|
127
|
+
...data,
|
|
128
|
+
_eventId: crypto.randomUUID(),
|
|
129
|
+
_timestamp: Date.now(),
|
|
130
|
+
_event: event,
|
|
131
|
+
} as EventMap[K] & { _eventId: string; _timestamp: number; _event: string };
|
|
132
|
+
|
|
133
|
+
this.emitter.emit(event, enrichedData);
|
|
134
|
+
|
|
135
|
+
if (process.env.DEBUG_EVENTS === "true") {
|
|
136
|
+
console.log(
|
|
137
|
+
`${this.logPrefix} emitted: ${event}`,
|
|
138
|
+
JSON.stringify(data, null, 2)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
on<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
|
|
144
|
+
this.emitter.on(event, handler);
|
|
145
|
+
return () => this.off(event, handler);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
once<K extends EventKey>(event: K, handler: EventHandler<K>): void {
|
|
149
|
+
this.emitter.once(event, handler);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
off<K extends EventKey>(event: K, handler: EventHandler<K>): void {
|
|
153
|
+
this.emitter.off(event, handler);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
removeAllListeners<K extends EventKey>(event?: K): void {
|
|
157
|
+
if (event) {
|
|
158
|
+
this.emitter.removeAllListeners(event);
|
|
159
|
+
} else {
|
|
160
|
+
this.emitter.removeAllListeners();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
listenerCount<K extends EventKey>(event: K): number {
|
|
165
|
+
return this.emitter.listenerCount(event);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const eventBus = new TypedEventBusImpl();
|
|
170
|
+
|
|
171
|
+
export type TypedEventBus = typeof eventBus;
|
package/src/gateway/server.ts
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
import { ChannelManager } from "../channels/manager";
|
|
13
13
|
import { Agent, watchEthics, watchSoul, watchUser } from "../agent/index";
|
|
14
14
|
import { AgentRunner } from "../agent/providers/index";
|
|
15
|
+
import { tool as aiTool, jsonSchema } from "ai";
|
|
15
16
|
import type { IncomingMessage } from "../channels/base";
|
|
16
|
-
import
|
|
17
|
+
import { mkdirSync, rmSync, unlinkSync } from "node:fs";
|
|
17
18
|
import * as path from "node:path";
|
|
18
19
|
import { dbService, type ChatMessageRow } from "../storage/sqlite";
|
|
19
20
|
|
|
@@ -99,8 +100,8 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
99
100
|
|
|
100
101
|
const pidFile = expandPath(config.gateway?.pidFile ?? "~/.hive/gateway.pid");
|
|
101
102
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
104
|
+
await Bun.write(pidFile, process.pid.toString());
|
|
104
105
|
} catch (error) {
|
|
105
106
|
log.warn(`Could not write PID file: ${(error as Error).message}`);
|
|
106
107
|
}
|
|
@@ -119,21 +120,109 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
119
120
|
await agent.initialize();
|
|
120
121
|
log.info(`Agent initialized: ${agent.agentId}`);
|
|
121
122
|
|
|
123
|
+
function prepareTools(agentInstance: Agent, sessionId: string) {
|
|
124
|
+
const tools: Record<string, any> = {};
|
|
125
|
+
|
|
126
|
+
const registry = agentInstance.getToolRegistry();
|
|
127
|
+
if (registry) {
|
|
128
|
+
for (const t of registry.list()) {
|
|
129
|
+
tools[t.name] = aiTool({
|
|
130
|
+
description: t.description,
|
|
131
|
+
parameters: jsonSchema(t.parameters as any),
|
|
132
|
+
execute: async (args: any): Promise<any> => {
|
|
133
|
+
log.info(`🛠️ LLM calling native tool: ${t.name}`);
|
|
134
|
+
const res = await registry.execute(t.name, { ...args, sessionId });
|
|
135
|
+
// AI SDK expects { content: string } or string, convert from ToolResult
|
|
136
|
+
return { content: res.success ? JSON.stringify(res.result) : `Error: ${res.error}` };
|
|
137
|
+
},
|
|
138
|
+
} as any);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const mcp = agentInstance.getMCPManager();
|
|
143
|
+
if (mcp) {
|
|
144
|
+
for (const server of mcp.listServers()) {
|
|
145
|
+
if (server.status !== "connected") continue;
|
|
146
|
+
const serverTools = mcp.getServerTools(server.name);
|
|
147
|
+
for (const t of serverTools) {
|
|
148
|
+
const mcpToolName = `${server.name}__${t.name}`.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
149
|
+
tools[mcpToolName] = aiTool({
|
|
150
|
+
description: t.description,
|
|
151
|
+
parameters: jsonSchema(t.inputSchema as any),
|
|
152
|
+
execute: async (args: any): Promise<any> => {
|
|
153
|
+
log.info(`🛠️ LLM calling MCP tool: ${t.name} (on ${server.name})`);
|
|
154
|
+
const res = await mcp.callTool(server.name, t.name, args as any);
|
|
155
|
+
// AI SDK expects { content: string } or string
|
|
156
|
+
return { content: typeof res === 'string' ? res : JSON.stringify(res) };
|
|
157
|
+
},
|
|
158
|
+
} as any);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Object.keys(tools).length > 0 ? tools : undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
agent.on("cron", (sessionId: string, task: string) => {
|
|
167
|
+
log.info(`[CRON] Triggered task '${task}' for session ${sessionId}`);
|
|
168
|
+
dbService.addMessage(sessionId, "system", `Scheduled task triggered: ${task}. Please execute it now.`);
|
|
169
|
+
|
|
170
|
+
laneQueue.enqueue(sessionId, async (_t, signal) => {
|
|
171
|
+
if (signal.aborted) return;
|
|
172
|
+
try {
|
|
173
|
+
const history = dbService.getMessages(sessionId);
|
|
174
|
+
const messages = history.map((row: ChatMessageRow) => ({
|
|
175
|
+
role: row.role as "user" | "assistant" | "system",
|
|
176
|
+
content: row.content,
|
|
177
|
+
}));
|
|
178
|
+
const systemPrompt = agent.buildPrompt();
|
|
179
|
+
|
|
180
|
+
const provider = config.models?.defaultProvider ?? "gemini";
|
|
181
|
+
|
|
182
|
+
log.info(`[CRON] Generating response for session ${sessionId}...`);
|
|
183
|
+
const response = await runner.generate({
|
|
184
|
+
provider: provider as any,
|
|
185
|
+
system: systemPrompt,
|
|
186
|
+
messages,
|
|
187
|
+
maxTokens: 4096,
|
|
188
|
+
tools: prepareTools(agent, sessionId),
|
|
189
|
+
maxSteps: 5,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const responseContent = response.content?.trim() || "Task completed.";
|
|
193
|
+
dbService.addMessage(sessionId, "assistant", responseContent);
|
|
194
|
+
|
|
195
|
+
const parts = sessionId.split(":");
|
|
196
|
+
const isWebchat = parts[2] === "webchat";
|
|
197
|
+
if (isWebchat) {
|
|
198
|
+
const session = sessionManager.get(sessionId);
|
|
199
|
+
if (session?.ws) {
|
|
200
|
+
session.ws.send(JSON.stringify({ type: "message", sessionId: sessionId, content: responseContent } as OutboundMessage));
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
await channelManager.send(parts[2], sessionId, responseContent);
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
log.error(`[CRON] Error for session ${sessionId}: ${(error as Error).message}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
122
211
|
// Set up hot reload watchers
|
|
123
212
|
const watchers: Array<() => void> = [];
|
|
124
213
|
const soulPath = path.join(workspacePath, "SOUL.md");
|
|
125
214
|
const userPath = path.join(workspacePath, "USER.md");
|
|
126
215
|
const ethicsPath = path.join(workspacePath, "ETHICS.md");
|
|
127
216
|
|
|
128
|
-
if (
|
|
217
|
+
if (await Bun.file(soulPath).exists()) {
|
|
129
218
|
watchers.push(watchSoul(soulPath, () => agent.reloadSoul()));
|
|
130
219
|
log.debug("Watching SOUL.md for changes");
|
|
131
220
|
}
|
|
132
|
-
if (
|
|
221
|
+
if (await Bun.file(userPath).exists()) {
|
|
133
222
|
watchers.push(watchUser(userPath, () => agent.reloadUser()));
|
|
134
223
|
log.debug("Watching USER.md for changes");
|
|
135
224
|
}
|
|
136
|
-
if (
|
|
225
|
+
if (await Bun.file(ethicsPath).exists()) {
|
|
137
226
|
watchers.push(watchEthics(ethicsPath, async () => agent.reloadEthics()));
|
|
138
227
|
log.debug("Watching ETHICS.md for changes");
|
|
139
228
|
}
|
|
@@ -150,7 +239,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
150
239
|
function getUnifiedUserId(channel: string, peerId: string): string {
|
|
151
240
|
const userConfig = config.user;
|
|
152
241
|
if (!userConfig) return peerId;
|
|
153
|
-
|
|
242
|
+
|
|
154
243
|
const channelId = userConfig.channels?.[channel];
|
|
155
244
|
if (channelId && channelId === peerId) {
|
|
156
245
|
return userConfig.id;
|
|
@@ -188,6 +277,8 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
188
277
|
system: systemPrompt,
|
|
189
278
|
messages,
|
|
190
279
|
maxTokens: 4096,
|
|
280
|
+
tools: prepareTools(agent, message.sessionId),
|
|
281
|
+
maxSteps: 5,
|
|
191
282
|
});
|
|
192
283
|
|
|
193
284
|
const responseContent = response.content || "...";
|
|
@@ -250,23 +341,25 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
250
341
|
if (!subPath) subPath = "index.html";
|
|
251
342
|
|
|
252
343
|
const filePath = path.join(dashboardDir, subPath);
|
|
253
|
-
|
|
344
|
+
const dashFile = Bun.file(filePath);
|
|
345
|
+
const dashFileStat = await dashFile.exists();
|
|
346
|
+
if (dashFileStat) {
|
|
254
347
|
if (subPath === "index.html" && token) {
|
|
255
348
|
const urlToken = url.searchParams.get("token");
|
|
256
349
|
if (!urlToken || urlToken !== token) {
|
|
257
350
|
return Response.redirect(`/dashboard?token=${token}`, 302);
|
|
258
351
|
}
|
|
259
|
-
const html =
|
|
352
|
+
const html = await dashFile.text();
|
|
260
353
|
const injected = html.replace(
|
|
261
354
|
"</head>",
|
|
262
355
|
` <script>window.HIVE_AUTH_TOKEN = "${token}";</script>\n</head>`
|
|
263
356
|
);
|
|
264
357
|
return new Response(injected, { headers: { "Content-Type": "text/html" } });
|
|
265
358
|
}
|
|
266
|
-
return new Response(
|
|
359
|
+
return new Response(dashFile);
|
|
267
360
|
}
|
|
268
361
|
const indexHtml = path.join(dashboardDir, "index.html");
|
|
269
|
-
if (
|
|
362
|
+
if (await Bun.file(indexHtml).exists()) return new Response(Bun.file(indexHtml));
|
|
270
363
|
return new Response("Dashboard build not found. Run 'bun run build' in packages/dashboard.", { status: 404 });
|
|
271
364
|
}
|
|
272
365
|
|
|
@@ -347,7 +440,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
347
440
|
const channelEntry = config.channels[name] as any;
|
|
348
441
|
channelEntry.accounts = channelEntry.accounts || {};
|
|
349
442
|
channelEntry.accounts[accountId] = channelConfigData;
|
|
350
|
-
saveConfig(config);
|
|
443
|
+
await saveConfig(config);
|
|
351
444
|
await channelManager.removeChannel(name, accountId);
|
|
352
445
|
await channelManager.startChannel(name, accountId);
|
|
353
446
|
return Response.json({ success: true });
|
|
@@ -372,7 +465,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
372
465
|
const channelEntry = config.channels[name] as any;
|
|
373
466
|
channelEntry.accounts = channelEntry.accounts || {};
|
|
374
467
|
channelEntry.accounts[accountId] = body.config;
|
|
375
|
-
saveConfig(config);
|
|
468
|
+
await saveConfig(config);
|
|
376
469
|
await channelManager.removeChannel(name, accountId);
|
|
377
470
|
await channelManager.startChannel(name, accountId);
|
|
378
471
|
return Response.json({ success: true });
|
|
@@ -386,7 +479,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
386
479
|
delete config.channels[name];
|
|
387
480
|
}
|
|
388
481
|
}
|
|
389
|
-
saveConfig(config);
|
|
482
|
+
await saveConfig(config);
|
|
390
483
|
await channelManager.removeChannel(name, accountId);
|
|
391
484
|
}
|
|
392
485
|
return Response.json({ success: true });
|
|
@@ -424,9 +517,9 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
424
517
|
const managedDir = expandPath(config.skills?.managedDir ?? "~/.hive/skills");
|
|
425
518
|
const skillDir = path.join(managedDir, name);
|
|
426
519
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
427
|
-
|
|
520
|
+
mkdirSync(skillDir, { recursive: true });
|
|
428
521
|
const skillMd = raw || `---\nname: ${name}\ndescription: ${description || ""}\n---\n${content}`;
|
|
429
|
-
|
|
522
|
+
await Bun.write(skillFile, skillMd);
|
|
430
523
|
agent.reloadSkills();
|
|
431
524
|
return Response.json({ success: true });
|
|
432
525
|
}
|
|
@@ -448,7 +541,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
448
541
|
} else {
|
|
449
542
|
config.skills.allowBundled = config.skills.allowBundled.filter((n) => n !== bundledName);
|
|
450
543
|
}
|
|
451
|
-
saveConfig(config);
|
|
544
|
+
await saveConfig(config);
|
|
452
545
|
await agent.updateConfig(config);
|
|
453
546
|
agent.reloadSkills();
|
|
454
547
|
return Response.json({ success: true });
|
|
@@ -464,7 +557,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
464
557
|
}
|
|
465
558
|
const skillFile = path.join(skill.path, "SKILL.md");
|
|
466
559
|
const skillMd = raw || `---\nname: ${name || skillName}\ndescription: ${description || ""}\n---\n${content}`;
|
|
467
|
-
|
|
560
|
+
await Bun.write(skillFile, skillMd);
|
|
468
561
|
agent.reloadSkills();
|
|
469
562
|
return Response.json({ success: true });
|
|
470
563
|
}
|
|
@@ -472,12 +565,12 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
472
565
|
if (req.method === "DELETE") {
|
|
473
566
|
const skill = agent.getSkills().find((s) => s.name === skillName);
|
|
474
567
|
if (skill?.source === "managed") {
|
|
475
|
-
|
|
568
|
+
rmSync(skill.path, { recursive: true, force: true });
|
|
476
569
|
agent.reloadSkills();
|
|
477
570
|
} else if (skill?.source === "bundled") {
|
|
478
571
|
config.skills = config.skills || {};
|
|
479
572
|
config.skills.allowBundled = (config.skills.allowBundled || []).filter((n) => n !== skillName);
|
|
480
|
-
saveConfig(config);
|
|
573
|
+
await saveConfig(config);
|
|
481
574
|
await agent.updateConfig(config);
|
|
482
575
|
agent.reloadSkills();
|
|
483
576
|
}
|
|
@@ -500,7 +593,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
500
593
|
if (defaultProvider) config.models.defaultProvider = defaultProvider;
|
|
501
594
|
if (defaults) config.models.defaults = { ...(config.models.defaults || {}), ...defaults };
|
|
502
595
|
if (providers) config.models.providers = { ...(config.models.providers || {}), ...providers };
|
|
503
|
-
saveConfig(config);
|
|
596
|
+
await saveConfig(config);
|
|
504
597
|
await agent.updateConfig(config);
|
|
505
598
|
return Response.json({ success: true });
|
|
506
599
|
}
|
|
@@ -523,7 +616,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
523
616
|
config.mcp = config.mcp || {};
|
|
524
617
|
config.mcp.servers = config.mcp.servers || {};
|
|
525
618
|
config.mcp.servers[body.name] = body.config;
|
|
526
|
-
saveConfig(config);
|
|
619
|
+
await saveConfig(config);
|
|
527
620
|
await agent.updateConfig(config);
|
|
528
621
|
return Response.json({ success: true });
|
|
529
622
|
}
|
|
@@ -552,14 +645,14 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
552
645
|
config.mcp = config.mcp || {};
|
|
553
646
|
config.mcp.servers = config.mcp.servers || {};
|
|
554
647
|
config.mcp.servers[serverName] = body.config;
|
|
555
|
-
saveConfig(config);
|
|
648
|
+
await saveConfig(config);
|
|
556
649
|
await agent.updateConfig(config);
|
|
557
650
|
return Response.json({ success: true });
|
|
558
651
|
}
|
|
559
652
|
if (req.method === "DELETE") {
|
|
560
653
|
if (config.mcp?.servers?.[serverName]) {
|
|
561
654
|
delete config.mcp.servers[serverName];
|
|
562
|
-
saveConfig(config);
|
|
655
|
+
await saveConfig(config);
|
|
563
656
|
await agent.updateConfig(config);
|
|
564
657
|
}
|
|
565
658
|
return Response.json({ success: true });
|
|
@@ -590,16 +683,17 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
590
683
|
user: "# User Profile\n\nAdd user preferences here.",
|
|
591
684
|
ethics: "# Ethics\n\nDefine ethical guidelines here.",
|
|
592
685
|
};
|
|
593
|
-
const
|
|
594
|
-
|
|
686
|
+
const wsFile = Bun.file(filePath);
|
|
687
|
+
const content = (await wsFile.exists())
|
|
688
|
+
? await wsFile.text()
|
|
595
689
|
: defaults[wsType];
|
|
596
690
|
return new Response(content, { headers: { "Content-Type": "text/plain" } });
|
|
597
691
|
}
|
|
598
692
|
|
|
599
693
|
if (req.method === "POST") {
|
|
600
694
|
const content = await req.text();
|
|
601
|
-
|
|
602
|
-
|
|
695
|
+
mkdirSync(workspacePath, { recursive: true });
|
|
696
|
+
await Bun.write(filePath, content);
|
|
603
697
|
if (wsType === "soul") agent.reloadSoul();
|
|
604
698
|
if (wsType === "user") agent.reloadUser();
|
|
605
699
|
if (wsType === "ethics") await agent.reloadEthics();
|
|
@@ -611,7 +705,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
611
705
|
// ── Reload API ───────────────────────────────────────────────────────
|
|
612
706
|
if (url.pathname === "/api/reload" && req.method === "POST") {
|
|
613
707
|
try {
|
|
614
|
-
const newConfig = loadConfig();
|
|
708
|
+
const newConfig = await loadConfig();
|
|
615
709
|
await agent.updateConfig(newConfig);
|
|
616
710
|
await agent.reload();
|
|
617
711
|
log.info("Configuration reloaded via API");
|
|
@@ -628,7 +722,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
628
722
|
if (url.pathname === "/api/user/channels" && req.method === "POST") {
|
|
629
723
|
const body = await req.json().catch(() => ({}));
|
|
630
724
|
const { channel, channelUserId } = body;
|
|
631
|
-
|
|
725
|
+
|
|
632
726
|
if (!channel || !channelUserId) {
|
|
633
727
|
return Response.json({ success: false, error: "Missing channel or channelUserId" }, { status: 400 });
|
|
634
728
|
}
|
|
@@ -636,10 +730,10 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
636
730
|
config.user = config.user || { id: "", name: "User" };
|
|
637
731
|
config.user.channels = config.user.channels || {};
|
|
638
732
|
config.user.channels[channel] = channelUserId;
|
|
639
|
-
|
|
640
|
-
saveConfig(config);
|
|
733
|
+
|
|
734
|
+
await saveConfig(config);
|
|
641
735
|
log.info(`Linked channel ${channel} to user ID ${channelUserId}`);
|
|
642
|
-
|
|
736
|
+
|
|
643
737
|
return Response.json({ success: true, channels: config.user.channels });
|
|
644
738
|
}
|
|
645
739
|
|
|
@@ -748,6 +842,8 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
748
842
|
system: systemPrompt,
|
|
749
843
|
messages,
|
|
750
844
|
maxTokens: 4096,
|
|
845
|
+
tools: prepareTools(agent, msg.sessionId),
|
|
846
|
+
maxSteps: 5,
|
|
751
847
|
});
|
|
752
848
|
|
|
753
849
|
const content = response.content?.trim() || "...";
|
|
@@ -813,14 +909,14 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
813
909
|
}
|
|
814
910
|
await channelManager.stopAll();
|
|
815
911
|
server.stop();
|
|
816
|
-
try {
|
|
912
|
+
try { unlinkSync(pidFile); } catch { }
|
|
817
913
|
process.exit(0);
|
|
818
914
|
});
|
|
819
915
|
|
|
820
916
|
process.on("SIGHUP", async () => {
|
|
821
917
|
log.info("Received SIGHUP, reloading configuration...");
|
|
822
918
|
try {
|
|
823
|
-
const newConfig = loadConfig();
|
|
919
|
+
const newConfig = await loadConfig();
|
|
824
920
|
await agent.updateConfig(newConfig);
|
|
825
921
|
await agent.reload();
|
|
826
922
|
log.info("Configuration reloaded successfully");
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,8 @@ export * from "./agent/index.ts";
|
|
|
4
4
|
export * from "./agent/ethics.ts";
|
|
5
5
|
export * from "./agent/context.ts";
|
|
6
6
|
export * from "./agent/soul.ts";
|
|
7
|
-
export
|
|
7
|
+
export { loadUser, watchUser, type UserConfig as AgentUserConfig } from "./agent/user.ts";
|
|
8
|
+
export * from "./agent/workspace.ts";
|
|
8
9
|
export * from "./channels/index.ts";
|
|
9
10
|
export * from "./channels/manager.ts";
|
|
10
11
|
export * from "./config/loader.ts";
|
|
@@ -14,8 +15,15 @@ export * from "./utils/retry.ts";
|
|
|
14
15
|
export * from "./security/index.ts";
|
|
15
16
|
export * from "./heartbeat/index.ts";
|
|
16
17
|
export * from "./tools/registry.ts";
|
|
18
|
+
export * from "./tools/dynamic-registry.ts";
|
|
17
19
|
export * from "./multi-agent/manager.ts";
|
|
18
20
|
export * from "./multi-agent/bindings.ts";
|
|
19
21
|
export * from "./multi-agent/sandbox.ts";
|
|
20
22
|
export * from "./multi-agent/subagents.ts";
|
|
21
23
|
export * from "./memory/notes.ts";
|
|
24
|
+
export * from "./events/event-bus.ts";
|
|
25
|
+
export * from "./state/store.ts";
|
|
26
|
+
export * from "./resilience/circuit-breaker.ts";
|
|
27
|
+
export * from "./plugins/index.ts";
|
|
28
|
+
export * from "./agents/index.ts";
|
|
29
|
+
export * from "./canvas/index.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { Config, AgentEntry } from "../config/loader.ts";
|
|
4
4
|
import { Agent } from "../agent/index.ts";
|
|
@@ -30,7 +30,7 @@ export class AgentManager {
|
|
|
30
30
|
|
|
31
31
|
async initialize(): Promise<void> {
|
|
32
32
|
const agentList = this.config.agents?.list ?? [];
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
if (agentList.length === 0) {
|
|
35
35
|
await this.createDefaultAgent();
|
|
36
36
|
return;
|
|
@@ -45,7 +45,7 @@ export class AgentManager {
|
|
|
45
45
|
|
|
46
46
|
private async createDefaultAgent(): Promise<void> {
|
|
47
47
|
const defaultWorkspace = this.expandPath(
|
|
48
|
-
this.config.agent?.baseDir
|
|
48
|
+
this.config.agent?.baseDir
|
|
49
49
|
? `${this.config.agent.baseDir}/main/workspace`
|
|
50
50
|
: "~/.hive/agents/main/workspace"
|
|
51
51
|
);
|
|
@@ -59,7 +59,7 @@ export class AgentManager {
|
|
|
59
59
|
|
|
60
60
|
private async createAgent(entry: AgentEntry): Promise<void> {
|
|
61
61
|
const workspacePath = this.expandPath(entry.workspace);
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
this.ensureWorkspace(workspacePath);
|
|
64
64
|
|
|
65
65
|
const agent = new Agent({
|
|
@@ -121,19 +121,19 @@ export class AgentManager {
|
|
|
121
121
|
];
|
|
122
122
|
|
|
123
123
|
for (const dir of dirs) {
|
|
124
|
-
if (!
|
|
125
|
-
|
|
124
|
+
if (!existsSync(dir)) {
|
|
125
|
+
mkdirSync(dir, { recursive: true });
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
const soulPath = path.join(workspacePath, "SOUL.md");
|
|
130
|
-
if (!
|
|
131
|
-
|
|
130
|
+
if (!existsSync(soulPath)) {
|
|
131
|
+
Bun.write(soulPath, `# Agent Identity\n\nThis is agent ${path.basename(workspacePath)}.\n`);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
const userPath = path.join(workspacePath, "USER.md");
|
|
135
|
-
if (!
|
|
136
|
-
|
|
135
|
+
if (!existsSync(userPath)) {
|
|
136
|
+
Bun.write(userPath, `# User Profile\n\nName: User\nLanguage: English\n`);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -172,7 +172,7 @@ export class AgentManager {
|
|
|
172
172
|
|
|
173
173
|
const baseDir = this.config.agent?.baseDir?.replace(/^~/, process.env.HOME ?? "")
|
|
174
174
|
?? `${process.env.HOME}/.hive/agents`;
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
const workspace = options.workspace ?? `${baseDir}/${agentId}/workspace`;
|
|
177
177
|
|
|
178
178
|
const entry: AgentEntry = {
|
|
@@ -182,7 +182,7 @@ export class AgentManager {
|
|
|
182
182
|
};
|
|
183
183
|
|
|
184
184
|
await this.createAgent(entry);
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
return this.agents.get(agentId)!;
|
|
187
187
|
}
|
|
188
188
|
|