@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.
- 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
package/src/tools/fs.ts
ADDED
|
@@ -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
|
+
}
|
package/src/tools/index.ts
CHANGED
package/src/tools/read.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
const entries =
|
|
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 =
|
|
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 (!
|
|
81
|
-
|
|
83
|
+
if (!existsSync(dir)) {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
151
|
-
|
|
154
|
+
await Bun.write(filePath, newContent);
|
|
155
|
+
|
|
152
156
|
return { success: true, path: filePath };
|
|
153
157
|
},
|
|
154
158
|
};
|