@johpaz/hive-sdk 0.0.12 → 0.0.15

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 (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,554 @@
1
+ import * as z from "zod";
2
+ import { mkdirSync, existsSync, readFileSync } from "node:fs";
3
+ import * as path from "node:path";
4
+
5
+ const LogLevelSchema = z.enum(["debug", "info", "warn", "error"]);
6
+ const DMPolicySchema = z.enum(["open", "pairing", "allowlist"]);
7
+ const TransportSchema = z.enum(["stdio", "sse", "websocket"]);
8
+
9
+ export function loadEnv(hiveDir: string): void {
10
+ const envPath = path.join(hiveDir, ".env");
11
+ if (existsSync(envPath)) {
12
+ try {
13
+ const text = readFileSync(envPath, "utf8");
14
+ const lines = text.split("\n");
15
+ for (const line of lines) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed || trimmed.startsWith("#")) continue;
18
+
19
+ const [key, ...valueParts] = trimmed.split("=");
20
+ if (key && valueParts.length > 0) {
21
+ const value = valueParts.join("=").trim().replace(/^['"]|['"]$/g, "");
22
+ process.env[key.trim()] = value;
23
+ }
24
+ }
25
+ } catch (e) {
26
+ // Ignore errors loading .env
27
+ }
28
+ }
29
+ }
30
+
31
+ export function getHiveDir(): string {
32
+ // Priority 1: HIVE_HOME explicitly set
33
+ if (process.env.HIVE_HOME) {
34
+ const hiveDir = process.env.HIVE_HOME.startsWith("~")
35
+ ? path.join(process.env.HOME || "", process.env.HIVE_HOME.slice(1))
36
+ : process.env.HIVE_HOME;
37
+ loadEnv(hiveDir);
38
+ return hiveDir;
39
+ }
40
+
41
+ // Priority 2: HIVE_DEV mode defaults (Local folder)
42
+ // Only check process.env.HIVE_DEV directly - don't load from .env files
43
+ // This ensures production mode is the default unless explicitly set
44
+ if (process.env.HIVE_DEV === "1" || process.env.HIVE_DEV === "true") {
45
+ const localDir = path.join(process.cwd(), ".hive-dev");
46
+ loadEnv(localDir);
47
+ return localDir;
48
+ }
49
+
50
+ // Priority 3: Default ~/.hive
51
+ const defaultDir = path.join(process.env.HOME || "", ".hive");
52
+ loadEnv(defaultDir);
53
+ return defaultDir;
54
+ }
55
+
56
+ const expandPath = (p: string): string => {
57
+ if (p.startsWith("~/.hive")) {
58
+ const hiveDir = getHiveDir();
59
+ return p.replace("~/.hive", hiveDir);
60
+ }
61
+ if (p.startsWith("~")) {
62
+ return path.join(process.env.HOME || "", p.slice(1));
63
+ }
64
+ return p;
65
+ };
66
+
67
+ const expandEnvVars = (value: string): string => {
68
+ return value.replace(/\$\{([^}]+)\}/g, (_, key) => {
69
+ return process.env[key] || "";
70
+ });
71
+ };
72
+
73
+ const expandEnvInObject = <T>(obj: T): T => {
74
+ if (typeof obj === "string") {
75
+ return expandEnvVars(obj) as T;
76
+ }
77
+ if (Array.isArray(obj)) {
78
+ return obj.map(expandEnvInObject) as T;
79
+ }
80
+ if (obj !== null && typeof obj === "object") {
81
+ const result: Record<string, unknown> = {};
82
+ for (const [key, value] of Object.entries(obj)) {
83
+ result[key] = expandEnvInObject(value);
84
+ }
85
+ return result as T;
86
+ }
87
+ return obj;
88
+ };
89
+
90
+ const ProviderConfigSchema = z.object({
91
+ apiKey: z.string().optional(),
92
+ baseUrl: z.string().optional(),
93
+ rateLimit: z.number().optional(),
94
+ retries: z.number().optional(),
95
+ retryDelayMs: z.number().optional(),
96
+ });
97
+
98
+ const ToolRestrictionsSchema = z.object({
99
+ allow: z.array(z.string()).optional(),
100
+ deny: z.array(z.string()).optional(),
101
+ });
102
+
103
+ const ExecConfigSchema = z.object({
104
+ enabled: z.boolean().optional(),
105
+ allowlist: z.array(z.string()).optional(),
106
+ denylist: z.array(z.string()).optional(),
107
+ timeoutSeconds: z.number().optional(),
108
+ workDir: z.string().optional(),
109
+ });
110
+
111
+ const WebConfigSchema = z.object({
112
+ allowlist: z.array(z.string()).optional(),
113
+ denylist: z.array(z.string()).optional(),
114
+ timeoutSeconds: z.number().optional(),
115
+ });
116
+
117
+ const BrowserConfigSchema = z.object({
118
+ enabled: z.boolean().optional(),
119
+ cdpUrl: z.string().optional(),
120
+ headless: z.boolean().optional(),
121
+ timeoutMs: z.number().optional(),
122
+ });
123
+
124
+ const CanvasConfigSchema = z.object({
125
+ enabled: z.boolean().optional(),
126
+ port: z.number().optional(),
127
+ });
128
+
129
+ const SandboxConfigSchema = z.object({
130
+ dm: ToolRestrictionsSchema.optional(),
131
+ group: ToolRestrictionsSchema.optional(),
132
+ });
133
+
134
+ const ToolsConfigSchema = z.object({
135
+ allow: z.array(z.string()).optional(),
136
+ deny: z.array(z.string()).optional(),
137
+ exec: ExecConfigSchema.optional(),
138
+ web: WebConfigSchema.optional(),
139
+ browser: BrowserConfigSchema.optional(),
140
+ canvas: CanvasConfigSchema.optional(),
141
+ sandbox: SandboxConfigSchema.optional(),
142
+ });
143
+
144
+ const ContextConfigSchema = z.object({
145
+ maxTokens: z.number().optional(),
146
+ compactionThreshold: z.number().optional(),
147
+ minMessagesAfterCompaction: z.number().optional(),
148
+ maxCompactionRetries: z.number().optional(),
149
+ });
150
+
151
+ const AgentEntrySchema = z.object({
152
+ id: z.string(),
153
+ default: z.boolean().optional(),
154
+ workspace: z.string(),
155
+ description: z.string().optional(),
156
+ });
157
+
158
+ const AccountConfigSchema = z.object({
159
+ botToken: z.string().optional(),
160
+ applicationId: z.string().optional(),
161
+ appToken: z.string().optional(),
162
+ signingSecret: z.string().optional(),
163
+ dmPolicy: DMPolicySchema.optional(),
164
+ allowFrom: z.array(z.string()).optional(),
165
+ });
166
+
167
+ const ChannelConfigSchema = z.object({
168
+ enabled: z.boolean().optional(),
169
+ accounts: z.record(z.string(), AccountConfigSchema).optional(),
170
+ dmPolicy: DMPolicySchema.optional(),
171
+ allowFrom: z.array(z.string()).optional(),
172
+ groups: z.boolean().optional(),
173
+ guilds: z.record(z.string(), z.unknown()).optional(),
174
+ experimental: z.boolean().optional(),
175
+ });
176
+
177
+ const PeerMatchSchema = z.object({
178
+ kind: z.enum(["direct", "group"]).optional(),
179
+ id: z.string().optional(),
180
+ });
181
+
182
+ const BindingMatchSchema = z.object({
183
+ channel: z.string().optional(),
184
+ accountId: z.string().optional(),
185
+ peer: PeerMatchSchema.optional(),
186
+ guildId: z.string().optional(),
187
+ teamId: z.string().optional(),
188
+ roles: z.array(z.string()).optional(),
189
+ });
190
+
191
+ const BindingSchema = z.object({
192
+ agentId: z.string(),
193
+ match: BindingMatchSchema,
194
+ });
195
+
196
+ const MCPServerConfigSchema = z.object({
197
+ enabled: z.boolean().optional(),
198
+ transport: TransportSchema,
199
+ command: z.string().optional(),
200
+ args: z.array(z.string()).optional(),
201
+ env: z.record(z.string(), z.string()).optional(),
202
+ url: z.string().optional(),
203
+ headers: z.record(z.string(), z.string()).optional(),
204
+ reconnect: z.object({
205
+ enabled: z.boolean().optional(),
206
+ maxRetries: z.number().optional(),
207
+ delayMs: z.number().optional(),
208
+ backoffMultiplier: z.number().optional(),
209
+ }).optional(),
210
+ });
211
+
212
+ const MCPConfigSchema = z.object({
213
+ enabled: z.boolean().optional(),
214
+ servers: z.record(z.string(), MCPServerConfigSchema).optional(),
215
+ healthCheck: z.object({
216
+ enabled: z.boolean().optional(),
217
+ intervalSeconds: z.number().optional(),
218
+ }).optional(),
219
+ });
220
+
221
+ const EpisodicMemoryConfigSchema = z.object({
222
+ enabled: z.boolean().optional(),
223
+ provider: z.enum(["openai", "local"]).optional(),
224
+ maxEpisodesPerSession: z.number().optional(),
225
+ });
226
+
227
+ const MemoryConfigSchema = z.object({
228
+ dbPath: z.string().optional(),
229
+ notesDir: z.string().optional(),
230
+ episodic: EpisodicMemoryConfigSchema.optional(),
231
+ });
232
+
233
+ const CronConfigSchema = z.object({
234
+ enabled: z.boolean().optional(),
235
+ dbPath: z.string().optional(),
236
+ maxConcurrentJobs: z.number().optional(),
237
+ timezone: z.string().optional(),
238
+ });
239
+
240
+ const RetryConfigSchema = z.object({
241
+ maxAttempts: z.number().optional(),
242
+ initialDelayMs: z.number().optional(),
243
+ backoffMultiplier: z.number().optional(),
244
+ maxDelayMs: z.number().optional(),
245
+ });
246
+
247
+ const HooksConfigSchema = z.object({
248
+ scripts: z.object({
249
+ before_model_resolve: z.string().optional(),
250
+ before_prompt_build: z.string().optional(),
251
+ before_tool_call: z.string().optional(),
252
+ after_tool_call: z.string().optional(),
253
+ tool_result_persist: z.string().optional(),
254
+ before_compaction: z.string().optional(),
255
+ after_compaction: z.string().optional(),
256
+ message_received: z.string().optional(),
257
+ message_sending: z.string().optional(),
258
+ message_sent: z.string().optional(),
259
+ session_start: z.string().optional(),
260
+ session_end: z.string().optional(),
261
+ gateway_start: z.string().optional(),
262
+ gateway_stop: z.string().optional(),
263
+ }).optional(),
264
+ });
265
+
266
+ const LoggingConfigSchema = z.object({
267
+ level: LogLevelSchema.optional(),
268
+ dir: z.string().optional(),
269
+ maxSizeMB: z.number().optional(),
270
+ maxFiles: z.number().optional(),
271
+ redactSensitive: z.boolean().optional(),
272
+ console: z.boolean().optional(),
273
+ });
274
+
275
+ const GatewayConfigSchema = z.object({
276
+ host: z.string().optional(),
277
+ port: z.number().optional(),
278
+ authToken: z.string().optional(),
279
+ pidFile: z.string().optional(),
280
+ tools: ToolRestrictionsSchema.optional(),
281
+ });
282
+
283
+ const ModelsConfigSchema = z.object({
284
+ defaultProvider: z.enum(["openai", "anthropic", "gemini", "mistral", "kimi", "ollama", "openrouter", "deepseek"]).optional(),
285
+ defaults: z.record(z.string(), z.string()).optional(),
286
+ providers: z.record(z.string(), ProviderConfigSchema).optional(),
287
+ });
288
+
289
+ const SessionsConfigSchema = z.object({
290
+ dir: z.string().optional(),
291
+ pruneAfterHours: z.number().optional(),
292
+ maxTranscriptSizeMB: z.number().optional(),
293
+ });
294
+
295
+ const SkillsConfigSchema = z.object({
296
+ allowBundled: z.array(z.string()).optional(),
297
+ managedDir: z.string().optional(),
298
+ extraDirs: z.array(z.string()).optional(),
299
+ hotReload: z.boolean().optional(),
300
+ maxSkillSizeKB: z.number().optional(),
301
+ });
302
+
303
+ const SecurityConfigSchema = z.object({
304
+ maxMessageLength: z.record(z.string(), z.number()).optional(),
305
+ skillScanning: z.boolean().optional(),
306
+ warnOnInsecureConfig: z.boolean().optional(),
307
+ allowedUsers: z.array(z.string()).optional(),
308
+ });
309
+
310
+ const CaptchaConfigSchema = z.object({
311
+ enabled: z.boolean().optional(),
312
+ autoSolve: z.boolean().optional(),
313
+ visionProvider: z.enum(["gemini", "openai", "anthropic"]).optional(),
314
+ visionModel: z.string().optional(),
315
+ maxAttempts: z.number().optional(),
316
+ maxRounds: z.number().optional(),
317
+ apiKey: z.string().optional(),
318
+ enabledSites: z.array(z.string()).optional(),
319
+ });
320
+
321
+ const UserConfigSchema = z.object({
322
+ id: z.string(),
323
+ name: z.string(),
324
+ channels: z.record(z.string(), z.string()).optional(),
325
+ });
326
+
327
+ const ConfigSchema = z.object({
328
+ gateway: GatewayConfigSchema.optional(),
329
+ logging: LoggingConfigSchema.optional(),
330
+ user: UserConfigSchema.optional(),
331
+ agent: z.object({
332
+ defaultAgentId: z.string().optional(),
333
+ baseDir: z.string().optional(),
334
+ context: ContextConfigSchema.optional(),
335
+ }).optional(),
336
+ models: ModelsConfigSchema.optional(),
337
+ sessions: SessionsConfigSchema.optional(),
338
+ agents: z.object({
339
+ list: z.array(AgentEntrySchema).optional(),
340
+ }).optional(),
341
+ bindings: z.array(BindingSchema).optional(),
342
+ channels: z.record(z.string(), ChannelConfigSchema).optional(),
343
+ tools: ToolsConfigSchema.optional(),
344
+ skills: SkillsConfigSchema.optional(),
345
+ mcp: MCPConfigSchema.optional(),
346
+ memory: MemoryConfigSchema.optional(),
347
+ cron: CronConfigSchema.optional(),
348
+ retry: RetryConfigSchema.optional(),
349
+ security: SecurityConfigSchema.optional(),
350
+ hooks: HooksConfigSchema.optional(),
351
+ captcha: CaptchaConfigSchema.optional(),
352
+ });
353
+
354
+ export type Config = z.infer<typeof ConfigSchema>;
355
+
356
+ export type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
357
+ export type MCPServerConfig = z.infer<typeof MCPServerConfigSchema>;
358
+ export type AgentEntry = z.infer<typeof AgentEntrySchema>;
359
+ export type Binding = z.infer<typeof BindingSchema>;
360
+ export type UserConfig = z.infer<typeof UserConfigSchema>;
361
+ export type CaptchaConfig = z.infer<typeof CaptchaConfigSchema>;
362
+
363
+ function buildDefaultConfig(): Config {
364
+ const hiveDir = getHiveDir();
365
+ return {
366
+ gateway: {
367
+ host: process.env.HIVE_HOST || "127.0.0.1",
368
+ port: parseInt(process.env.HIVE_PORT || "18790", 10),
369
+ pidFile: path.join(hiveDir, "gateway.pid"),
370
+ authToken: process.env.HIVE_AUTH_TOKEN || undefined,
371
+ tools: {
372
+ allow: ["*"],
373
+ deny: [],
374
+ },
375
+ },
376
+ logging: {
377
+ level: (process.env.HIVE_LOG_LEVEL as any) || "info",
378
+ dir: path.join(hiveDir, "logs"),
379
+ maxSizeMB: 10,
380
+ maxFiles: 5,
381
+ redactSensitive: true,
382
+ console: true,
383
+ },
384
+ agent: {
385
+ defaultAgentId: "main",
386
+ baseDir: path.join(hiveDir, "agents"),
387
+ context: {
388
+ maxTokens: 0,
389
+ compactionThreshold: 0.8,
390
+ minMessagesAfterCompaction: 4,
391
+ maxCompactionRetries: 3,
392
+ },
393
+ },
394
+ models: {
395
+ defaultProvider: "openai",
396
+ defaults: {
397
+ openai: "gpt-4o",
398
+ anthropic: "claude-sonnet-4-20250514",
399
+ ollama: "llama3.2",
400
+ openrouter: "anthropic/claude-sonnet-4",
401
+ },
402
+ providers: {},
403
+ },
404
+ sessions: {
405
+ dir: path.join(hiveDir, "sessions"),
406
+ pruneAfterHours: 24,
407
+ maxTranscriptSizeMB: 50,
408
+ },
409
+ agents: {
410
+ list: [
411
+ {
412
+ id: "main",
413
+ default: true,
414
+ workspace: path.join(hiveDir, "agents", "main", "workspace"),
415
+ description: "Default personal assistant",
416
+ },
417
+ ],
418
+ },
419
+ bindings: [],
420
+ channels: {
421
+ webchat: { enabled: true },
422
+ },
423
+ tools: {
424
+ allow: ["*"],
425
+ deny: [],
426
+ exec: {
427
+ enabled: true,
428
+ allowlist: [],
429
+ denylist: ["rm -rf /", "sudo", "chmod 777", "> /dev/", "mkfs"],
430
+ timeoutSeconds: 30,
431
+ workDir: path.join(process.env.HOME || "", "exec"), // Points to home for exec by default
432
+ },
433
+ web: {
434
+ allowlist: [],
435
+ denylist: ["file://", "ftp://"],
436
+ timeoutSeconds: 30,
437
+ },
438
+ browser: {
439
+ enabled: true,
440
+ cdpUrl: "ws://127.0.0.1:9222",
441
+ headless: true,
442
+ timeoutMs: 30000,
443
+ },
444
+ canvas: {
445
+ enabled: true,
446
+ port: 18793,
447
+ },
448
+ sandbox: {
449
+ dm: { allow: ["*"], deny: [] },
450
+ group: { allow: ["*"], deny: [] },
451
+ },
452
+ },
453
+ skills: {
454
+ allowBundled: [],
455
+ managedDir: path.join(hiveDir, "skills"),
456
+ extraDirs: [],
457
+ hotReload: true,
458
+ maxSkillSizeKB: 100,
459
+ },
460
+ mcp: {
461
+ enabled: true,
462
+ servers: {},
463
+ healthCheck: {
464
+ enabled: true,
465
+ intervalSeconds: 60,
466
+ },
467
+ },
468
+ memory: {
469
+ dbPath: path.join(hiveDir, "memory.db"),
470
+ notesDir: path.join(hiveDir, "agents", "main", "workspace", "memory"),
471
+ episodic: {
472
+ enabled: false,
473
+ provider: "openai",
474
+ maxEpisodesPerSession: 100,
475
+ },
476
+ },
477
+ cron: {
478
+ enabled: true,
479
+ dbPath: path.join(hiveDir, "cron.db"),
480
+ maxConcurrentJobs: 5,
481
+ timezone: "UTC",
482
+ },
483
+ retry: {
484
+ maxAttempts: 3,
485
+ initialDelayMs: 1000,
486
+ backoffMultiplier: 2,
487
+ maxDelayMs: 30000,
488
+ },
489
+ security: {
490
+ maxMessageLength: {
491
+ telegram: 4096,
492
+ discord: 2000,
493
+ slack: 40000,
494
+ webchat: 100000,
495
+ whatsapp: 65536,
496
+ },
497
+ skillScanning: true,
498
+ warnOnInsecureConfig: true,
499
+ },
500
+ hooks: {
501
+ scripts: {},
502
+ },
503
+ captcha: {
504
+ enabled: false,
505
+ autoSolve: true,
506
+ visionProvider: 'gemini',
507
+ visionModel: 'gemini-2.0-flash-exp',
508
+ maxAttempts: 3,
509
+ maxRounds: 5,
510
+ enabledSites: [],
511
+ },
512
+ };
513
+ }
514
+
515
+
516
+ // deepMerge kept for potential future use
517
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
518
+ const result = { ...target };
519
+
520
+ for (const key of Object.keys(source) as (keyof T)[]) {
521
+ const sourceValue = source[key];
522
+ const targetValue = result[key];
523
+
524
+ if (
525
+ sourceValue !== undefined &&
526
+ sourceValue !== null &&
527
+ typeof sourceValue === "object" &&
528
+ !Array.isArray(sourceValue) &&
529
+ targetValue !== undefined &&
530
+ targetValue !== null &&
531
+ typeof targetValue === "object" &&
532
+ !Array.isArray(targetValue)
533
+ ) {
534
+ result[key] = deepMerge(
535
+ targetValue as Record<string, unknown>,
536
+ sourceValue as Record<string, unknown>
537
+ ) as T[keyof T];
538
+ } else if (sourceValue !== undefined) {
539
+ result[key] = sourceValue as T[keyof T];
540
+ }
541
+ }
542
+
543
+ return result;
544
+ }
545
+ export function loadConfig(): Config {
546
+ return buildDefaultConfig();
547
+ }
548
+
549
+ export function expandConfigPath(p: string | undefined): string | undefined {
550
+ if (!p) return undefined;
551
+ return expandPath(p);
552
+ }
553
+
554
+ export { expandPath };
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "bun:test";
2
+ import { EthicsGuard } from "./EthicsGuard.ts";
3
+ import { getDb, initializeDatabase, dbService } from "../storage/SQLiteStorage.ts";
4
+
5
+ describe("EthicsGuard", () => {
6
+ let db: any;
7
+
8
+ beforeAll(async () => {
9
+ await initializeDatabase();
10
+ db = getDb();
11
+ db.run(`
12
+ INSERT OR IGNORE INTO playbook (id, rule, category, applicable_to, helpful_count, active)
13
+ VALUES (1, 'Siempre verificar fuentes antes de responder', 'response_quality', 'agent', 5, 1)
14
+ `);
15
+ });
16
+
17
+ afterAll(() => {
18
+ dbService.close();
19
+ });
20
+
21
+ it("loads rules from DB", () => {
22
+ const guard = new EthicsGuard(db);
23
+ const rules = guard.getRules();
24
+ expect(Array.isArray(rules)).toBe(true);
25
+ });
26
+
27
+ it("injectIntoPrompt appends rules to system prompt", () => {
28
+ const guard = new EthicsGuard(db);
29
+ const rules = guard.getRules();
30
+ const result = guard.injectIntoPrompt("Eres un asistente.", rules);
31
+ expect(result).toContain("Eres un asistente.");
32
+ if (rules.length > 0) {
33
+ expect(result).toContain("Calidad de Respuesta");
34
+ }
35
+ });
36
+
37
+ it("hasEthicsLayer detects response quality rules", () => {
38
+ const guard = new EthicsGuard(db);
39
+ const has = guard.hasEthicsLayer();
40
+ expect(typeof has).toBe("boolean");
41
+ });
42
+
43
+ it("getRules accepts optional agentRole for FTS5 search", () => {
44
+ const guard = new EthicsGuard(db);
45
+ const rules = guard.getRules("agent");
46
+ expect(Array.isArray(rules)).toBe(true);
47
+ });
48
+
49
+ it("getRules without agentRole returns all rules", () => {
50
+ const guard = new EthicsGuard(db);
51
+ const rules = guard.getRules();
52
+ expect(Array.isArray(rules)).toBe(true);
53
+ });
54
+ });
@@ -0,0 +1,66 @@
1
+ export interface EthicsRule {
2
+ id: number;
3
+ rule: string;
4
+ category: string;
5
+ applicable_to: string;
6
+ helpful_count: number;
7
+ active: number;
8
+ }
9
+
10
+ export class EthicsGuard {
11
+ private db: any;
12
+
13
+ constructor(db: any) {
14
+ this.db = db;
15
+ }
16
+
17
+ getRules(agentRole?: string): EthicsRule[] {
18
+ if (!agentRole) {
19
+ return this.db
20
+ .query(
21
+ `SELECT p.* FROM playbook p
22
+ WHERE p.category = 'response_quality' AND p.active = 1
23
+ ORDER BY p.helpful_count DESC`
24
+ )
25
+ .all() as EthicsRule[];
26
+ }
27
+
28
+ try {
29
+ const ftsRows = this.db
30
+ .query(
31
+ `SELECT p.* FROM playbook p
32
+ JOIN playbook_fts fts ON p.rowid = fts.rowid
33
+ WHERE fts.playbook_fts MATCH ?
34
+ AND p.category = 'response_quality' AND p.active = 1
35
+ ORDER BY p.helpful_count DESC`,
36
+ [agentRole]
37
+ )
38
+ .all() as EthicsRule[];
39
+
40
+ if (ftsRows.length > 0) return ftsRows;
41
+ } catch {}
42
+
43
+ return this.db
44
+ .query(
45
+ `SELECT * FROM playbook
46
+ WHERE category = 'response_quality' AND active = 1
47
+ ORDER BY helpful_count DESC`
48
+ )
49
+ .all() as EthicsRule[];
50
+ }
51
+
52
+ injectIntoPrompt(systemPrompt: string, rules: EthicsRule[]): string {
53
+ if (rules.length === 0) return systemPrompt;
54
+ const ethicsSection = rules
55
+ .map(r => `- ${r.rule}`)
56
+ .join("\n");
57
+ return `${systemPrompt}\n\n## Reglas de Calidad de Respuesta\n${ethicsSection}`;
58
+ }
59
+
60
+ hasEthicsLayer(): boolean {
61
+ const count = this.db
62
+ .query(`SELECT COUNT(*) as c FROM playbook WHERE category = 'response_quality' AND active = 1`)
63
+ .get() as any;
64
+ return (count?.c ?? 0) > 0;
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ export type { EthicsRule } from "./EthicsGuard.ts";
2
+ export { EthicsGuard } from "./EthicsGuard.ts";
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { sendToUserChannel } from "./channel-notify.ts";
3
+
4
+ describe("sendToUserChannel (stub)", () => {
5
+ it("returns ok without real gateway", async () => {
6
+ const result = await sendToUserChannel("cli:test", "user-1", "hello");
7
+ expect(result.ok).toBe(true);
8
+ });
9
+
10
+ it("handles empty message", async () => {
11
+ const result = await sendToUserChannel("cli:test", "user-1", "");
12
+ expect(result.ok).toBe(true);
13
+ });
14
+ });
@@ -0,0 +1,12 @@
1
+ import { logger } from "../utils/logger.ts";
2
+
3
+ const log = logger.child("channel-notify");
4
+
5
+ export async function sendToUserChannel(
6
+ channel: string,
7
+ userId: string,
8
+ message: string,
9
+ ): Promise<{ ok: boolean; error?: string }> {
10
+ log.info(`[stub] channel=${channel} userId=${userId} msg=${message.substring(0, 80)}`);
11
+ return { ok: true };
12
+ }
@@ -0,0 +1 @@
1
+ export { sendToUserChannel } from "./channel-notify.ts";