@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.
Files changed (53) hide show
  1. package/package.json +10 -9
  2. package/src/agent/ethics.ts +70 -68
  3. package/src/agent/index.ts +48 -17
  4. package/src/agent/providers/index.ts +11 -5
  5. package/src/agent/soul.ts +19 -15
  6. package/src/agent/user.ts +19 -15
  7. package/src/agent/workspace.ts +6 -6
  8. package/src/agents/index.ts +4 -0
  9. package/src/agents/inter-agent-bus.test.ts +264 -0
  10. package/src/agents/inter-agent-bus.ts +279 -0
  11. package/src/agents/registry.test.ts +275 -0
  12. package/src/agents/registry.ts +273 -0
  13. package/src/agents/router.test.ts +229 -0
  14. package/src/agents/router.ts +251 -0
  15. package/src/agents/team-coordinator.test.ts +401 -0
  16. package/src/agents/team-coordinator.ts +480 -0
  17. package/src/canvas/canvas-manager.test.ts +159 -0
  18. package/src/canvas/canvas-manager.ts +219 -0
  19. package/src/canvas/canvas-tools.ts +189 -0
  20. package/src/canvas/index.ts +2 -0
  21. package/src/channels/whatsapp.ts +12 -12
  22. package/src/config/loader.ts +12 -9
  23. package/src/events/event-bus.test.ts +98 -0
  24. package/src/events/event-bus.ts +171 -0
  25. package/src/gateway/server.ts +131 -35
  26. package/src/index.ts +9 -1
  27. package/src/multi-agent/manager.ts +12 -12
  28. package/src/plugins/api.ts +129 -0
  29. package/src/plugins/index.ts +2 -0
  30. package/src/plugins/loader.test.ts +285 -0
  31. package/src/plugins/loader.ts +363 -0
  32. package/src/resilience/circuit-breaker.test.ts +129 -0
  33. package/src/resilience/circuit-breaker.ts +223 -0
  34. package/src/security/google-chat.test.ts +219 -0
  35. package/src/security/google-chat.ts +269 -0
  36. package/src/security/index.ts +5 -0
  37. package/src/security/pairing.test.ts +302 -0
  38. package/src/security/pairing.ts +250 -0
  39. package/src/security/rate-limit.test.ts +239 -0
  40. package/src/security/rate-limit.ts +270 -0
  41. package/src/security/signal.test.ts +92 -0
  42. package/src/security/signal.ts +321 -0
  43. package/src/state/store.test.ts +190 -0
  44. package/src/state/store.ts +310 -0
  45. package/src/storage/sqlite.ts +3 -3
  46. package/src/tools/cron.ts +42 -2
  47. package/src/tools/dynamic-registry.test.ts +226 -0
  48. package/src/tools/dynamic-registry.ts +258 -0
  49. package/src/tools/fs.test.ts +127 -0
  50. package/src/tools/fs.ts +364 -0
  51. package/src/tools/index.ts +1 -0
  52. package/src/tools/read.ts +23 -19
  53. 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;
@@ -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 * as fs from "node:fs";
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
- fs.mkdirSync(path.dirname(pidFile), { recursive: true });
103
- fs.writeFileSync(pidFile, process.pid.toString());
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 (fs.existsSync(soulPath)) {
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 (fs.existsSync(userPath)) {
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 (fs.existsSync(ethicsPath)) {
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
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
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 = fs.readFileSync(filePath, "utf-8");
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(Bun.file(filePath));
359
+ return new Response(dashFile);
267
360
  }
268
361
  const indexHtml = path.join(dashboardDir, "index.html");
269
- if (fs.existsSync(indexHtml)) return new Response(Bun.file(indexHtml));
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
- fs.mkdirSync(skillDir, { recursive: true });
520
+ mkdirSync(skillDir, { recursive: true });
428
521
  const skillMd = raw || `---\nname: ${name}\ndescription: ${description || ""}\n---\n${content}`;
429
- fs.writeFileSync(skillFile, skillMd);
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
- fs.writeFileSync(skillFile, skillMd);
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
- fs.rmSync(skill.path, { recursive: true, force: true });
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 content = fs.existsSync(filePath)
594
- ? fs.readFileSync(filePath, "utf-8")
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
- fs.mkdirSync(workspacePath, { recursive: true });
602
- fs.writeFileSync(filePath, content);
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 { fs.unlinkSync(pidFile); } catch { }
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 * from "./agent/user.ts";
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 * as fs from "node:fs";
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 (!fs.existsSync(dir)) {
125
- fs.mkdirSync(dir, { recursive: true });
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 (!fs.existsSync(soulPath)) {
131
- fs.writeFileSync(soulPath, `# Agent Identity\n\nThis is agent ${path.basename(workspacePath)}.\n`);
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 (!fs.existsSync(userPath)) {
136
- fs.writeFileSync(userPath, `# User Profile\n\nName: User\nLanguage: English\n`);
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