@johpaz/hive-sdk 0.0.14 → 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,251 @@
1
+ /**
2
+ * DAGScheduler — Main orchestrator
3
+ *
4
+ * Executes a TaskGraph by:
5
+ * 1. Identifying all nodes with no dependencies → mark READY
6
+ * 2. Launching them concurrently via AgentExecutor (respecting maxConcurrentWorkers)
7
+ * 3. When a node completes, finding newly unblocked nodes and launching them
8
+ * 4. Propagating failures to dependent nodes
9
+ * 5. Emitting progress via EventBridge → agentBus + canvas
10
+ *
11
+ * Parallelism model: Promise.race() over a Set of active promises + a FIFO/priority
12
+ * queue of READY nodes waiting for a slot. No Bun Worker threads — workers are async
13
+ * agent calls (runAgentIsolated) running concurrently in the same process.
14
+ */
15
+
16
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs"
17
+ import * as path from "node:path"
18
+ import { logger } from "../utils/logger.ts"
19
+ import { TaskGraph } from "./TaskGraph"
20
+ import { TaskNode } from "./TaskNode"
21
+ import { AgentExecutor } from "./AgentExecutor"
22
+ import { EventBridge } from "./EventBridge"
23
+ import { TaskFailureError } from "./errors"
24
+ import type { DAGResult, NodeSummary } from "./TaskResult"
25
+ import type { ExecutionStrategy } from "./strategies/ParallelStrategy"
26
+ import { ParallelStrategy } from "./strategies/ParallelStrategy"
27
+
28
+ const log = logger.child("dag-scheduler")
29
+
30
+ export interface IAgentExecutor {
31
+ execute(node: TaskNode, depResults: Record<string, string>, threadId: string): Promise<string>
32
+ }
33
+
34
+ export interface DAGSchedulerOptions {
35
+ strategy?: ExecutionStrategy
36
+ maxConcurrentWorkers?: number
37
+ /** Project ID for agentBus/canvas events */
38
+ projectId?: string
39
+ /** Coordinator agent ID for agentBus events */
40
+ coordinatorId?: string
41
+ /** Disables ASCII log and file logging. Default: false in development */
42
+ silent?: boolean
43
+ /** Custom executor — defaults to AgentExecutor (runAgentIsolated). Override to bypass context-compiler. */
44
+ executor?: IAgentExecutor
45
+ }
46
+
47
+ export class DAGScheduler {
48
+ private strategy: ExecutionStrategy
49
+ private maxConcurrentWorkers: number
50
+ private executor: IAgentExecutor
51
+ private aborted = false
52
+
53
+ constructor(options: DAGSchedulerOptions = {}) {
54
+ this.strategy = options.strategy ?? new ParallelStrategy()
55
+ this.maxConcurrentWorkers = options.maxConcurrentWorkers ?? 2
56
+ this.executor = options.executor ?? new AgentExecutor()
57
+ }
58
+
59
+ abort(): void {
60
+ this.aborted = true
61
+ }
62
+
63
+ async execute(graph: TaskGraph, options: DAGSchedulerOptions = {}): Promise<DAGResult> {
64
+ this.aborted = false
65
+ const swarmId = crypto.randomUUID()
66
+ const startedAt = Date.now()
67
+
68
+ const projectId = options.projectId ?? `swarm:${swarmId}`
69
+ const coordinatorId = options.coordinatorId ?? "dag-scheduler"
70
+ const silent = options.silent ?? (process.env.NODE_ENV === "production")
71
+ const executor = options.executor ?? this.executor
72
+
73
+ const bridge = new EventBridge(swarmId, projectId, coordinatorId)
74
+
75
+ // Allow strategy to precompute (e.g. critical path)
76
+ if (this.strategy.initialize) {
77
+ this.strategy.initialize(graph.nodes)
78
+ }
79
+
80
+ bridge.onSwarmStarted(graph.nodes.size)
81
+ this.logState(swarmId, graph, startedAt, silent, swarmId)
82
+
83
+ // Seed the READY queue with nodes that have no dependencies
84
+ const readyQueue: TaskNode[] = []
85
+ const completedIds = graph.getCompletedIds()
86
+
87
+ for (const node of graph.nodes.values()) {
88
+ if (node.deps.length === 0) {
89
+ node.markReady()
90
+ readyQueue.push(node)
91
+ }
92
+ }
93
+
94
+ // Active promise set — we track them with a wrapper so we can drain
95
+ const running = new Set<Promise<void>>()
96
+
97
+ const launchNode = (node: TaskNode): void => {
98
+ if (this.aborted) return
99
+
100
+ node.markRunning()
101
+ bridge.onTaskStarted(node)
102
+ this.logState(swarmId, graph, startedAt, silent, swarmId)
103
+
104
+ const depResults = graph.getDepResults(node.id)
105
+ const threadId = `dag-${swarmId}-${node.id}`
106
+
107
+ const p: Promise<void> = executor
108
+ .execute(node, depResults, threadId)
109
+ .then(result => {
110
+ node.markCompleted(result)
111
+ log.info(`[DAG] ${node.name} COMPLETED in ${node.elapsedSeconds()}s`)
112
+ bridge.onTaskCompleted(node, graph.getProgress())
113
+ this.logState(swarmId, graph, startedAt, silent, swarmId)
114
+
115
+ // Unlock dependent nodes
116
+ const newlyReady = graph.getNewlyReadyNodes(graph.getCompletedIds())
117
+ for (const n of newlyReady) {
118
+ n.markReady()
119
+ readyQueue.push(n)
120
+ }
121
+ })
122
+ .catch(err => {
123
+ const error = err instanceof Error ? err.message : String(err)
124
+
125
+ if (node.canRetry()) {
126
+ node.retryCount++
127
+ log.warn(`[DAG] ${node.name} failed (retry ${node.retryCount}/${node.maxRetries}): ${error}`)
128
+ node.status = "PENDING"
129
+ node.markReady()
130
+ readyQueue.push(node)
131
+ } else {
132
+ node.markFailed(error)
133
+ log.error(`[DAG] ${node.name} FAILED permanently: ${error}`)
134
+ bridge.onTaskFailed(node, graph.getProgress())
135
+ graph.propagateFailure(node.id, error)
136
+ this.logState(swarmId, graph, startedAt, silent, swarmId)
137
+ }
138
+ })
139
+ .finally(() => {
140
+ running.delete(p)
141
+ drain()
142
+ })
143
+
144
+ running.add(p)
145
+ }
146
+
147
+ // Drain the ready queue into available worker slots
148
+ const drain = (): void => {
149
+ while (readyQueue.length > 0 && running.size < this.maxConcurrentWorkers && !this.aborted) {
150
+ const node = this.strategy.pick(readyQueue)
151
+ if (!node) break
152
+ launchNode(node)
153
+ }
154
+ }
155
+
156
+ // Start initial drain
157
+ drain()
158
+
159
+ // Wait until the graph is complete
160
+ while (!graph.isComplete() && !this.aborted) {
161
+ if (running.size === 0 && readyQueue.length === 0) {
162
+ // Deadlock guard: no running, nothing ready, but graph not done
163
+ // This can happen if all remaining nodes are FAILED
164
+ break
165
+ }
166
+ // Wait for any active promise to settle
167
+ if (running.size > 0) {
168
+ await Promise.race([...running])
169
+ drain()
170
+ } else {
171
+ // Brief yield to let microtasks settle
172
+ await new Promise(resolve => setTimeout(resolve, 10))
173
+ }
174
+ }
175
+
176
+ // Collect results
177
+ const completed: NodeSummary[] = []
178
+ const failed: NodeSummary[] = []
179
+
180
+ for (const node of graph.nodes.values()) {
181
+ const summary: NodeSummary = {
182
+ id: node.id,
183
+ name: node.name,
184
+ status: node.status === "COMPLETED" ? "COMPLETED" : "FAILED",
185
+ durationMs: node.startedAt ? (node.completedAt ?? Date.now()) - node.startedAt : 0,
186
+ result: node.result,
187
+ error: node.error,
188
+ retries: node.retryCount,
189
+ }
190
+ if (node.status === "COMPLETED") completed.push(summary)
191
+ else failed.push(summary)
192
+ }
193
+
194
+ const result: DAGResult = {
195
+ swarmId,
196
+ totalDurationMs: Date.now() - startedAt,
197
+ completed,
198
+ failed,
199
+ success: failed.length === 0,
200
+ }
201
+
202
+ bridge.onSwarmCompleted(result)
203
+ this.logState(swarmId, graph, startedAt, silent, swarmId)
204
+
205
+ log.info(`[DAG] swarm ${swarmId} finished. ${completed.length} completed, ${failed.length} failed. Total: ${Math.round(result.totalDurationMs / 1000)}s`)
206
+
207
+ return result
208
+ }
209
+
210
+ // ─── ASCII log ───────────────────────────────────────────────────────────────
211
+
212
+ private logState(
213
+ swarmId: string,
214
+ graph: TaskGraph,
215
+ startedAt: number,
216
+ silent: boolean,
217
+ sessionId: string
218
+ ): void {
219
+ const elapsed = Math.round((Date.now() - startedAt) / 1000)
220
+ const lines: string[] = [`[DAG] swarm:${swarmId.slice(0, 8)} T+${elapsed}s`]
221
+
222
+ for (const node of graph.nodes.values()) {
223
+ const icon =
224
+ node.status === "COMPLETED" ? "✓" :
225
+ node.status === "FAILED" ? "✗" :
226
+ node.status === "RUNNING" ? "●" : "○"
227
+
228
+ const depStr = node.deps.length > 0 ? ` (deps: ${node.deps.join(", ")})` : ""
229
+ const timeStr = node.startedAt ? ` (${node.elapsedSeconds()}s)` : ""
230
+ const statusLabel = node.status.padEnd(10)
231
+
232
+ lines.push(` ${icon} ${node.name.padEnd(24)} ${statusLabel}${timeStr}${depStr}`)
233
+ }
234
+
235
+ const output = lines.join("\n")
236
+
237
+ if (!silent) {
238
+ // Write to log file (never committed — in .gitignore)
239
+ try {
240
+ const logDir = path.join(process.cwd(), "packages", "core", "logs")
241
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true })
242
+ const logFile = path.join(logDir, `dag-${sessionId.slice(0, 8)}.log`)
243
+ writeFileSync(logFile, output + "\n\n", { flag: "a" })
244
+ } catch {
245
+ // Non-critical — never throw for logging
246
+ }
247
+ }
248
+
249
+ log.debug(output)
250
+ }
251
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * DAGScheduler — EventBridge
3
+ *
4
+ * Maps DAG lifecycle events to the existing agentBus so that the rest of
5
+ * Hive OSS can observe swarm progress without coupling to DAGScheduler directly.
6
+ *
7
+ * Also emits canvas:node_update events so the UI reflects task state in real time.
8
+ */
9
+
10
+ import { agentBus } from "../swarm/AgentBus.ts"
11
+ import { emitCanvas } from "../canvas/emitter.ts"
12
+ import { TaskNode } from "./TaskNode"
13
+ import { DAGResult } from "./TaskResult"
14
+
15
+ const STATUS_TO_CANVAS: Record<string, string> = {
16
+ RUNNING: "thinking",
17
+ COMPLETED: "idle",
18
+ FAILED: "error",
19
+ }
20
+
21
+ export class EventBridge {
22
+ private swarmId: string
23
+ private projectId: string
24
+ private coordinatorId: string
25
+
26
+ constructor(swarmId: string, projectId: string, coordinatorId: string) {
27
+ this.swarmId = swarmId
28
+ this.projectId = projectId
29
+ this.coordinatorId = coordinatorId
30
+ }
31
+
32
+ onSwarmStarted(totalTasks: number): void {
33
+ agentBus.publish("project:started", {
34
+ projectId: this.projectId,
35
+ projectName: `swarm:${this.swarmId}`,
36
+ coordinatorId: this.coordinatorId,
37
+ timestamp: Date.now(),
38
+ })
39
+
40
+ emitCanvas("canvas:node_update", {
41
+ nodeId: this.projectId,
42
+ changes: { status: "thinking", label: `Swarm started (${totalTasks} tasks)` },
43
+ })
44
+ }
45
+
46
+ onTaskStarted(node: TaskNode): void {
47
+ agentBus.notifyTaskStarted(
48
+ node.agentId,
49
+ node.name,
50
+ 0, // task numeric ID not tracked here — DAG uses string IDs
51
+ node.name,
52
+ this.projectId
53
+ )
54
+
55
+ emitCanvas("canvas:node_update", {
56
+ nodeId: node.agentId,
57
+ changes: { status: STATUS_TO_CANVAS["RUNNING"], label: node.name },
58
+ })
59
+ }
60
+
61
+ onTaskCompleted(node: TaskNode, progress: number): void {
62
+ agentBus.notifyTaskCompleted(
63
+ node.agentId,
64
+ node.name,
65
+ 0,
66
+ node.name,
67
+ this.projectId,
68
+ node.result ?? ""
69
+ )
70
+
71
+ emitCanvas("canvas:node_update", {
72
+ nodeId: node.agentId,
73
+ changes: { status: STATUS_TO_CANVAS["COMPLETED"], progress },
74
+ })
75
+
76
+ // Broadcast overall swarm progress
77
+ agentBus.publish("message:custom", {
78
+ fromWorkerId: this.coordinatorId,
79
+ fromWorkerName: "DAGScheduler",
80
+ topic: "swarm:progress",
81
+ content: String(progress),
82
+ timestamp: Date.now(),
83
+ })
84
+ }
85
+
86
+ onTaskFailed(node: TaskNode, progress: number): void {
87
+ agentBus.notifyTaskFailed(
88
+ node.agentId,
89
+ node.name,
90
+ 0,
91
+ node.name,
92
+ this.projectId,
93
+ node.error ?? "unknown error"
94
+ )
95
+
96
+ emitCanvas("canvas:node_update", {
97
+ nodeId: node.agentId,
98
+ changes: { status: STATUS_TO_CANVAS["FAILED"] },
99
+ })
100
+ }
101
+
102
+ onSwarmCompleted(result: DAGResult): void {
103
+ const summary = `Completed ${result.completed.length}/${result.completed.length + result.failed.length} tasks in ${Math.round(result.totalDurationMs / 1000)}s`
104
+
105
+ agentBus.publish("project:completed", {
106
+ projectId: this.projectId,
107
+ projectName: `swarm:${this.swarmId}`,
108
+ coordinatorId: this.coordinatorId,
109
+ summary,
110
+ timestamp: Date.now(),
111
+ })
112
+
113
+ emitCanvas("canvas:node_update", {
114
+ nodeId: this.projectId,
115
+ changes: {
116
+ status: result.success ? "idle" : "error",
117
+ progress: 100,
118
+ label: summary,
119
+ },
120
+ })
121
+ }
122
+ }
@@ -0,0 +1,169 @@
1
+ import { EventEmitter } from "events";
2
+ import { logger } from "../utils/logger.ts";
3
+
4
+ export interface EventMap {
5
+ "message:received": {
6
+ channel: string;
7
+ userId: string;
8
+ content: string;
9
+ timestamp: number;
10
+ sessionId: string;
11
+ };
12
+ "message:sent": {
13
+ channel: string;
14
+ userId: string;
15
+ content: string;
16
+ messageId: string;
17
+ sessionId: string;
18
+ };
19
+ "agent:thinking": {
20
+ agentId: string;
21
+ sessionId: string;
22
+ stage: "planning" | "executing" | "responding";
23
+ };
24
+ "agent:response": {
25
+ agentId: string;
26
+ sessionId: string;
27
+ content: string;
28
+ toolsUsed: string[];
29
+ duration: number;
30
+ };
31
+ "tool:executing": {
32
+ toolName: string;
33
+ args: Record<string, unknown>;
34
+ sessionId: string;
35
+ };
36
+ "tool:completed": {
37
+ toolName: string;
38
+ result: unknown;
39
+ duration: number;
40
+ success: boolean;
41
+ };
42
+ "tool:error": {
43
+ toolName: string;
44
+ error: Error;
45
+ args: Record<string, unknown>;
46
+ };
47
+ "error": {
48
+ source: string;
49
+ error: Error;
50
+ context: Record<string, unknown>;
51
+ recoverable: boolean;
52
+ };
53
+ "session:started": {
54
+ sessionId: string;
55
+ agentId: string;
56
+ channel: string;
57
+ userId: string;
58
+ };
59
+ "session:ended": {
60
+ sessionId: string;
61
+ duration: number;
62
+ messageCount: number;
63
+ reason: "completed" | "cancelled" | "error" | "timeout";
64
+ };
65
+ "mcp:connected": {
66
+ serverName: string;
67
+ toolsCount: number;
68
+ resourcesCount: number;
69
+ };
70
+ "mcp:disconnected": {
71
+ serverName: string;
72
+ reason: string;
73
+ };
74
+ "mcp:error": {
75
+ serverName: string;
76
+ error: Error;
77
+ };
78
+ "channel:started": {
79
+ channel: string;
80
+ accountId: string;
81
+ };
82
+ "channel:stopped": {
83
+ channel: string;
84
+ accountId: string;
85
+ reason: string;
86
+ };
87
+ "gateway:started": {
88
+ host: string;
89
+ port: number;
90
+ };
91
+ "gateway:stopped": {
92
+ reason: string;
93
+ };
94
+ "pairing:requested": {
95
+ channel: string;
96
+ userId: string;
97
+ code: string;
98
+ expiresAt: number;
99
+ };
100
+ "pairing:approved": {
101
+ channel: string;
102
+ userId: string;
103
+ };
104
+ "pairing:rejected": {
105
+ channel: string;
106
+ userId: string;
107
+ reason: string;
108
+ };
109
+ "pairing:expired": {
110
+ code: string;
111
+ channel: string;
112
+ userId: string;
113
+ };
114
+ }
115
+
116
+ export type EventKey = keyof EventMap;
117
+
118
+ export interface EventHandler<K extends EventKey> {
119
+ (data: EventMap[K]): void | Promise<void>;
120
+ }
121
+
122
+ class TypedEventBusImpl {
123
+ private emitter = new EventEmitter();
124
+ private logPrefix = "[events]";
125
+
126
+ emit<K extends EventKey>(event: K, data: EventMap[K]): void {
127
+ const enrichedData = {
128
+ ...data,
129
+ _eventId: crypto.randomUUID(),
130
+ _timestamp: Date.now(),
131
+ _event: event,
132
+ } as EventMap[K] & { _eventId: string; _timestamp: number; _event: string };
133
+
134
+ this.emitter.emit(event, enrichedData);
135
+
136
+ if (process.env.DEBUG_EVENTS === "true") {
137
+ logger.debug(`${this.logPrefix} emitted: ${event}`, { data });
138
+ }
139
+ }
140
+
141
+ on<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
142
+ this.emitter.on(event, handler);
143
+ return () => this.off(event, handler);
144
+ }
145
+
146
+ once<K extends EventKey>(event: K, handler: EventHandler<K>): void {
147
+ this.emitter.once(event, handler);
148
+ }
149
+
150
+ off<K extends EventKey>(event: K, handler: EventHandler<K>): void {
151
+ this.emitter.off(event, handler);
152
+ }
153
+
154
+ removeAllListeners<K extends EventKey>(event?: K): void {
155
+ if (event) {
156
+ this.emitter.removeAllListeners(event);
157
+ } else {
158
+ this.emitter.removeAllListeners();
159
+ }
160
+ }
161
+
162
+ listenerCount<K extends EventKey>(event: K): number {
163
+ return this.emitter.listenerCount(event);
164
+ }
165
+ }
166
+
167
+ export const eventBus = new TypedEventBusImpl();
168
+
169
+ export type TypedEventBus = typeof eventBus;