@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,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAGScheduler — TaskGraph
|
|
3
|
+
*
|
|
4
|
+
* Owns the full set of TaskNodes for one swarm execution.
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* 1. Validate no cyclic dependencies (DFS) at construction time
|
|
7
|
+
* 2. Calculate the critical path (longest weighted path)
|
|
8
|
+
* 3. Provide runtime queries: ready nodes, overall progress
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { TaskNode, type TaskNodeConfig } from "./TaskNode"
|
|
12
|
+
import { CyclicDependencyError } from "./errors"
|
|
13
|
+
|
|
14
|
+
export class TaskGraph {
|
|
15
|
+
readonly nodes: Map<string, TaskNode>
|
|
16
|
+
private criticalPath_: string[] | null = null
|
|
17
|
+
|
|
18
|
+
constructor(configs: TaskNodeConfig[]) {
|
|
19
|
+
this.nodes = new Map(configs.map(c => [c.id, new TaskNode(c)]))
|
|
20
|
+
this.validateNoCycles()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
private validateNoCycles(): void {
|
|
26
|
+
// Three-color DFS: WHITE=unvisited, GRAY=in-stack, BLACK=done
|
|
27
|
+
const WHITE = 0, GRAY = 1, BLACK = 2
|
|
28
|
+
const color = new Map<string, number>()
|
|
29
|
+
|
|
30
|
+
for (const id of this.nodes.keys()) color.set(id, WHITE)
|
|
31
|
+
|
|
32
|
+
const path: string[] = []
|
|
33
|
+
|
|
34
|
+
const visit = (id: string): void => {
|
|
35
|
+
color.set(id, GRAY)
|
|
36
|
+
path.push(id)
|
|
37
|
+
|
|
38
|
+
const node = this.nodes.get(id)!
|
|
39
|
+
for (const dep of node.deps) {
|
|
40
|
+
if (!this.nodes.has(dep)) {
|
|
41
|
+
throw new Error(`TaskGraph: node "${id}" depends on unknown node "${dep}"`)
|
|
42
|
+
}
|
|
43
|
+
const c = color.get(dep)!
|
|
44
|
+
if (c === GRAY) {
|
|
45
|
+
// Found back-edge: reconstruct cycle
|
|
46
|
+
const cycleStart = path.indexOf(dep)
|
|
47
|
+
throw new CyclicDependencyError([...path.slice(cycleStart), dep])
|
|
48
|
+
}
|
|
49
|
+
if (c === WHITE) visit(dep)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
path.pop()
|
|
53
|
+
color.set(id, BLACK)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const id of this.nodes.keys()) {
|
|
57
|
+
if (color.get(id) === WHITE) visit(id)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Critical Path ───────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns the IDs of the longest dependency chain (by node count).
|
|
65
|
+
* Nodes on this path get higher priority in PriorityStrategy.
|
|
66
|
+
*/
|
|
67
|
+
getCriticalPath(): string[] {
|
|
68
|
+
if (this.criticalPath_) return this.criticalPath_
|
|
69
|
+
|
|
70
|
+
// longest[id] = length of longest path ending at id (inclusive)
|
|
71
|
+
const longest = new Map<string, number>()
|
|
72
|
+
const predecessor = new Map<string, string | null>()
|
|
73
|
+
|
|
74
|
+
const compute = (id: string): number => {
|
|
75
|
+
if (longest.has(id)) return longest.get(id)!
|
|
76
|
+
const node = this.nodes.get(id)!
|
|
77
|
+
if (node.deps.length === 0) {
|
|
78
|
+
longest.set(id, 1)
|
|
79
|
+
predecessor.set(id, null)
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
let max = 0
|
|
83
|
+
let maxPred: string | null = null
|
|
84
|
+
for (const dep of node.deps) {
|
|
85
|
+
const l = compute(dep)
|
|
86
|
+
if (l > max) { max = l; maxPred = dep }
|
|
87
|
+
}
|
|
88
|
+
longest.set(id, max + 1)
|
|
89
|
+
predecessor.set(id, maxPred)
|
|
90
|
+
return max + 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const id of this.nodes.keys()) compute(id)
|
|
94
|
+
|
|
95
|
+
// Find sink with max longest
|
|
96
|
+
let sinkId = ""
|
|
97
|
+
let maxLen = 0
|
|
98
|
+
for (const [id, len] of longest) {
|
|
99
|
+
if (len > maxLen) { maxLen = len; sinkId = id }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Reconstruct path from sink to source, then reverse
|
|
103
|
+
const path: string[] = []
|
|
104
|
+
let cur: string | null = sinkId
|
|
105
|
+
while (cur !== null) {
|
|
106
|
+
path.push(cur)
|
|
107
|
+
cur = predecessor.get(cur) ?? null
|
|
108
|
+
}
|
|
109
|
+
path.reverse()
|
|
110
|
+
|
|
111
|
+
this.criticalPath_ = path
|
|
112
|
+
return path
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Runtime queries ─────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/** Nodes that are still PENDING but now have all deps completed */
|
|
118
|
+
getNewlyReadyNodes(completedIds: Set<string>): TaskNode[] {
|
|
119
|
+
const ready: TaskNode[] = []
|
|
120
|
+
for (const node of this.nodes.values()) {
|
|
121
|
+
if (node.status === "PENDING" && node.canStart(completedIds)) {
|
|
122
|
+
ready.push(node)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return ready
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** All nodes currently in READY state */
|
|
129
|
+
getReadyNodes(): TaskNode[] {
|
|
130
|
+
return [...this.nodes.values()].filter(n => n.status === "READY")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** IDs of all COMPLETED nodes */
|
|
134
|
+
getCompletedIds(): Set<string> {
|
|
135
|
+
const ids = new Set<string>()
|
|
136
|
+
for (const [id, node] of this.nodes) {
|
|
137
|
+
if (node.status === "COMPLETED") ids.add(id)
|
|
138
|
+
}
|
|
139
|
+
return ids
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Collect results of all deps for a given node */
|
|
143
|
+
getDepResults(nodeId: string): Record<string, string> {
|
|
144
|
+
const node = this.nodes.get(nodeId)!
|
|
145
|
+
const results: Record<string, string> = {}
|
|
146
|
+
for (const dep of node.deps) {
|
|
147
|
+
const depNode = this.nodes.get(dep)!
|
|
148
|
+
if (depNode.result !== undefined) results[dep] = depNode.result
|
|
149
|
+
}
|
|
150
|
+
return results
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 0–100 progress based on nodes in terminal state */
|
|
154
|
+
getProgress(): number {
|
|
155
|
+
const total = this.nodes.size
|
|
156
|
+
if (total === 0) return 100
|
|
157
|
+
let done = 0
|
|
158
|
+
for (const node of this.nodes.values()) {
|
|
159
|
+
if (node.status === "COMPLETED" || node.status === "FAILED") done++
|
|
160
|
+
}
|
|
161
|
+
return Math.round((done / total) * 100)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** True when every node is in a terminal state */
|
|
165
|
+
isComplete(): boolean {
|
|
166
|
+
for (const node of this.nodes.values()) {
|
|
167
|
+
if (node.status === "PENDING" || node.status === "READY" || node.status === "RUNNING") {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Propagate FAILED status to all nodes that depend (directly or transitively) on failedId */
|
|
175
|
+
propagateFailure(failedId: string, reason: string): void {
|
|
176
|
+
const failedSet = new Set<string>([failedId])
|
|
177
|
+
|
|
178
|
+
let changed = true
|
|
179
|
+
while (changed) {
|
|
180
|
+
changed = false
|
|
181
|
+
for (const node of this.nodes.values()) {
|
|
182
|
+
if (node.status === "PENDING" || node.status === "READY") {
|
|
183
|
+
if (node.deps.some(d => failedSet.has(d))) {
|
|
184
|
+
node.markFailed(`dependency_failed: ${reason}`)
|
|
185
|
+
failedSet.add(node.id)
|
|
186
|
+
changed = true
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAGScheduler — TaskNode
|
|
3
|
+
*
|
|
4
|
+
* Represents a single node in the task graph. Tracks its own state,
|
|
5
|
+
* retry count, timing, and the results of its dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type NodeStatus = "PENDING" | "READY" | "RUNNING" | "COMPLETED" | "FAILED"
|
|
9
|
+
|
|
10
|
+
export interface TaskNodeConfig {
|
|
11
|
+
/** Unique ID within this graph (can match SQLite task.id) */
|
|
12
|
+
id: string
|
|
13
|
+
/** agents.id of the worker agent to execute */
|
|
14
|
+
agentId: string
|
|
15
|
+
/** Human-readable name for logging */
|
|
16
|
+
name: string
|
|
17
|
+
/** Task description passed to the worker */
|
|
18
|
+
taskDescription: string
|
|
19
|
+
/** IDs of other TaskNodes that must complete first */
|
|
20
|
+
deps: string[]
|
|
21
|
+
/** Timeout in ms before the task is cancelled. Default: 120_000 */
|
|
22
|
+
timeout?: number
|
|
23
|
+
/** How many times to retry on failure. Default: 1 */
|
|
24
|
+
maxRetries?: number
|
|
25
|
+
/** Priority hint for PriorityStrategy. Higher = runs first. Default: 0 */
|
|
26
|
+
priority?: number
|
|
27
|
+
/** Optional arbitrary metadata forwarded to the worker */
|
|
28
|
+
metadata?: Record<string, unknown>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class TaskNode {
|
|
32
|
+
readonly id: string
|
|
33
|
+
readonly agentId: string
|
|
34
|
+
readonly name: string
|
|
35
|
+
readonly taskDescription: string
|
|
36
|
+
readonly deps: string[]
|
|
37
|
+
readonly timeout: number
|
|
38
|
+
readonly maxRetries: number
|
|
39
|
+
readonly priority: number
|
|
40
|
+
readonly metadata: Record<string, unknown>
|
|
41
|
+
|
|
42
|
+
status: NodeStatus = "PENDING"
|
|
43
|
+
retryCount = 0
|
|
44
|
+
startedAt?: number
|
|
45
|
+
completedAt?: number
|
|
46
|
+
result?: string
|
|
47
|
+
error?: string
|
|
48
|
+
|
|
49
|
+
constructor(config: TaskNodeConfig) {
|
|
50
|
+
this.id = config.id
|
|
51
|
+
this.agentId = config.agentId
|
|
52
|
+
this.name = config.name
|
|
53
|
+
this.taskDescription = config.taskDescription
|
|
54
|
+
this.deps = config.deps
|
|
55
|
+
this.timeout = config.timeout ?? 120_000
|
|
56
|
+
this.maxRetries = config.maxRetries ?? 1
|
|
57
|
+
this.priority = config.priority ?? 0
|
|
58
|
+
this.metadata = config.metadata ?? {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Returns true if all dependency IDs are in the completed set */
|
|
62
|
+
canStart(completedIds: Set<string>): boolean {
|
|
63
|
+
return this.deps.every(dep => completedIds.has(dep))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
markReady(): void {
|
|
67
|
+
this.status = "READY"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
markRunning(): void {
|
|
71
|
+
this.status = "RUNNING"
|
|
72
|
+
this.startedAt = Date.now()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
markCompleted(result: string): void {
|
|
76
|
+
this.status = "COMPLETED"
|
|
77
|
+
this.completedAt = Date.now()
|
|
78
|
+
this.result = result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
markFailed(error: string): void {
|
|
82
|
+
this.status = "FAILED"
|
|
83
|
+
this.completedAt = Date.now()
|
|
84
|
+
this.error = error
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
canRetry(): boolean {
|
|
88
|
+
return this.retryCount < this.maxRetries
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Elapsed time in seconds since start, or total duration if done */
|
|
92
|
+
elapsedSeconds(): number {
|
|
93
|
+
if (!this.startedAt) return 0
|
|
94
|
+
const end = this.completedAt ?? Date.now()
|
|
95
|
+
return Math.round((end - this.startedAt) / 1000)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAGScheduler — Result types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface NodeSummary {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
status: "COMPLETED" | "FAILED"
|
|
9
|
+
durationMs: number
|
|
10
|
+
result?: string
|
|
11
|
+
error?: string
|
|
12
|
+
retries: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DAGResult {
|
|
16
|
+
swarmId: string
|
|
17
|
+
totalDurationMs: number
|
|
18
|
+
completed: NodeSummary[]
|
|
19
|
+
failed: NodeSummary[]
|
|
20
|
+
/** true if all nodes completed successfully */
|
|
21
|
+
success: boolean
|
|
22
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive Scheduler - Pipeline Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates cron jobs with the Hive agent pipeline.
|
|
5
|
+
* Converts cron jobs into system messages that flow through the agent system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CronJob, CronJobExecutionResult } from "./types";
|
|
9
|
+
import { logger } from "../utils/logger.ts";
|
|
10
|
+
import { getDb } from "../storage/SQLiteStorage.ts";
|
|
11
|
+
import { buildAgentLoop } from "../agent/AgentRunner.ts";
|
|
12
|
+
import { resolveAgentId } from "../storage/onboarding.ts";
|
|
13
|
+
import { sendToUserChannel } from "../gateway/channel-notify.ts";
|
|
14
|
+
import { addMessage } from "../agent/ConversationStore.ts";
|
|
15
|
+
import { resolveBestChannel } from "../tools/cron/index.ts";
|
|
16
|
+
|
|
17
|
+
const log = logger.child("SchedulerIntegration");
|
|
18
|
+
|
|
19
|
+
let _scheduler: { runCleanup(): void } | null = null;
|
|
20
|
+
|
|
21
|
+
export function setSchedulerForCleanup(scheduler: { runCleanup(): void }): void {
|
|
22
|
+
_scheduler = scheduler;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Execute a cron job through the agent pipeline
|
|
27
|
+
*
|
|
28
|
+
* This handler:
|
|
29
|
+
* 1. Parses the job payload
|
|
30
|
+
* 2. Builds a system message with metadata including the `task` field as instruction
|
|
31
|
+
* 3. Routes to the target agent (or Coordinator if none specified)
|
|
32
|
+
* 4. Executes the tool if tool_name is specified
|
|
33
|
+
* 5. Returns the agent response
|
|
34
|
+
*/
|
|
35
|
+
export async function executeScheduledTask(job: CronJob): Promise<CronJobExecutionResult> {
|
|
36
|
+
log.info(`[execute] Processing job "${job.name}" (${job.id})`);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
let payload: Record<string, unknown>;
|
|
40
|
+
try {
|
|
41
|
+
payload = JSON.parse(job.payload);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.error(`[execute] Invalid payload JSON for job "${job.id}": ${(err as Error).message}`);
|
|
44
|
+
return { success: false, error: "Invalid payload JSON" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const prompt = (payload.prompt || payload.message) as string | undefined;
|
|
48
|
+
if (!prompt && !payload._internal && !job.task) {
|
|
49
|
+
log.error(`[execute] Job "${job.id}" has no prompt, message, or task instruction`);
|
|
50
|
+
return { success: false, error: "Missing prompt, message, or task instruction" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (payload._internal === true && (payload as any).action === "cleanup") {
|
|
54
|
+
if (_scheduler) {
|
|
55
|
+
_scheduler.runCleanup();
|
|
56
|
+
} else {
|
|
57
|
+
log.warn("[execute] Cleanup job fired but scheduler instance not available");
|
|
58
|
+
}
|
|
59
|
+
log.info("[execute] Cleanup job executed");
|
|
60
|
+
return { success: true, response: "Cleanup completed" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build message metadata
|
|
64
|
+
const metadata = {
|
|
65
|
+
source: "scheduler" as const,
|
|
66
|
+
task_id: job.id,
|
|
67
|
+
task_name: job.name,
|
|
68
|
+
channel: job.channel,
|
|
69
|
+
scheduled: true,
|
|
70
|
+
tool_name: job.tool_name || undefined,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let targetAgentId: string | null = job.agent_id || null;
|
|
74
|
+
|
|
75
|
+
if (!targetAgentId) {
|
|
76
|
+
targetAgentId = resolveAgentId(null);
|
|
77
|
+
log.debug(`[execute] No agent specified, routing to Coordinator: ${targetAgentId}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const db = getDb();
|
|
81
|
+
const user = db.query("SELECT id, timezone, language FROM users LIMIT 1").get() as {
|
|
82
|
+
id: string;
|
|
83
|
+
timezone: string;
|
|
84
|
+
language: string | null;
|
|
85
|
+
} | undefined;
|
|
86
|
+
|
|
87
|
+
const userTimezone = user?.timezone || "UTC";
|
|
88
|
+
const userLanguage = user?.language || "en";
|
|
89
|
+
|
|
90
|
+
const now = new Date();
|
|
91
|
+
const dateOptions: Intl.DateTimeFormatOptions = {
|
|
92
|
+
timeZone: userTimezone,
|
|
93
|
+
year: "numeric",
|
|
94
|
+
month: "long",
|
|
95
|
+
day: "numeric",
|
|
96
|
+
weekday: "long",
|
|
97
|
+
};
|
|
98
|
+
const timeOptions: Intl.DateTimeFormatOptions = {
|
|
99
|
+
timeZone: userTimezone,
|
|
100
|
+
hour: "2-digit",
|
|
101
|
+
minute: "2-digit",
|
|
102
|
+
second: "2-digit",
|
|
103
|
+
hour12: false,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const fecha_usuario = new Intl.DateTimeFormat(userLanguage === "es" ? "es-ES" : "en-US", dateOptions).format(now);
|
|
107
|
+
const hora_usuario = new Intl.DateTimeFormat(userLanguage === "es" ? "es-ES" : "en-US", timeOptions).format(now);
|
|
108
|
+
|
|
109
|
+
// Build the full prompt with the `task` field as the primary instruction
|
|
110
|
+
const contextPrompt = `[SCHEDULED TASK]
|
|
111
|
+
Name: ${job.name}
|
|
112
|
+
Instruction: ${job.task}
|
|
113
|
+
Type: ${job.task_type}
|
|
114
|
+
Triggered at: ${hora_usuario} on ${fecha_usuario} (${userTimezone})
|
|
115
|
+
|
|
116
|
+
${prompt || `Execute tool: ${job.tool_name}`}`;
|
|
117
|
+
|
|
118
|
+
log.debug(`[execute] Sending to agent ${targetAgentId}: "${contextPrompt.slice(0, 100)}..."`);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const agentLoop = buildAgentLoop({ mcpManager: undefined });
|
|
122
|
+
|
|
123
|
+
const sessionId = `sched_${job.id}_${Date.now()}`;
|
|
124
|
+
|
|
125
|
+
const agentChannel = (job.channel && job.channel !== "system")
|
|
126
|
+
? job.channel
|
|
127
|
+
: resolveBestChannel(user?.id || "");
|
|
128
|
+
|
|
129
|
+
const messages = [{ role: "user", content: contextPrompt }];
|
|
130
|
+
const stream = agentLoop.stream({ messages }, {
|
|
131
|
+
configurable: {
|
|
132
|
+
thread_id: sessionId,
|
|
133
|
+
agent_id: targetAgentId || undefined,
|
|
134
|
+
channel: agentChannel,
|
|
135
|
+
user_id: user?.id || "",
|
|
136
|
+
system_prompt: undefined,
|
|
137
|
+
raw_user_message: contextPrompt,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
let response = "";
|
|
142
|
+
let hasError = false;
|
|
143
|
+
|
|
144
|
+
for await (const chunk of stream) {
|
|
145
|
+
if (chunk.agent?.messages) {
|
|
146
|
+
const lastMsg = chunk.agent.messages[chunk.agent.messages.length - 1];
|
|
147
|
+
if (lastMsg?.content) {
|
|
148
|
+
response += lastMsg.content;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (chunk.tools?.messages) {
|
|
152
|
+
for (const msg of chunk.tools.messages) {
|
|
153
|
+
if (msg.content?.error) {
|
|
154
|
+
hasError = true;
|
|
155
|
+
response += ` [Tool error: ${JSON.stringify(msg.content)}]`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (hasError && !response) {
|
|
162
|
+
throw new Error("Agent execution returned errors");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
log.info(`[execute] Agent response received for job "${job.name}"`);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
success: true,
|
|
169
|
+
response: response || "Task executed successfully",
|
|
170
|
+
};
|
|
171
|
+
} catch (agentErr) {
|
|
172
|
+
log.error(`[execute] Agent execution failed: ${(agentErr as Error).message}`);
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: `Agent execution failed: ${(agentErr as Error).message}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
log.error(`[execute] Job execution failed: ${(err as Error).message}`);
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: (err as Error).message,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Send notification to user's channel after job execution
|
|
189
|
+
*/
|
|
190
|
+
export async function notifyTaskCompletion(
|
|
191
|
+
taskId: string,
|
|
192
|
+
taskName: string,
|
|
193
|
+
success: boolean,
|
|
194
|
+
response?: string,
|
|
195
|
+
error?: string
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
const db = getDb();
|
|
198
|
+
|
|
199
|
+
const task = db.query(
|
|
200
|
+
"SELECT channel, agent_id FROM cron_jobs WHERE id = ?"
|
|
201
|
+
).get(taskId) as { channel: string; agent_id: string | null } | undefined;
|
|
202
|
+
|
|
203
|
+
if (!task) {
|
|
204
|
+
log.warn(`[notify] Job "${taskId}" not found`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const userRow = db.query("SELECT id FROM users LIMIT 1").get() as { id: string } | undefined;
|
|
209
|
+
const userId = userRow?.id || "";
|
|
210
|
+
|
|
211
|
+
const explicitChannel = task.channel && task.channel !== "system" ? task.channel : undefined;
|
|
212
|
+
const notifyChannel = resolveBestChannel(userId, explicitChannel) || "webchat";
|
|
213
|
+
|
|
214
|
+
const status = success ? "✅" : "❌";
|
|
215
|
+
const message = success
|
|
216
|
+
? `${status} Scheduled task "${taskName}" completed\n${response || ""}`
|
|
217
|
+
: `${status} Scheduled task "${taskName}" failed\n${error || ""}`;
|
|
218
|
+
|
|
219
|
+
log.info(`[notify] Sending notification to ${notifyChannel}: "${message.slice(0, 50)}..."`);
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
addMessage(userId, "assistant", message, { channel: notifyChannel });
|
|
223
|
+
} catch (e) {
|
|
224
|
+
log.warn(`[notify] Failed to persist notification to DB: ${(e as Error).message}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await sendToUserChannel(notifyChannel, userId, message);
|
|
228
|
+
log.info(`[notify] Notification sent to ${notifyChannel}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create the job execution handler for CronScheduler
|
|
233
|
+
*/
|
|
234
|
+
export function createTaskHandler() {
|
|
235
|
+
return executeScheduledTask;
|
|
236
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAGScheduler — Custom errors
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class CyclicDependencyError extends Error {
|
|
6
|
+
readonly cycle: string[]
|
|
7
|
+
|
|
8
|
+
constructor(cycle: string[]) {
|
|
9
|
+
super(`Cyclic dependency detected: ${cycle.join(" → ")}`)
|
|
10
|
+
this.name = "CyclicDependencyError"
|
|
11
|
+
this.cycle = cycle
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TaskTimeoutError extends Error {
|
|
16
|
+
readonly nodeId: string
|
|
17
|
+
readonly timeoutMs: number
|
|
18
|
+
|
|
19
|
+
constructor(nodeId: string, timeoutMs: number) {
|
|
20
|
+
super(`Task "${nodeId}" timed out after ${timeoutMs}ms`)
|
|
21
|
+
this.name = "TaskTimeoutError"
|
|
22
|
+
this.nodeId = nodeId
|
|
23
|
+
this.timeoutMs = timeoutMs
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class TaskFailureError extends Error {
|
|
28
|
+
readonly nodeId: string
|
|
29
|
+
readonly cause: Error
|
|
30
|
+
|
|
31
|
+
constructor(nodeId: string, cause: Error) {
|
|
32
|
+
super(`Task "${nodeId}" failed: ${cause.message}`)
|
|
33
|
+
this.name = "TaskFailureError"
|
|
34
|
+
this.nodeId = nodeId
|
|
35
|
+
this.cause = cause
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { DAGScheduler } from "./Coordinator.ts";
|
|
2
|
+
export type { DAGSchedulerOptions, IAgentExecutor } from "./Coordinator.ts";
|
|
3
|
+
|
|
4
|
+
export { TaskGraph } from "./TaskGraph.ts";
|
|
5
|
+
export { TaskNode } from "./TaskNode.ts";
|
|
6
|
+
export type { TaskNodeConfig, NodeStatus } from "./TaskNode.ts";
|
|
7
|
+
export type { DAGResult, NodeSummary } from "./TaskResult.ts";
|
|
8
|
+
|
|
9
|
+
export { AgentExecutor } from "./AgentExecutor.ts";
|
|
10
|
+
export { EventBridge } from "./EventBridge.ts";
|
|
11
|
+
|
|
12
|
+
export { CyclicDependencyError, TaskTimeoutError, TaskFailureError } from "./errors.ts";
|
|
13
|
+
|
|
14
|
+
export type { AgentBusEventMap, AgentBusEventKey, AgentBusEventHandler, AgentBusMessage } from "./AgentBus.ts";
|
|
15
|
+
export { getUnreadMessagesForWorker, getProjectMessageHistory, agentBus } from "./AgentBus.ts";
|
|
16
|
+
export type { AgentBus } from "./AgentBus.ts";
|
|
17
|
+
|
|
18
|
+
export type { EventMap, EventKey, EventHandler } from "./EventBus.ts";
|
|
19
|
+
export { eventBus } from "./EventBus.ts";
|
|
20
|
+
export type { TypedEventBus } from "./EventBus.ts";
|
|
21
|
+
|
|
22
|
+
export { setSchedulerForCleanup, executeScheduledTask, notifyTaskCompletion, createTaskHandler } from "./WorkerPool.ts";
|
|
23
|
+
|
|
24
|
+
export type { ExecutionStrategy } from "./strategies/index.ts";
|
|
25
|
+
export { ParallelStrategy, PriorityStrategy } from "./strategies/index.ts";
|
|
26
|
+
|
|
27
|
+
export { createHiveLearnGraph } from "./presets/index.ts";
|
|
28
|
+
export type { HiveLearnAgentIds, HiveLearnInput } from "./presets/index.ts";
|
|
29
|
+
export { createResearchGraph } from "./presets/index.ts";
|
|
30
|
+
export type { ResearchAgentIds } from "./presets/index.ts";
|