@johpaz/hive-core 1.0.4 → 1.0.6
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 +2 -2
- package/src/agent/index.ts +14 -0
- package/src/gateway/server.ts +120 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Hive Gateway — Personal AI agent runtime",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -39,4 +39,4 @@
|
|
|
39
39
|
"./config": "./src/config/loader.ts",
|
|
40
40
|
"./utils": "./src/utils/logger.ts"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
package/src/agent/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Config } from "../config/loader.ts";
|
|
2
2
|
import { loadSoul } from "./soul.ts";
|
|
3
3
|
import { loadUser } from "./user.ts";
|
|
4
|
+
import { loadEthics, type EthicsConfig, DEFAULT_ETHICS } from "./ethics.ts";
|
|
4
5
|
import { buildSystemPrompt } from "./context.ts";
|
|
5
6
|
import { logger } from "../utils/logger.ts";
|
|
6
7
|
import * as path from "node:path";
|
|
8
|
+
import * as fs from "node:fs";
|
|
7
9
|
|
|
8
10
|
export interface AgentOptions {
|
|
9
11
|
agentId: string;
|
|
@@ -15,6 +17,7 @@ export class Agent {
|
|
|
15
17
|
readonly agentId: string;
|
|
16
18
|
readonly workspacePath: string;
|
|
17
19
|
private config: Config;
|
|
20
|
+
private ethics: EthicsConfig | null = null;
|
|
18
21
|
private log = logger.child("agent");
|
|
19
22
|
|
|
20
23
|
constructor(options: AgentOptions) {
|
|
@@ -28,6 +31,7 @@ export class Agent {
|
|
|
28
31
|
|
|
29
32
|
const soul = loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
30
33
|
const user = loadUser(path.join(this.workspacePath, "USER.md"));
|
|
34
|
+
this.ethics = await loadEthics(path.join(this.workspacePath, "ETHICS.md"));
|
|
31
35
|
|
|
32
36
|
if (soul) {
|
|
33
37
|
this.log.debug(`Loaded SOUL.md for agent ${this.agentId}`);
|
|
@@ -35,13 +39,23 @@ export class Agent {
|
|
|
35
39
|
if (user) {
|
|
36
40
|
this.log.debug(`Loaded USER.md for agent ${this.agentId}`);
|
|
37
41
|
}
|
|
42
|
+
if (this.ethics) {
|
|
43
|
+
this.log.debug(`Loaded ETHICS.md for agent ${this.agentId}`);
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
buildPrompt(): string {
|
|
41
48
|
const soul = loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
42
49
|
const user = loadUser(path.join(this.workspacePath, "USER.md"));
|
|
50
|
+
|
|
51
|
+
const ethicsConfig = this.ethics ?? {
|
|
52
|
+
raw: DEFAULT_ETHICS,
|
|
53
|
+
loadedAt: new Date(),
|
|
54
|
+
path: "",
|
|
55
|
+
};
|
|
43
56
|
|
|
44
57
|
return buildSystemPrompt({
|
|
58
|
+
ethics: ethicsConfig,
|
|
45
59
|
soul: soul ?? {
|
|
46
60
|
identity: "",
|
|
47
61
|
personality: "",
|
package/src/gateway/server.ts
CHANGED
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
isSlashCommand,
|
|
9
9
|
executeSlashCommand,
|
|
10
10
|
} from "./slash-commands.ts";
|
|
11
|
+
import { ChannelManager } from "../channels/manager.ts";
|
|
12
|
+
import { Agent } from "../agent/index.ts";
|
|
13
|
+
import { AgentRunner } from "../agent/providers/index.ts";
|
|
14
|
+
import type { IncomingMessage } from "../channels/base.ts";
|
|
11
15
|
import * as fs from "node:fs";
|
|
12
16
|
import * as path from "node:path";
|
|
13
17
|
|
|
@@ -54,7 +58,7 @@ const UI_HTML = `<!DOCTYPE html>
|
|
|
54
58
|
</head>
|
|
55
59
|
<body>
|
|
56
60
|
<div class="header">
|
|
57
|
-
<h1
|
|
61
|
+
<h1>🐝 Hive</h1>
|
|
58
62
|
<span class="status" id="status">Connecting...</span>
|
|
59
63
|
</div>
|
|
60
64
|
<div class="container">
|
|
@@ -172,6 +176,10 @@ interface WebSocketData {
|
|
|
172
176
|
authenticatedAt: number;
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
interface SessionMessages {
|
|
180
|
+
messages: Array<{ role: string; content: string }>;
|
|
181
|
+
}
|
|
182
|
+
|
|
175
183
|
export async function startGateway(config: Config): Promise<void> {
|
|
176
184
|
const host = config.gateway?.host ?? "127.0.0.1";
|
|
177
185
|
const port = config.gateway?.port ?? 18790;
|
|
@@ -193,6 +201,78 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
193
201
|
log.warn(`Could not write PID file: ${(error as Error).message}`);
|
|
194
202
|
}
|
|
195
203
|
|
|
204
|
+
// Initialize agent
|
|
205
|
+
const agentList = config.agents?.list ?? [];
|
|
206
|
+
const defaultAgent = agentList.find((a) => a.default) ?? agentList[0];
|
|
207
|
+
const workspacePath = expandPath(defaultAgent?.workspace ?? "~/.hive/workspace");
|
|
208
|
+
|
|
209
|
+
const agent = new Agent({
|
|
210
|
+
agentId: defaultAgent?.id ?? "main",
|
|
211
|
+
config,
|
|
212
|
+
workspacePath,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await agent.initialize();
|
|
216
|
+
log.info(`Agent initialized: ${agent.agentId}`);
|
|
217
|
+
|
|
218
|
+
// Initialize LLM runner
|
|
219
|
+
const runner = new AgentRunner(config);
|
|
220
|
+
const provider = config.models?.defaultProvider ?? "gemini";
|
|
221
|
+
const model = config.models?.defaults?.[provider] ?? "unknown";
|
|
222
|
+
log.info(`LLM provider: ${provider}/${model}`);
|
|
223
|
+
|
|
224
|
+
// Initialize channel manager
|
|
225
|
+
const channelManager = new ChannelManager(config);
|
|
226
|
+
|
|
227
|
+
// Track sessions
|
|
228
|
+
const sessionStore = new Map<string, SessionMessages>();
|
|
229
|
+
|
|
230
|
+
// Handle messages from channels
|
|
231
|
+
channelManager.onMessage(async (message: IncomingMessage) => {
|
|
232
|
+
log.info(`Message from ${message.channel}:${message.accountId} - Session: ${message.sessionId}`);
|
|
233
|
+
log.debug(`Content: ${message.content.substring(0, 100)}...`);
|
|
234
|
+
|
|
235
|
+
// Get or create session
|
|
236
|
+
let session = sessionStore.get(message.sessionId);
|
|
237
|
+
if (!session) {
|
|
238
|
+
session = { messages: [] };
|
|
239
|
+
sessionStore.set(message.sessionId, session);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Add user message
|
|
243
|
+
session.messages.push({ role: "user", content: message.content });
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Build system prompt
|
|
247
|
+
const systemPrompt = agent.buildPrompt();
|
|
248
|
+
|
|
249
|
+
// Generate response
|
|
250
|
+
const response = await runner.generate({
|
|
251
|
+
provider: provider as any,
|
|
252
|
+
system: systemPrompt,
|
|
253
|
+
messages: session.messages,
|
|
254
|
+
maxTokens: 4096,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Add assistant message to history
|
|
258
|
+
session.messages.push({ role: "assistant", content: response.content });
|
|
259
|
+
|
|
260
|
+
// Send response back through channel
|
|
261
|
+
await channelManager.send(message.channel, message.sessionId, { content: response.content });
|
|
262
|
+
|
|
263
|
+
log.info(`Response sent to ${message.sessionId}`);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
log.error(`Error processing message: ${(error as Error).message}`);
|
|
266
|
+
await channelManager.send(message.channel, message.sessionId, {
|
|
267
|
+
content: `Error: ${(error as Error).message}`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Initialize and start channels
|
|
273
|
+
await channelManager.initialize();
|
|
274
|
+
await channelManager.startAll();
|
|
275
|
+
|
|
196
276
|
const server = Bun.serve<WebSocketData>({
|
|
197
277
|
port,
|
|
198
278
|
hostname: host,
|
|
@@ -236,6 +316,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
236
316
|
createdAt: s.createdAt,
|
|
237
317
|
messageCount: s.messageCount,
|
|
238
318
|
})),
|
|
319
|
+
channels: channelManager.listChannels(),
|
|
239
320
|
queue: {
|
|
240
321
|
activeSessions: 0,
|
|
241
322
|
},
|
|
@@ -255,7 +336,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
255
336
|
ws.send(JSON.stringify({
|
|
256
337
|
type: "status",
|
|
257
338
|
sessionId: data.sessionId,
|
|
258
|
-
status: { state: "connected" },
|
|
339
|
+
status: { state: "connected", model: `${provider}/${model}` },
|
|
259
340
|
} as OutboundMessage));
|
|
260
341
|
},
|
|
261
342
|
|
|
@@ -291,6 +372,16 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
291
372
|
if (msg.type === "message" && msg.content) {
|
|
292
373
|
log.debug(`Message received for session ${msg.sessionId}`, { content: msg.content.substring(0, 100) });
|
|
293
374
|
|
|
375
|
+
// Get or create session
|
|
376
|
+
let session = sessionStore.get(msg.sessionId);
|
|
377
|
+
if (!session) {
|
|
378
|
+
session = { messages: [] };
|
|
379
|
+
sessionStore.set(msg.sessionId, session);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Add user message
|
|
383
|
+
session.messages.push({ role: "user", content: msg.content });
|
|
384
|
+
|
|
294
385
|
laneQueue.enqueue(msg.sessionId, async (_task, signal) => {
|
|
295
386
|
if (signal.aborted) {
|
|
296
387
|
ws.send(JSON.stringify({
|
|
@@ -301,11 +392,30 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
301
392
|
return;
|
|
302
393
|
}
|
|
303
394
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
395
|
+
try {
|
|
396
|
+
const systemPrompt = agent.buildPrompt();
|
|
397
|
+
|
|
398
|
+
const response = await runner.generate({
|
|
399
|
+
provider: provider as any,
|
|
400
|
+
system: systemPrompt,
|
|
401
|
+
messages: session!.messages,
|
|
402
|
+
maxTokens: 4096,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
session!.messages.push({ role: "assistant", content: response.content });
|
|
406
|
+
|
|
407
|
+
ws.send(JSON.stringify({
|
|
408
|
+
type: "message",
|
|
409
|
+
sessionId: msg.sessionId,
|
|
410
|
+
content: response.content,
|
|
411
|
+
} as OutboundMessage));
|
|
412
|
+
} catch (error) {
|
|
413
|
+
ws.send(JSON.stringify({
|
|
414
|
+
type: "error",
|
|
415
|
+
sessionId: msg.sessionId,
|
|
416
|
+
error: (error as Error).message,
|
|
417
|
+
} as OutboundMessage));
|
|
418
|
+
}
|
|
309
419
|
});
|
|
310
420
|
|
|
311
421
|
return;
|
|
@@ -329,9 +439,11 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
329
439
|
log.info(`Gateway started successfully`);
|
|
330
440
|
log.info(`Control UI: http://${host}:${port}/ui`);
|
|
331
441
|
log.info(`WebSocket: ws://${host}:${port}/ws`);
|
|
442
|
+
log.info(`Channels: ${channelManager.listChannels().map((c) => c.name).join(", ") || "none"}`);
|
|
332
443
|
|
|
333
|
-
process.on("SIGTERM", () => {
|
|
444
|
+
process.on("SIGTERM", async () => {
|
|
334
445
|
log.info("Received SIGTERM, shutting down gracefully...");
|
|
446
|
+
await channelManager.stopAll();
|
|
335
447
|
server.stop();
|
|
336
448
|
try {
|
|
337
449
|
fs.unlinkSync(pidFile);
|