@johpaz/hive-sdk 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,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";