@johpaz/hive-core 1.0.7 → 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,364 @@
1
+ import type { Tool } from "./registry.ts";
2
+ import type { Config } from "../config/loader.ts";
3
+ import { logger } from "../utils/logger.ts";
4
+ import { readdir, stat, mkdir, rm, rmdir, unlink, copyFile, rename } from "fs/promises";
5
+ import * as path from "path";
6
+
7
+ interface FSConfig {
8
+ allowedPaths: string[];
9
+ sandboxed: boolean;
10
+ }
11
+
12
+ function validatePath(targetPath: string, allowedPaths: string[]): void {
13
+ const resolved = path.resolve(targetPath);
14
+ const isAllowed = allowedPaths.some((allowed) => resolved.startsWith(path.resolve(allowed)));
15
+
16
+ if (!isAllowed) {
17
+ throw new Error(`Path ${targetPath} not in allowed paths`);
18
+ }
19
+ }
20
+
21
+ function getFSConfig(config: Config): FSConfig {
22
+ const toolsConfig = config.tools as Record<string, unknown> | undefined;
23
+ const fsConfig = toolsConfig?.fs as Record<string, unknown> | undefined;
24
+
25
+ return {
26
+ allowedPaths: (fsConfig?.allowedPaths as string[]) ?? [process.cwd()],
27
+ sandboxed: (fsConfig?.sandboxed as boolean) ?? true,
28
+ };
29
+ }
30
+
31
+ export function createFSReadTool(config: Config): Tool {
32
+ const log = logger.child("fs-read");
33
+ const fsConfig = getFSConfig(config);
34
+
35
+ return {
36
+ name: "fs_read",
37
+ description: "Read a file from the filesystem",
38
+ parameters: {
39
+ type: "object",
40
+ properties: {
41
+ path: {
42
+ type: "string",
43
+ description: "Path to the file to read",
44
+ },
45
+ encoding: {
46
+ type: "string",
47
+ description: "File encoding (default: utf-8)",
48
+ },
49
+ },
50
+ required: ["path"],
51
+ },
52
+ execute: async (params: Record<string, unknown>) => {
53
+ const filePath = params.path as string;
54
+ const encoding = (params.encoding as BufferEncoding) ?? "utf-8";
55
+
56
+ if (fsConfig.sandboxed) {
57
+ validatePath(filePath, fsConfig.allowedPaths);
58
+ }
59
+
60
+ log.debug(`Reading: ${filePath}`);
61
+
62
+ const content = await Bun.file(filePath).text();
63
+
64
+ return {
65
+ success: true,
66
+ path: filePath,
67
+ content,
68
+ size: content.length,
69
+ };
70
+ },
71
+ };
72
+ }
73
+
74
+ export function createFSWriteTool(config: Config): Tool {
75
+ const log = logger.child("fs-write");
76
+ const fsConfig = getFSConfig(config);
77
+
78
+ return {
79
+ name: "fs_write",
80
+ description: "Write content to a file",
81
+ parameters: {
82
+ type: "object",
83
+ properties: {
84
+ path: {
85
+ type: "string",
86
+ description: "Path to the file to write",
87
+ },
88
+ content: {
89
+ type: "string",
90
+ description: "Content to write",
91
+ },
92
+ encoding: {
93
+ type: "string",
94
+ description: "File encoding (default: utf-8)",
95
+ },
96
+ },
97
+ required: ["path", "content"],
98
+ },
99
+ execute: async (params: Record<string, unknown>) => {
100
+ const filePath = params.path as string;
101
+ const content = params.content as string;
102
+ const encoding = (params.encoding as BufferEncoding) ?? "utf-8";
103
+
104
+ if (fsConfig.sandboxed) {
105
+ validatePath(filePath, fsConfig.allowedPaths);
106
+ }
107
+
108
+ log.debug(`Writing: ${filePath}`);
109
+
110
+ await Bun.write(filePath, content);
111
+
112
+ return {
113
+ success: true,
114
+ path: filePath,
115
+ bytesWritten: content.length,
116
+ };
117
+ },
118
+ };
119
+ }
120
+
121
+ export function createFSListTool(config: Config): Tool {
122
+ const log = logger.child("fs-list");
123
+ const fsConfig = getFSConfig(config);
124
+
125
+ return {
126
+ name: "fs_list",
127
+ description: "List contents of a directory",
128
+ parameters: {
129
+ type: "object",
130
+ properties: {
131
+ path: {
132
+ type: "string",
133
+ description: "Path to the directory",
134
+ },
135
+ recursive: {
136
+ type: "boolean",
137
+ description: "List recursively (default: false)",
138
+ },
139
+ },
140
+ required: ["path"],
141
+ },
142
+ execute: async (params: Record<string, unknown>) => {
143
+ const dirPath = params.path as string;
144
+ const recursive = (params.recursive as boolean) ?? false;
145
+
146
+ if (fsConfig.sandboxed) {
147
+ validatePath(dirPath, fsConfig.allowedPaths);
148
+ }
149
+
150
+ log.debug(`Listing: ${dirPath}`);
151
+
152
+ const entries = await readdir(dirPath, { withFileTypes: true });
153
+
154
+ const results: Array<{
155
+ name: string;
156
+ path: string;
157
+ type: string;
158
+ size: number;
159
+ modified: string;
160
+ }> = [];
161
+
162
+ for (const entry of entries) {
163
+ const fullPath = path.join(dirPath, entry.name);
164
+ const stats = await stat(fullPath);
165
+
166
+ results.push({
167
+ name: entry.name,
168
+ path: fullPath,
169
+ type: entry.isDirectory() ? "directory" : "file",
170
+ size: stats.size,
171
+ modified: stats.mtime.toISOString(),
172
+ });
173
+ }
174
+
175
+ if (recursive) {
176
+ const subdirs = results.filter((r) => r.type === "directory");
177
+ for (const subdir of subdirs) {
178
+ const subParams = { path: subdir.path, recursive: true };
179
+ const subResult = await createFSListTool(config).execute(subParams);
180
+ const subFiles = (subResult as { entries: unknown[] }).entries;
181
+ results.push(...(subFiles as typeof results));
182
+ }
183
+ }
184
+
185
+ return {
186
+ success: true,
187
+ path: dirPath,
188
+ entries: results,
189
+ count: results.length,
190
+ };
191
+ },
192
+ };
193
+ }
194
+
195
+ export function createFSMkdirTool(config: Config): Tool {
196
+ const log = logger.child("fs-mkdir");
197
+ const fsConfig = getFSConfig(config);
198
+
199
+ return {
200
+ name: "fs_mkdir",
201
+ description: "Create a directory",
202
+ parameters: {
203
+ type: "object",
204
+ properties: {
205
+ path: {
206
+ type: "string",
207
+ description: "Path to create",
208
+ },
209
+ },
210
+ required: ["path"],
211
+ },
212
+ execute: async (params: Record<string, unknown>) => {
213
+ const dirPath = params.path as string;
214
+
215
+ if (fsConfig.sandboxed) {
216
+ validatePath(dirPath, fsConfig.allowedPaths);
217
+ }
218
+
219
+ log.debug(`Creating directory: ${dirPath}`);
220
+
221
+ await mkdir(dirPath, { recursive: true });
222
+
223
+ return { success: true, path: dirPath };
224
+ },
225
+ };
226
+ }
227
+
228
+ export function createFSDeleteTool(config: Config): Tool {
229
+ const log = logger.child("fs-delete");
230
+ const fsConfig = getFSConfig(config);
231
+
232
+ return {
233
+ name: "fs_delete",
234
+ description: "Delete a file or directory",
235
+ parameters: {
236
+ type: "object",
237
+ properties: {
238
+ path: {
239
+ type: "string",
240
+ description: "Path to delete",
241
+ },
242
+ recursive: {
243
+ type: "boolean",
244
+ description: "Delete recursively for directories",
245
+ },
246
+ },
247
+ required: ["path"],
248
+ },
249
+ execute: async (params: Record<string, unknown>) => {
250
+ const targetPath = params.path as string;
251
+ const recursive = (params.recursive as boolean) ?? false;
252
+
253
+ if (fsConfig.sandboxed) {
254
+ validatePath(targetPath, fsConfig.allowedPaths);
255
+ }
256
+
257
+ log.debug(`Deleting: ${targetPath}`);
258
+
259
+ const stats = await stat(targetPath);
260
+
261
+ if (stats.isDirectory()) {
262
+ if (recursive) {
263
+ await rm(targetPath, { recursive: true });
264
+ } else {
265
+ await rmdir(targetPath);
266
+ }
267
+ } else {
268
+ await unlink(targetPath);
269
+ }
270
+
271
+ return { success: true, path: targetPath };
272
+ },
273
+ };
274
+ }
275
+
276
+ export function createFSCopyTool(config: Config): Tool {
277
+ const log = logger.child("fs-copy");
278
+ const fsConfig = getFSConfig(config);
279
+
280
+ return {
281
+ name: "fs_copy",
282
+ description: "Copy a file",
283
+ parameters: {
284
+ type: "object",
285
+ properties: {
286
+ source: {
287
+ type: "string",
288
+ description: "Source file path",
289
+ },
290
+ destination: {
291
+ type: "string",
292
+ description: "Destination file path",
293
+ },
294
+ },
295
+ required: ["source", "destination"],
296
+ },
297
+ execute: async (params: Record<string, unknown>) => {
298
+ const source = params.source as string;
299
+ const destination = params.destination as string;
300
+
301
+ if (fsConfig.sandboxed) {
302
+ validatePath(source, fsConfig.allowedPaths);
303
+ validatePath(destination, fsConfig.allowedPaths);
304
+ }
305
+
306
+ log.debug(`Copying: ${source} -> ${destination}`);
307
+
308
+ await copyFile(source, destination);
309
+
310
+ return { success: true, source, destination };
311
+ },
312
+ };
313
+ }
314
+
315
+ export function createFSMoveTool(config: Config): Tool {
316
+ const log = logger.child("fs-move");
317
+ const fsConfig = getFSConfig(config);
318
+
319
+ return {
320
+ name: "fs_move",
321
+ description: "Move/rename a file",
322
+ parameters: {
323
+ type: "object",
324
+ properties: {
325
+ source: {
326
+ type: "string",
327
+ description: "Source file path",
328
+ },
329
+ destination: {
330
+ type: "string",
331
+ description: "Destination file path",
332
+ },
333
+ },
334
+ required: ["source", "destination"],
335
+ },
336
+ execute: async (params: Record<string, unknown>) => {
337
+ const source = params.source as string;
338
+ const destination = params.destination as string;
339
+
340
+ if (fsConfig.sandboxed) {
341
+ validatePath(source, fsConfig.allowedPaths);
342
+ validatePath(destination, fsConfig.allowedPaths);
343
+ }
344
+
345
+ log.debug(`Moving: ${source} -> ${destination}`);
346
+
347
+ await rename(source, destination);
348
+
349
+ return { success: true, source, destination };
350
+ },
351
+ };
352
+ }
353
+
354
+ export function createFSTools(config: Config): Tool[] {
355
+ return [
356
+ createFSReadTool(config),
357
+ createFSWriteTool(config),
358
+ createFSListTool(config),
359
+ createFSMkdirTool(config),
360
+ createFSDeleteTool(config),
361
+ createFSCopyTool(config),
362
+ createFSMoveTool(config),
363
+ ];
364
+ }
@@ -4,3 +4,4 @@ export * from "./exec.ts";
4
4
  export * from "./web.ts";
5
5
  export * from "./notify.ts";
6
6
  export * from "./cron.ts";
7
+ export * from "./fs.ts";
package/src/tools/read.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as fs from "node:fs";
1
+ import { readdirSync, existsSync, mkdirSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { Tool } from "./registry.ts";
4
4
 
@@ -28,27 +28,30 @@ export const readTool: Tool = {
28
28
  const offset = (params.offset as number) ?? 1;
29
29
  const limit = (params.limit as number) ?? 2000;
30
30
 
31
- if (!fs.existsSync(filePath)) {
31
+ const file = Bun.file(filePath);
32
+ if (!(await file.exists())) {
32
33
  throw new Error(`File not found: ${filePath}`);
33
34
  }
34
35
 
35
- const stats = fs.statSync(filePath);
36
- if (stats.isDirectory()) {
37
- const entries = fs.readdirSync(filePath, { withFileTypes: true });
36
+ // Check if it's a directory by trying readdir
37
+ try {
38
+ const entries = readdirSync(filePath, { withFileTypes: true });
38
39
  return entries.map((e) => ({
39
40
  name: e.name,
40
41
  type: e.isDirectory() ? "directory" : "file",
41
42
  }));
43
+ } catch {
44
+ // Not a directory, read as file
42
45
  }
43
46
 
44
- const content = fs.readFileSync(filePath, "utf-8");
47
+ const content = await file.text();
45
48
  const lines = content.split("\n");
46
-
49
+
47
50
  const start = Math.max(0, offset - 1);
48
51
  const end = Math.min(lines.length, start + limit);
49
-
52
+
50
53
  const selected = lines.slice(start, end);
51
-
54
+
52
55
  return selected
53
56
  .map((line, i) => `${start + i + 1}: ${line}`)
54
57
  .join("\n");
@@ -77,12 +80,12 @@ export const writeTool: Tool = {
77
80
  const content = params.content as string;
78
81
 
79
82
  const dir = path.dirname(filePath);
80
- if (!fs.existsSync(dir)) {
81
- fs.mkdirSync(dir, { recursive: true });
83
+ if (!existsSync(dir)) {
84
+ mkdirSync(dir, { recursive: true });
82
85
  }
83
86
 
84
- fs.writeFileSync(filePath, content, "utf-8");
85
-
87
+ await Bun.write(filePath, content);
88
+
86
89
  return { success: true, path: filePath, bytes: content.length };
87
90
  },
88
91
  };
@@ -118,11 +121,12 @@ export const editTool: Tool = {
118
121
  const newString = params.newString as string;
119
122
  const replaceAll = (params.replaceAll as boolean) ?? false;
120
123
 
121
- if (!fs.existsSync(filePath)) {
124
+ const file = Bun.file(filePath);
125
+ if (!(await file.exists())) {
122
126
  throw new Error(`File not found: ${filePath}`);
123
127
  }
124
128
 
125
- const content = fs.readFileSync(filePath, "utf-8");
129
+ const content = await file.text();
126
130
 
127
131
  if (!content.includes(oldString)) {
128
132
  throw new Error(`String not found in file: ${oldString.substring(0, 50)}...`);
@@ -136,19 +140,19 @@ export const editTool: Tool = {
136
140
  if (index === -1) {
137
141
  throw new Error(`String not found in file`);
138
142
  }
139
-
143
+
140
144
  const occurrences = content.split(oldString).length - 1;
141
145
  if (occurrences > 1) {
142
146
  throw new Error(
143
147
  `Found ${occurrences} occurrences. Use replaceAll: true or provide more context.`
144
148
  );
145
149
  }
146
-
150
+
147
151
  newContent = content.replace(oldString, newString);
148
152
  }
149
153
 
150
- fs.writeFileSync(filePath, newContent, "utf-8");
151
-
154
+ await Bun.write(filePath, newContent);
155
+
152
156
  return { success: true, path: filePath };
153
157
  },
154
158
  };