@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johpaz/hive-core",
3
- "version": "1.0.4",
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
+ }
@@ -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: "",
@@ -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>🤖 Hive</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
- ws.send(JSON.stringify({
305
- type: "message",
306
- sessionId: msg.sessionId,
307
- content: `Echo: ${msg.content}`,
308
- } as OutboundMessage));
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);