@johpaz/hive-sdk 0.0.15 → 0.0.16

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 (94) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +179 -57
  3. package/docs/API-AGENTS.md +9 -9
  4. package/docs/API-CONTEXT-COMPILER.md +13 -13
  5. package/docs/API-DAG-SCHEDULER.md +8 -8
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +150 -93
  7. package/docs/API-WORKERS-EVENTS.md +206 -59
  8. package/docs/INDEX.md +99 -50
  9. package/docs/README.md +117 -24
  10. package/docs/TEMPLATE-HIVE-APP.md +360 -0
  11. package/package.json +13 -6
  12. package/packages/cli/bin/hive +2 -0
  13. package/packages/cli/src/commands/add-skill.ts +42 -0
  14. package/packages/cli/src/commands/add-tool.ts +45 -0
  15. package/packages/cli/src/commands/add-worker.ts +49 -0
  16. package/packages/cli/src/commands/create-app-utils.ts +32 -0
  17. package/packages/cli/src/commands/create-app.test.ts +151 -0
  18. package/packages/cli/src/commands/create-app.ts +35 -0
  19. package/packages/cli/src/index.ts +21 -5
  20. package/packages/cli/templates/hive-app/.env.example +17 -0
  21. package/packages/cli/templates/hive-app/docker-compose.yml +20 -0
  22. package/packages/cli/templates/hive-app/hive.config.ts +19 -0
  23. package/packages/cli/templates/hive-app/package.json +16 -0
  24. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +9 -0
  25. package/packages/cli/templates/hive-app/src/main.ts +56 -0
  26. package/packages/core/src/auth/auth.ts +108 -0
  27. package/packages/core/src/auth/index.ts +1 -0
  28. package/packages/core/src/canvas/canvas.test.ts +32 -0
  29. package/packages/core/src/canvas/emitter.ts +1 -1
  30. package/packages/core/src/canvas/index.ts +3 -6
  31. package/packages/core/src/channels/base.ts +154 -0
  32. package/packages/core/src/channels/channels.test.ts +18 -0
  33. package/packages/core/src/channels/discord.ts +273 -0
  34. package/packages/core/src/channels/index.ts +7 -0
  35. package/packages/core/src/channels/manager.ts +450 -0
  36. package/packages/core/src/channels/slack.ts +323 -0
  37. package/packages/core/src/channels/telegram.ts +612 -0
  38. package/packages/core/src/channels/webchat.ts +139 -0
  39. package/packages/core/src/channels/whatsapp.ts +548 -0
  40. package/packages/core/src/events/agent-bus.ts +460 -0
  41. package/packages/core/src/events/event-bus.ts +169 -0
  42. package/packages/core/src/gateway/channel-notify.ts +32 -7
  43. package/packages/core/src/gateway/gateway.test.ts +38 -0
  44. package/packages/core/src/gateway/index.ts +2 -1
  45. package/packages/core/src/gateway/server.ts +139 -0
  46. package/packages/core/src/heartbeat/index.ts +157 -0
  47. package/packages/core/src/index.ts +44 -0
  48. package/packages/core/src/multimodal/index.ts +2 -2
  49. package/packages/core/src/multimodal/vision-service.ts +283 -0
  50. package/packages/core/src/plugins/api.ts +128 -0
  51. package/packages/core/src/plugins/index.ts +2 -0
  52. package/packages/core/src/plugins/loader.ts +365 -0
  53. package/packages/core/src/resilience/circuit-breaker.ts +225 -0
  54. package/packages/core/src/scheduler/CronScheduler.ts +699 -0
  55. package/packages/core/src/scheduler/dag/AgentExecutor.ts +53 -0
  56. package/packages/core/src/scheduler/dag/DAGScheduler.ts +250 -0
  57. package/packages/core/src/scheduler/dag/EventBridge.ts +122 -0
  58. package/packages/core/src/scheduler/dag/TaskGraph.ts +192 -0
  59. package/packages/core/src/scheduler/dag/TaskNode.ts +97 -0
  60. package/packages/core/src/scheduler/dag/TaskResult.ts +22 -0
  61. package/packages/core/src/scheduler/dag/errors.ts +37 -0
  62. package/packages/core/src/scheduler/dag/index.ts +26 -0
  63. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +97 -0
  64. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +21 -0
  65. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +46 -0
  66. package/packages/core/src/scheduler/index.ts +22 -0
  67. package/packages/core/src/scheduler/integration.ts +237 -0
  68. package/packages/core/src/scheduler/scheduler.test.ts +19 -0
  69. package/packages/core/src/scheduler/types.ts +164 -0
  70. package/packages/core/src/security/google-chat.ts +269 -0
  71. package/packages/core/src/security/index.ts +192 -4
  72. package/packages/core/src/security/rate-limit.ts +270 -0
  73. package/packages/core/src/security/signal.ts +321 -0
  74. package/packages/core/src/storage/crypto.ts +198 -66
  75. package/packages/core/src/storage/storage.test.ts +37 -0
  76. package/packages/core/src/swarm/swarm.test.ts +24 -0
  77. package/packages/core/src/tool-runtime/index.ts +522 -0
  78. package/packages/core/src/tool-runtime/tool-runtime.test.ts +91 -0
  79. package/packages/core/src/tool-runtime/tool-worker.ts +125 -0
  80. package/packages/core/src/voice/index.ts +5 -18
  81. package/packages/core/src/workers/WorkerPool.ts +167 -0
  82. package/packages/core/src/workers/agent.worker.ts +68 -0
  83. package/packages/core/src/workers/createWorker.ts +144 -0
  84. package/packages/core/src/workers/index.ts +5 -0
  85. package/packages/core/src/workers/workers.test.ts +48 -0
  86. package/test/setup-db.ts +2 -2
  87. package/tsconfig.json +2 -1
  88. package/.github/CODEOWNERS +0 -9
  89. package/.github/workflows/publish.yml +0 -89
  90. package/.github/workflows/version-bump.yml +0 -102
  91. package/bun.lock +0 -543
  92. package/bunfig.toml +0 -7
  93. package/packages/core/src/agent/providers.ts +0 -1
  94. package/packages/core/src/gateway/channel-notify.test.ts +0 -14
@@ -0,0 +1,125 @@
1
+ import { createAllTools } from "../tools/index.ts"
2
+ import type { Config } from "../config/loader.ts"
3
+
4
+ type WorkerRunMessage = {
5
+ type: "run"
6
+ jobId: string
7
+ toolName: string
8
+ args: unknown
9
+ toolConfig: Record<string, unknown>
10
+ hiveConfig: Config
11
+ mainThreadToolNames: string[]
12
+ }
13
+
14
+ type WorkerRpcResponse = {
15
+ type: "rpc_result"
16
+ rpcId: string
17
+ ok: boolean
18
+ result?: unknown
19
+ error?: SerializedError
20
+ }
21
+
22
+ type SerializedError = {
23
+ name: string
24
+ message: string
25
+ stack?: string
26
+ }
27
+
28
+ const pendingRpc = new Map<string, {
29
+ resolve: (value: unknown) => void
30
+ reject: (error: Error) => void
31
+ }>()
32
+
33
+ function serializeError(error: unknown): SerializedError {
34
+ const err = error instanceof Error ? error : new Error(String(error))
35
+ return {
36
+ name: err.name,
37
+ message: err.message,
38
+ stack: err.stack,
39
+ }
40
+ }
41
+
42
+ function parseArgs(args: unknown): Record<string, unknown> {
43
+ if (typeof args === "string") {
44
+ return JSON.parse(args) as Record<string, unknown>
45
+ }
46
+ if (args && typeof args === "object") {
47
+ return args as Record<string, unknown>
48
+ }
49
+ return {}
50
+ }
51
+
52
+ function requestMainThreadTool(
53
+ jobId: string,
54
+ toolName: string,
55
+ args: unknown,
56
+ toolConfig: Record<string, unknown>
57
+ ): Promise<unknown> {
58
+ const rpcId = `${jobId}:${crypto.randomUUID()}`
59
+ return new Promise((resolve, reject) => {
60
+ pendingRpc.set(rpcId, { resolve, reject })
61
+ postMessage({
62
+ type: "rpc_call",
63
+ rpcId,
64
+ jobId,
65
+ toolName,
66
+ args,
67
+ toolConfig,
68
+ })
69
+ })
70
+ }
71
+
72
+ async function runTool(message: WorkerRunMessage): Promise<void> {
73
+ const startedAt = performance.now()
74
+
75
+ try {
76
+ const parsedArgs = parseArgs(message.args)
77
+ const forceMainThread = message.mainThreadToolNames.includes(message.toolName)
78
+ const allTools = forceMainThread ? [] : createAllTools(message.hiveConfig)
79
+ const tool = allTools.find((candidate) => candidate.name === message.toolName)
80
+
81
+ const result = tool?.execute
82
+ ? await tool.execute(parsedArgs, { configurable: message.toolConfig })
83
+ : await requestMainThreadTool(message.jobId, message.toolName, parsedArgs, message.toolConfig)
84
+
85
+ postMessage({
86
+ type: "result",
87
+ jobId: message.jobId,
88
+ ok: true,
89
+ result,
90
+ durationMs: Math.round(performance.now() - startedAt),
91
+ })
92
+ } catch (error) {
93
+ postMessage({
94
+ type: "result",
95
+ jobId: message.jobId,
96
+ ok: false,
97
+ error: serializeError(error),
98
+ durationMs: Math.round(performance.now() - startedAt),
99
+ })
100
+ }
101
+ }
102
+
103
+ onmessage = (event: MessageEvent<WorkerRunMessage | WorkerRpcResponse>) => {
104
+ const message = event.data
105
+
106
+ if (message.type === "rpc_result") {
107
+ const pending = pendingRpc.get(message.rpcId)
108
+ if (!pending) return
109
+
110
+ pendingRpc.delete(message.rpcId)
111
+ if (message.ok) {
112
+ pending.resolve(message.result)
113
+ } else {
114
+ const error = new Error(message.error?.message || "Tool RPC failed")
115
+ error.name = message.error?.name || "ToolRpcError"
116
+ error.stack = message.error?.stack
117
+ pending.reject(error)
118
+ }
119
+ return
120
+ }
121
+
122
+ if (message.type === "run") {
123
+ void runTool(message)
124
+ }
125
+ }
@@ -1,6 +1,6 @@
1
- import { getDb } from "../storage/SQLiteStorage.ts";
2
- import { decryptApiKey } from "../storage/crypto.ts";
3
- import { logger } from "../utils/logger.ts";
1
+ import { getDb } from "../storage/SQLiteStorage";
2
+ import { loadProviderApiKey } from "../storage/crypto";
3
+ import { logger } from "../utils/logger";
4
4
 
5
5
  export interface VoiceConfig {
6
6
  voiceEnabled: boolean;
@@ -126,21 +126,8 @@ class VoiceService {
126
126
  }
127
127
 
128
128
  private async getProviderApiKey(providerId: string): Promise<string | null> {
129
- const db = getDb();
130
- const provider = db.query(`
131
- SELECT api_key_encrypted, api_key_iv FROM providers WHERE id = ?
132
- `).get(providerId) as { api_key_encrypted: string; api_key_iv: string } | undefined;
133
-
134
- if (!provider?.api_key_encrypted) {
135
- return null;
136
- }
137
-
138
- try {
139
- return await decryptApiKey(provider.api_key_encrypted, provider.api_key_iv);
140
- } catch (error) {
141
- log.error(`Failed to decrypt API key for provider ${providerId}: ${(error as Error).message}`);
142
- return null;
143
- }
129
+ const apiKey = await loadProviderApiKey(providerId);
130
+ return apiKey || null;
144
131
  }
145
132
 
146
133
  private async transcribeWithGroq(audio: AudioInput, modelId: string): Promise<string> {
@@ -0,0 +1,167 @@
1
+ /**
2
+ * WorkerPool — Manages a pool of Bun Workers for parallel task execution.
3
+ *
4
+ * Creates workers on demand, reuses idle workers, and handles task queuing.
5
+ */
6
+
7
+ import { logger } from "../utils/logger.ts";
8
+ import { createWorker, type WorkerConfig, type WorkerInstance } from "./createWorker.ts";
9
+
10
+ const log = logger.child("worker-pool");
11
+
12
+ export interface WorkerPoolConfig {
13
+ maxWorkers?: number;
14
+ taskTimeoutMs?: number;
15
+ workerConfig?: WorkerConfig;
16
+ }
17
+
18
+ export interface PoolTask {
19
+ id: string;
20
+ message: string;
21
+ agentId?: string;
22
+ threadId?: string;
23
+ channel?: string;
24
+ systemPrompt?: string;
25
+ }
26
+
27
+ export interface PoolTaskResult {
28
+ taskId: string;
29
+ result: string;
30
+ error?: string;
31
+ durationMs: number;
32
+ }
33
+
34
+ export class WorkerPool {
35
+ private maxWorkers: number;
36
+ private taskTimeoutMs: number;
37
+ private workerConfig: WorkerConfig;
38
+ private workers: Map<string, WorkerInstance> = new Map();
39
+ private idleWorkers: string[] = [];
40
+ private taskQueue: Array<{ task: PoolTask; resolve: (result: PoolTaskResult) => void }> = [];
41
+ private busyWorkers: Set<string> = new Set();
42
+
43
+ constructor(config: WorkerPoolConfig = {}) {
44
+ this.maxWorkers = config.maxWorkers ?? 4;
45
+ this.taskTimeoutMs = config.taskTimeoutMs ?? 120000;
46
+ this.workerConfig = config.workerConfig ?? { name: "pool-worker" };
47
+ }
48
+
49
+ /**
50
+ * Execute a single task in a worker.
51
+ */
52
+ async execute(task: PoolTask): Promise<PoolTaskResult> {
53
+ const worker = await this.acquireWorker();
54
+ const startedAt = performance.now();
55
+
56
+ try {
57
+ const result = await worker.run(task.message, {
58
+ threadId: task.threadId,
59
+ channel: task.channel,
60
+ });
61
+ return {
62
+ taskId: task.id,
63
+ result,
64
+ durationMs: Math.round(performance.now() - startedAt),
65
+ };
66
+ } catch (err) {
67
+ return {
68
+ taskId: task.id,
69
+ result: "",
70
+ error: (err as Error).message,
71
+ durationMs: Math.round(performance.now() - startedAt),
72
+ };
73
+ } finally {
74
+ this.releaseWorker(worker.id);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Execute multiple tasks in parallel, up to maxWorkers at a time.
80
+ */
81
+ async executeBatch(tasks: PoolTask[]): Promise<PoolTaskResult[]> {
82
+ return Promise.all(tasks.map((task) => this.execute(task)));
83
+ }
84
+
85
+ /**
86
+ * Execute tasks with a limit on concurrency.
87
+ */
88
+ async executeWithConcurrency(tasks: PoolTask[], concurrency: number): Promise<PoolTaskResult[]> {
89
+ const results: PoolTaskResult[] = [];
90
+ const executing = new Set<Promise<void>>();
91
+
92
+ for (const task of tasks) {
93
+ const promise = this.execute(task).then((result) => {
94
+ results.push(result);
95
+ });
96
+ executing.add(promise);
97
+
98
+ if (executing.size >= concurrency) {
99
+ await Promise.race(executing);
100
+ executing.delete(promise);
101
+ }
102
+ }
103
+
104
+ await Promise.all(executing);
105
+ return results;
106
+ }
107
+
108
+ /**
109
+ * Get or create a worker.
110
+ */
111
+ private acquireWorker(): Promise<WorkerInstance> {
112
+ return new Promise((resolve) => {
113
+ // Try to reuse an idle worker
114
+ if (this.idleWorkers.length > 0) {
115
+ const workerId = this.idleWorkers.pop()!;
116
+ this.busyWorkers.add(workerId);
117
+ resolve(this.workers.get(workerId)!);
118
+ return;
119
+ }
120
+
121
+ // Create a new worker if under limit
122
+ if (this.workers.size < this.maxWorkers) {
123
+ const worker = createWorker(this.workerConfig);
124
+ this.workers.set(worker.id, worker);
125
+ this.busyWorkers.add(worker.id);
126
+ resolve(worker);
127
+ return;
128
+ }
129
+
130
+ // Queue the request
131
+ const checkInterval = setInterval(() => {
132
+ if (this.idleWorkers.length > 0) {
133
+ clearInterval(checkInterval);
134
+ const workerId = this.idleWorkers.pop()!;
135
+ this.busyWorkers.add(workerId);
136
+ resolve(this.workers.get(workerId)!);
137
+ }
138
+ }, 50);
139
+ });
140
+ }
141
+
142
+ private releaseWorker(workerId: string): void {
143
+ this.busyWorkers.delete(workerId);
144
+ this.idleWorkers.push(workerId);
145
+ }
146
+
147
+ /**
148
+ * Terminate all workers in the pool.
149
+ */
150
+ shutdown(): void {
151
+ log.info(`[WorkerPool] Shutting down ${this.workers.size} workers`);
152
+ for (const worker of this.workers.values()) {
153
+ worker.terminate();
154
+ }
155
+ this.workers.clear();
156
+ this.idleWorkers = [];
157
+ this.busyWorkers.clear();
158
+ }
159
+
160
+ get stats(): { total: number; busy: number; idle: number } {
161
+ return {
162
+ total: this.workers.size,
163
+ busy: this.busyWorkers.size,
164
+ idle: this.idleWorkers.length,
165
+ };
166
+ }
167
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Agent Worker — Bun Worker that runs an agent task in isolation.
3
+ *
4
+ * Receives: { type: "AGENT_TASK", taskId, message, agentId, threadId, channel, systemPrompt }
5
+ * Sends: { type: "AGENT_RESULT", taskId, result } | { type: "AGENT_CHUNK", taskId, chunk }
6
+ */
7
+
8
+ import { runAgent } from "../agent/AgentRunner.ts";
9
+ import type { StreamChunk } from "../agent/AgentRunner.ts";
10
+
11
+ declare var self: {
12
+ onmessage: ((event: { data: WorkerMessage }) => void) | null;
13
+ postMessage(message: WorkerResponse): void;
14
+ };
15
+
16
+ type WorkerMessage = {
17
+ type: "AGENT_TASK";
18
+ taskId: string;
19
+ message: string;
20
+ agentId: string;
21
+ threadId?: string;
22
+ channel?: string;
23
+ systemPrompt?: string;
24
+ };
25
+
26
+ type WorkerResponse =
27
+ | { type: "AGENT_CHUNK"; taskId: string; chunk: StreamChunk }
28
+ | { type: "AGENT_RESULT"; taskId: string; result: string; error?: string };
29
+
30
+ self.onmessage = async (event) => {
31
+ const { type, taskId, message, agentId, threadId, channel, systemPrompt } = event.data;
32
+
33
+ if (type !== "AGENT_TASK") return;
34
+
35
+ try {
36
+ const stream = runAgent({
37
+ agentId,
38
+ userMessage: message,
39
+ threadId: threadId ?? `worker-${taskId}`,
40
+ channel: channel ?? "worker",
41
+ isolated: true,
42
+ systemPromptOverride: systemPrompt,
43
+ });
44
+
45
+ let fullResponse = "";
46
+
47
+ for await (const chunk of stream) {
48
+ self.postMessage({ type: "AGENT_CHUNK", taskId, chunk });
49
+
50
+ if (chunk.agent?.messages) {
51
+ for (const msg of chunk.agent.messages) {
52
+ if (msg.content && typeof msg.content === "string") {
53
+ fullResponse = msg.content;
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ self.postMessage({ type: "AGENT_RESULT", taskId, result: fullResponse });
60
+ } catch (err) {
61
+ self.postMessage({
62
+ type: "AGENT_RESULT",
63
+ taskId,
64
+ result: "",
65
+ error: (err as Error).message,
66
+ });
67
+ }
68
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * createWorker — Factory for creating specialized Bun Workers.
3
+ *
4
+ * Creates a dedicated worker thread that runs an agent with a custom system prompt.
5
+ * Useful for parallel task execution and specialized agent roles.
6
+ */
7
+
8
+ import { logger } from "../utils/logger.ts";
9
+
10
+ const log = logger.child("createWorker");
11
+
12
+ export interface WorkerConfig {
13
+ name: string;
14
+ agentId?: string;
15
+ systemPrompt?: string;
16
+ model?: string;
17
+ provider?: string;
18
+ }
19
+
20
+ export interface WorkerInstance {
21
+ readonly name: string;
22
+ readonly id: string;
23
+ run(message: string, opts?: { threadId?: string; channel?: string }): Promise<string>;
24
+ runStream(message: string, opts?: { threadId?: string; channel?: string }): AsyncGenerator<WorkerChunk>;
25
+ terminate(): void;
26
+ }
27
+
28
+ export interface WorkerChunk {
29
+ type: "chunk" | "result" | "error";
30
+ content?: string;
31
+ chunk?: any;
32
+ error?: string;
33
+ }
34
+
35
+ const WORKER_EXT = import.meta.url.endsWith(".ts") ? ".worker.ts" : ".worker.js";
36
+
37
+ function resolveWorkerPath(): string {
38
+ return new URL(`./agent${WORKER_EXT}`, import.meta.url).pathname;
39
+ }
40
+
41
+ export function createWorker(config: WorkerConfig): WorkerInstance {
42
+ const workerId = `${config.name}-${crypto.randomUUID().slice(0, 8)}`;
43
+ const workerPath = resolveWorkerPath();
44
+
45
+ log.info(`[createWorker] Spawning worker ${workerId} from ${workerPath}`);
46
+
47
+ const worker = new Worker(workerPath, { smol: true });
48
+
49
+ return {
50
+ name: config.name,
51
+ id: workerId,
52
+
53
+ async run(message, opts) {
54
+ return new Promise((resolve, reject) => {
55
+ const taskId = crypto.randomUUID();
56
+ const timeout = setTimeout(() => {
57
+ reject(new Error(`Worker ${workerId} timed out`));
58
+ }, 120000);
59
+
60
+ const handler = (event: MessageEvent) => {
61
+ const data = event.data;
62
+ if (data.taskId !== taskId) return;
63
+
64
+ if (data.type === "AGENT_RESULT") {
65
+ clearTimeout(timeout);
66
+ worker.removeEventListener("message", handler);
67
+ if (data.error) {
68
+ reject(new Error(data.error));
69
+ } else {
70
+ resolve(data.result);
71
+ }
72
+ }
73
+ };
74
+
75
+ worker.addEventListener("message", handler);
76
+ worker.postMessage({
77
+ type: "AGENT_TASK",
78
+ taskId,
79
+ message,
80
+ agentId: config.agentId ?? config.name,
81
+ threadId: opts?.threadId,
82
+ channel: opts?.channel ?? config.name,
83
+ systemPrompt: config.systemPrompt,
84
+ });
85
+ });
86
+ },
87
+
88
+ async *runStream(message, opts) {
89
+ const taskId = crypto.randomUUID();
90
+
91
+ worker.postMessage({
92
+ type: "AGENT_TASK",
93
+ taskId,
94
+ message,
95
+ agentId: config.agentId ?? config.name,
96
+ threadId: opts?.threadId,
97
+ channel: opts?.channel ?? config.name,
98
+ systemPrompt: config.systemPrompt,
99
+ });
100
+
101
+ let resolved = false;
102
+ const pending: WorkerResponse[] = [];
103
+
104
+ const handler = (event: MessageEvent) => {
105
+ const data = event.data as WorkerResponse;
106
+ if (data.taskId !== taskId) return;
107
+ pending.push(data);
108
+ };
109
+
110
+ worker.addEventListener("message", handler);
111
+
112
+ try {
113
+ while (!resolved) {
114
+ while (pending.length === 0) {
115
+ await new Promise((r) => setTimeout(r, 10));
116
+ }
117
+ const data = pending.shift()!;
118
+
119
+ if (data.type === "AGENT_CHUNK") {
120
+ yield { type: "chunk" as const, chunk: data.chunk };
121
+ } else if (data.type === "AGENT_RESULT") {
122
+ resolved = true;
123
+ if (data.error) {
124
+ yield { type: "error" as const, error: data.error };
125
+ } else {
126
+ yield { type: "result" as const, content: data.result };
127
+ }
128
+ }
129
+ }
130
+ } finally {
131
+ worker.removeEventListener("message", handler);
132
+ }
133
+ },
134
+
135
+ terminate() {
136
+ log.info(`[createWorker] Terminating worker ${workerId}`);
137
+ worker.terminate();
138
+ },
139
+ };
140
+ }
141
+
142
+ type WorkerResponse =
143
+ | { type: "AGENT_CHUNK"; taskId: string; chunk: any }
144
+ | { type: "AGENT_RESULT"; taskId: string; result: string; error?: string };
@@ -0,0 +1,5 @@
1
+ export { createWorker } from "./createWorker.ts";
2
+ export type { WorkerConfig, WorkerInstance, WorkerChunk } from "./createWorker.ts";
3
+
4
+ export { WorkerPool } from "./WorkerPool.ts";
5
+ export type { WorkerPoolConfig, PoolTask, PoolTaskResult } from "./WorkerPool.ts";
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it, beforeAll, afterAll } from "bun:test";
2
+ import { createWorker, WorkerPool } from "./index.ts";
3
+ import { setupTestDb, teardownTestDb, insertTestAgent, insertTestProvider } from "../../../../test/setup-db.ts";
4
+
5
+ describe("createWorker", () => {
6
+ beforeAll(() => {
7
+ setupTestDb();
8
+ });
9
+
10
+ afterAll(() => {
11
+ teardownTestDb();
12
+ });
13
+
14
+ it("creates a worker instance with config", () => {
15
+ const worker = createWorker({
16
+ name: "test-worker",
17
+ systemPrompt: "You are a test worker.",
18
+ });
19
+
20
+ expect(worker.name).toBe("test-worker");
21
+ expect(worker.id).toContain("test-worker-");
22
+ worker.terminate();
23
+ });
24
+
25
+ it("WorkerPool creates and manages workers", () => {
26
+ const pool = new WorkerPool({ maxWorkers: 2 });
27
+ expect(pool.stats.total).toBe(0);
28
+ expect(pool.stats.idle).toBe(0);
29
+ expect(pool.stats.busy).toBe(0);
30
+ pool.shutdown();
31
+ });
32
+
33
+ it("WorkerPool executes tasks with limited concurrency", async () => {
34
+ const pool = new WorkerPool({
35
+ maxWorkers: 2,
36
+ workerConfig: { name: "pool-test" },
37
+ });
38
+
39
+ const tasks = Array.from({ length: 4 }, (_, i) => ({
40
+ id: `task-${i}`,
41
+ message: `Test message ${i}`,
42
+ }));
43
+
44
+ // Since we don't have a real LLM in tests, we just verify the pool structure
45
+ expect(tasks).toHaveLength(4);
46
+ pool.shutdown();
47
+ });
48
+ });
package/test/setup-db.ts CHANGED
@@ -195,7 +195,7 @@ export function insertTestPlaybookRule(overrides: Record<string, any> = {}): num
195
195
  overrides.active ?? 1
196
196
  );
197
197
 
198
- return result.lastInsertRowId as number;
198
+ return result.lastInsertRowid as number;
199
199
  }
200
200
 
201
201
  export function insertTestConversation(threadId: string, role: string, content: string): number {
@@ -212,5 +212,5 @@ export function insertTestConversation(threadId: string, role: string, content:
212
212
  Math.floor(content.length / 4)
213
213
  );
214
214
 
215
- return result.lastInsertRowId as number;
215
+ return result.lastInsertRowid as number;
216
216
  }
package/tsconfig.json CHANGED
@@ -34,6 +34,7 @@
34
34
  ],
35
35
  "exclude": [
36
36
  "node_modules",
37
- "dist"
37
+ "dist",
38
+ "packages/cli/templates"
38
39
  ]
39
40
  }
@@ -1,9 +0,0 @@
1
- # Code Owners
2
- * @anomalyco @johpaz
3
-
4
- # Reviewers
5
- /.github/ @anomalyco
6
- /docs/ @johpaz
7
- /packages/agent/ @johpaz
8
- /packages/scheduler/ @johpaz
9
- /packages/events/ @johpaz