@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.
- package/.github/CODEOWNERS +9 -0
- package/.github/workflows/publish.yml +89 -0
- package/.github/workflows/version-bump.yml +102 -0
- package/CHANGELOG.md +38 -0
- package/README.md +158 -0
- package/bun.lock +543 -0
- package/bunfig.toml +7 -0
- package/docs/API-AGENTS.md +316 -0
- package/docs/API-CONTEXT-COMPILER.md +252 -0
- package/docs/API-DAG-SCHEDULER.md +273 -0
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
- package/docs/API-WORKERS-EVENTS.md +152 -0
- package/docs/INDEX.md +141 -0
- package/docs/README.md +68 -0
- package/package.json +54 -105
- package/packages/cli/package.json +17 -0
- package/packages/cli/src/commands/init.ts +56 -0
- package/packages/cli/src/commands/run.ts +45 -0
- package/packages/cli/src/commands/test.ts +42 -0
- package/packages/cli/src/commands/trace.ts +55 -0
- package/packages/cli/src/index.ts +43 -0
- package/packages/core/package.json +58 -0
- package/packages/core/src/ace/Curator.ts +158 -0
- package/packages/core/src/ace/Reflector.ts +200 -0
- package/packages/core/src/ace/Tracer.ts +100 -0
- package/packages/core/src/ace/index.ts +4 -0
- package/packages/core/src/agent/AgentRunner.ts +699 -0
- package/packages/core/src/agent/Compaction.ts +221 -0
- package/packages/core/src/agent/ContextCompiler.ts +567 -0
- package/packages/core/src/agent/ContextGuard.ts +91 -0
- package/packages/core/src/agent/ConversationStore.ts +244 -0
- package/packages/core/src/agent/Hooks.ts +166 -0
- package/packages/core/src/agent/NativeTools.ts +31 -0
- package/packages/core/src/agent/PromptBuilder.ts +169 -0
- package/packages/core/src/agent/Service.ts +267 -0
- package/packages/core/src/agent/StuckLoop.ts +133 -0
- package/packages/core/src/agent/index.ts +12 -0
- package/packages/core/src/agent/providers/LLMClient.ts +149 -0
- package/packages/core/src/agent/providers/anthropic.ts +212 -0
- package/packages/core/src/agent/providers/gemini.ts +215 -0
- package/packages/core/src/agent/providers/index.ts +199 -0
- package/packages/core/src/agent/providers/interface.ts +195 -0
- package/packages/core/src/agent/providers/ollama.ts +175 -0
- package/packages/core/src/agent/providers/openai-compat.ts +231 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
- package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
- package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
- package/packages/core/src/agent/selectors/index.ts +6 -0
- package/packages/core/src/api/createAgent.test.ts +48 -0
- package/packages/core/src/api/createAgent.ts +122 -0
- package/packages/core/src/api/index.ts +2 -0
- package/packages/core/src/canvas/CanvasManager.ts +390 -0
- package/packages/core/src/canvas/a2ui-tools.ts +255 -0
- package/packages/core/src/canvas/canvas-tools.ts +448 -0
- package/packages/core/src/canvas/emitter.ts +149 -0
- package/packages/core/src/canvas/index.ts +6 -0
- package/packages/core/src/config/index.ts +2 -0
- package/packages/core/src/config/loader.ts +554 -0
- package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
- package/packages/core/src/ethics/EthicsGuard.ts +66 -0
- package/packages/core/src/ethics/index.ts +2 -0
- package/packages/core/src/gateway/channel-notify.test.ts +14 -0
- package/packages/core/src/gateway/channel-notify.ts +12 -0
- package/packages/core/src/gateway/index.ts +1 -0
- package/packages/core/src/index.ts +37 -0
- package/packages/core/src/mcp/MCPClient.ts +439 -0
- package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
- package/packages/core/src/mcp/config.ts +13 -0
- package/packages/core/src/mcp/hot-reload.ts +147 -0
- package/packages/core/src/mcp/index.ts +11 -0
- package/packages/core/src/mcp/logger.ts +42 -0
- package/packages/core/src/mcp/singleton.ts +21 -0
- package/packages/core/src/mcp/transports/index.ts +67 -0
- package/packages/core/src/mcp/transports/sse.ts +241 -0
- package/packages/core/src/mcp/transports/websocket.ts +159 -0
- package/packages/core/src/memory/Scratchpad.test.ts +47 -0
- package/packages/core/src/memory/Scratchpad.ts +37 -0
- package/packages/core/src/memory/Storage.ts +6 -0
- package/packages/core/src/memory/index.ts +2 -0
- package/packages/core/src/multimodal/VisionService.ts +293 -0
- package/packages/core/src/multimodal/index.ts +2 -0
- package/packages/core/src/multimodal/types.ts +28 -0
- package/packages/core/src/security/Pairing.ts +250 -0
- package/packages/core/src/security/RateLimit.ts +270 -0
- package/packages/core/src/security/index.ts +4 -0
- package/packages/core/src/skills/SkillLoader.ts +388 -0
- package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
- package/packages/core/src/skills/defineSkill.ts +18 -0
- package/packages/core/src/skills/index.ts +4 -0
- package/packages/core/src/state/index.ts +2 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/SQLiteStorage.ts +407 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/index.ts +10 -0
- package/packages/core/src/storage/onboarding.ts +1603 -0
- package/packages/core/src/storage/schema.ts +689 -0
- package/packages/core/src/storage/seed.ts +740 -0
- package/packages/core/src/storage/usage.ts +374 -0
- package/packages/core/src/swarm/AgentBus.ts +460 -0
- package/packages/core/src/swarm/AgentExecutor.ts +53 -0
- package/packages/core/src/swarm/Coordinator.ts +251 -0
- package/packages/core/src/swarm/EventBridge.ts +122 -0
- package/packages/core/src/swarm/EventBus.ts +169 -0
- package/packages/core/src/swarm/TaskGraph.ts +192 -0
- package/packages/core/src/swarm/TaskNode.ts +97 -0
- package/packages/core/src/swarm/TaskResult.ts +22 -0
- package/packages/core/src/swarm/WorkerPool.ts +236 -0
- package/packages/core/src/swarm/errors.ts +37 -0
- package/packages/core/src/swarm/index.ts +30 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
- package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
- package/packages/core/src/swarm/presets/index.ts +4 -0
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
- package/packages/core/src/swarm/strategies/index.ts +3 -0
- package/packages/core/src/swarm/types.ts +164 -0
- package/packages/core/src/tools/ToolExecutor.ts +58 -0
- package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
- package/packages/core/src/tools/ToolRegistry.ts +61 -0
- package/packages/core/src/tools/agents/get-available-models.ts +118 -0
- package/packages/core/src/tools/agents/index.ts +715 -0
- package/packages/core/src/tools/bridge-events.ts +26 -0
- package/packages/core/src/tools/canvas/index.ts +375 -0
- package/packages/core/src/tools/cli/index.ts +142 -0
- package/packages/core/src/tools/codebridge/index.ts +342 -0
- package/packages/core/src/tools/core/index.ts +476 -0
- package/packages/core/src/tools/cron/index.ts +626 -0
- package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
- package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
- package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
- package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
- package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
- package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
- package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
- package/packages/core/src/tools/filesystem/index.ts +34 -0
- package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
- package/packages/core/src/tools/index.ts +231 -0
- package/packages/core/src/tools/meeting/index.ts +363 -0
- package/packages/core/src/tools/office/index.ts +47 -0
- package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
- package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
- package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
- package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
- package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
- package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
- package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
- package/packages/core/src/tools/projects/index.ts +37 -0
- package/packages/core/src/tools/projects/project-create.ts +94 -0
- package/packages/core/src/tools/projects/project-done.ts +66 -0
- package/packages/core/src/tools/projects/project-fail.ts +66 -0
- package/packages/core/src/tools/projects/project-list.ts +96 -0
- package/packages/core/src/tools/projects/project-update.ts +72 -0
- package/packages/core/src/tools/projects/task-create.ts +68 -0
- package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
- package/packages/core/src/tools/projects/task-update.ts +93 -0
- package/packages/core/src/tools/types.ts +39 -0
- package/packages/core/src/tools/voice/index.ts +104 -0
- package/packages/core/src/tools/web/browser-click.ts +78 -0
- package/packages/core/src/tools/web/browser-extract.ts +139 -0
- package/packages/core/src/tools/web/browser-navigate.ts +106 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
- package/packages/core/src/tools/web/browser-script.ts +88 -0
- package/packages/core/src/tools/web/browser-service.ts +554 -0
- package/packages/core/src/tools/web/browser-type.ts +101 -0
- package/packages/core/src/tools/web/browser-wait.ts +136 -0
- package/packages/core/src/tools/web/index.ts +41 -0
- package/packages/core/src/tools/web/web-fetch.ts +78 -0
- package/packages/core/src/tools/web/web-search.ts +123 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +10 -0
- package/packages/core/src/utils/logger.ts +389 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/utils/toon.ts +253 -0
- package/packages/core/src/voice/index.ts +656 -0
- package/test/setup-db.ts +216 -0
- package/tsconfig.json +39 -0
- package/src/agents.ts +0 -1
- package/src/canvas.ts +0 -1
- package/src/channels.ts +0 -1
- package/src/config.ts +0 -1
- package/src/events.ts +0 -1
- package/src/gateway.ts +0 -1
- package/src/index.ts +0 -304
- package/src/mcp.ts +0 -1
- package/src/multimodal.ts +0 -1
- package/src/scheduler.ts +0 -1
- package/src/security.ts +0 -1
- package/src/skills.ts +0 -1
- package/src/state.ts +0 -1
- package/src/storage.ts +0 -1
- package/src/tools.ts +0 -1
- package/src/tts.ts +0 -1
- package/src/types.ts +0 -82
- package/src/utils.ts +0 -1
- 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;
|