@johpaz/hive-sdk 0.1.4 → 0.1.5
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/CHANGELOG.md +97 -0
- package/README.md +78 -23
- package/bunfig.toml +4 -2
- package/docs/API-AGENTS.md +78 -27
- package/docs/API-CONTEXT-COMPILER.md +31 -34
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
- package/docs/HIVE-HARNESS.md +1 -1
- package/docs/INDEX.md +4 -4
- package/docs/TEMPLATE-HIVE-APP.md +10 -10
- package/package.json +9 -4
- package/packages/cli/package.json +2 -2
- package/packages/cli/src/commands/create-app.test.ts +36 -7
- package/packages/cli/src/commands/init.ts +3 -3
- package/packages/cli/src/commands/run.ts +1 -1
- package/packages/cli/src/commands/test.ts +37 -25
- package/packages/cli/src/commands/trace.ts +30 -28
- package/packages/cli/templates/hive-app/.env.example +10 -2
- package/packages/cli/templates/hive-app/README.md +103 -0
- package/packages/cli/templates/hive-app/hive.config.ts +9 -3
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
- package/packages/cli/templates/hive-app/src/main.ts +12 -19
- package/packages/core/package.json +5 -4
- package/packages/core/src/agent/acceptance-checks.ts +166 -0
- package/packages/core/src/agent/agent-catalog.ts +348 -0
- package/packages/core/src/agent/agent-loop.ts +1373 -0
- package/packages/core/src/agent/capability-search.ts +186 -0
- package/packages/core/src/agent/catalog-selector.ts +103 -0
- package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
- package/packages/core/src/agent/context-compiler.ts +689 -0
- package/packages/core/src/agent/conversation-store.ts +381 -0
- package/packages/core/src/agent/curator.ts +276 -0
- package/packages/core/src/agent/delegation-runtime.ts +241 -0
- package/packages/core/src/agent/goal-runner.ts +323 -0
- package/packages/core/src/agent/index.ts +17 -12
- package/packages/core/src/agent/llm-client.ts +266 -0
- package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
- package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
- package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
- package/packages/core/src/agent/llm-providers/groq.ts +5 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
- package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
- package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
- package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
- package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
- package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
- package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
- package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
- package/packages/core/src/agent/llm-providers/openai.ts +5 -0
- package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
- package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
- package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
- package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
- package/packages/core/src/agent/minimal-loadout.ts +47 -0
- package/packages/core/src/agent/playbook-selector.ts +119 -0
- package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
- package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
- package/packages/core/src/agent/providers/index.ts +35 -16
- package/packages/core/src/agent/reflector.ts +320 -0
- package/packages/core/src/agent/routing-intent.ts +22 -0
- package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
- package/packages/core/src/{harness → agent}/run-store.ts +142 -81
- package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
- package/packages/core/src/agent/skill-selector.ts +374 -0
- package/packages/core/src/agent/stuck-loop.ts +209 -0
- package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
- package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
- package/packages/core/src/api/createAgent.test.ts +139 -27
- package/packages/core/src/api/createAgent.ts +232 -44
- package/packages/core/src/artifacts/store.ts +162 -0
- package/packages/core/src/canvas/canvas-manager.ts +161 -0
- package/packages/core/src/canvas/canvas.test.ts +8 -4
- package/packages/core/src/canvas/emitter.ts +131 -80
- package/packages/core/src/canvas/index.ts +1 -3
- package/packages/core/src/channels/base.ts +9 -1
- package/packages/core/src/channels/discord.ts +5 -4
- package/packages/core/src/channels/manager.ts +122 -30
- package/packages/core/src/channels/slack.ts +5 -4
- package/packages/core/src/channels/telegram.ts +36 -6
- package/packages/core/src/channels/webchat.ts +11 -10
- package/packages/core/src/channels/whatsapp.ts +23 -7
- package/packages/core/src/config/index.ts +13 -2
- package/packages/core/src/config/loader.ts +71 -29
- package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
- package/packages/core/src/ethics/EthicsGuard.ts +51 -47
- package/packages/core/src/events/agent-bus.ts +44 -68
- package/packages/core/src/events/channel-narration.ts +150 -0
- package/packages/core/src/events/narration.ts +82 -0
- package/packages/core/src/events/tool-narration.ts +62 -0
- package/packages/core/src/gateway/delegation-groups.ts +258 -0
- package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
- package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
- package/packages/core/src/gateway/lane-queue.ts +173 -0
- package/packages/core/src/gateway/notification-inbox.ts +57 -0
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/harness/index.ts +46 -27
- package/packages/core/src/index.ts +33 -27
- package/packages/core/src/mcp/hot-reload.ts +32 -23
- package/packages/core/src/mcp/index.ts +6 -3
- package/packages/core/src/mcp/singleton.ts +1 -4
- package/packages/core/src/mcp/tool-sync.ts +138 -0
- package/packages/core/src/memory/Scratchpad.test.ts +39 -20
- package/packages/core/src/memory/Scratchpad.ts +27 -34
- package/packages/core/src/multimodal/vision-service.ts +44 -38
- package/packages/core/src/resilience/retry.ts +95 -0
- package/packages/core/src/scheduler/CronScheduler.ts +334 -287
- package/packages/core/src/scheduler/index.ts +9 -7
- package/packages/core/src/scheduler/integration.ts +46 -26
- package/packages/core/src/scheduler/scheduler.test.ts +9 -13
- package/packages/core/src/scheduler/types.ts +7 -2
- package/packages/core/src/security/Pairing.ts +1 -1
- package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
- package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
- package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
- package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
- package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
- package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
- package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
- package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
- package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
- package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
- package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
- package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
- package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
- package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
- package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
- package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
- package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
- package/packages/core/src/skills/skills.test.ts +52 -11
- package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
- package/packages/core/src/storage/bootstrap.ts +151 -0
- package/packages/core/src/storage/causal-events.ts +84 -0
- package/packages/core/src/storage/collections.ts +680 -0
- package/packages/core/src/storage/crypto.ts +205 -74
- package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
- package/packages/core/src/storage/hivedb.ts +61 -0
- package/packages/core/src/storage/index.ts +111 -18
- package/packages/core/src/storage/model-id.ts +53 -0
- package/packages/core/src/storage/onboarding.ts +540 -972
- package/packages/core/src/storage/reconcile.ts +238 -0
- package/packages/core/src/storage/seed.ts +572 -406
- package/packages/core/src/storage/usage.ts +285 -225
- package/packages/core/src/storage/user-email.ts +11 -0
- package/packages/core/src/swarm/AgentExecutor.ts +1 -1
- package/packages/core/src/swarm/EventBridge.ts +1 -1
- package/packages/core/src/swarm/index.ts +12 -9
- package/packages/core/src/tool-runtime/index.ts +146 -23
- package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
- package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
- package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
- package/packages/core/src/tools/agents/get-available-models.ts +36 -54
- package/packages/core/src/tools/agents/index.ts +784 -292
- package/packages/core/src/tools/api/api-request.test.ts +164 -0
- package/packages/core/src/tools/api/api-request.ts +174 -0
- package/packages/core/src/tools/api/index.ts +16 -0
- package/packages/core/src/tools/cli/index.ts +4 -0
- package/packages/core/src/tools/core/index.ts +281 -112
- package/packages/core/src/tools/cron/index.ts +121 -124
- package/packages/core/src/tools/index.ts +63 -78
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
- package/packages/core/src/tools/types.ts +3 -1
- package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +5 -0
- package/packages/core/src/tools/web/browser-type.ts +3 -8
- package/packages/core/src/tools/web/index.ts +4 -4
- package/packages/core/src/voice/index.ts +89 -63
- package/packages/core/src/workers/agent.worker.ts +2 -2
- package/packages/core/src/workers/workers.test.ts +3 -10
- package/scripts/bump-version.ts +248 -0
- package/scripts/generate-skill-bundle.ts +108 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/catalog-agents-stay-enabled.test.ts +117 -0
- package/test/causal-events.test.ts +117 -0
- package/test/compaction.test.ts +105 -0
- package/test/context-compiler.test.ts +269 -0
- package/test/curator.test.ts +130 -0
- package/test/durable-queue.test.ts +114 -0
- package/test/harness-barrel.test.ts +64 -0
- package/test/hive-helpers.test.ts +130 -0
- package/test/hivedb-search.test.ts +189 -0
- package/test/internal-turns.test.ts +166 -0
- package/test/job-idempotency.test.ts +68 -0
- package/test/job-retry-backoff.test.ts +184 -0
- package/test/job-store.test.ts +381 -0
- package/test/llm-retry.test.ts +97 -0
- package/test/memory-perf.test.ts +774 -0
- package/test/minimal-loadout.test.ts +78 -0
- package/test/model-catalog.test.ts +105 -0
- package/test/preload.ts +12 -0
- package/test/reflector.test.ts +320 -0
- package/test/retention-cap.test.ts +91 -0
- package/test/retired-capabilities-pruned.test.ts +192 -0
- package/test/run-store.test.ts +355 -0
- package/test/scratchpad.test.ts +74 -0
- package/test/secrets-durability.test.ts +119 -0
- package/test/seed-model-reseed.test.ts +155 -0
- package/test/setup-agent-seed.test.ts +264 -0
- package/test/tool-inventory.test.ts +65 -0
- package/test/tool-runtime.test.ts +258 -0
- package/test/toon.test.ts +429 -0
- package/tsconfig.json +2 -0
- package/packages/core/src/ace/Curator.ts +0 -158
- package/packages/core/src/ace/Reflector.ts +0 -200
- package/packages/core/src/ace/index.ts +0 -4
- package/packages/core/src/agent/AgentRunner.ts +0 -711
- package/packages/core/src/agent/ContextCompiler.ts +0 -567
- package/packages/core/src/agent/ContextGuard.ts +0 -91
- package/packages/core/src/agent/ConversationStore.ts +0 -254
- package/packages/core/src/agent/Hooks.ts +0 -166
- package/packages/core/src/agent/StuckLoop.ts +0 -133
- package/packages/core/src/agent/providers/LLMClient.ts +0 -149
- package/packages/core/src/agent/providers/anthropic.ts +0 -212
- package/packages/core/src/agent/providers/openai-compat.ts +0 -231
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
- package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
- package/packages/core/src/agent/selectors/index.ts +0 -6
- package/packages/core/src/auth/auth.ts +0 -121
- package/packages/core/src/auth/index.ts +0 -1
- package/packages/core/src/canvas/CanvasManager.ts +0 -390
- package/packages/core/src/canvas/canvas-tools.ts +0 -448
- package/packages/core/src/harness/collections.ts +0 -98
- package/packages/core/src/harness/goal-verifier.ts +0 -141
- package/packages/core/src/harness/harness.test.ts +0 -236
- package/packages/core/src/harness/reconcile.ts +0 -149
- package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
- package/packages/core/src/multimodal/VisionService.ts +0 -293
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
- package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
- package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
- package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
- package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
- package/packages/core/src/scheduler/dag/errors.ts +0 -37
- package/packages/core/src/scheduler/dag/index.ts +0 -26
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
- package/packages/core/src/storage/HiveDBStorage.ts +0 -64
- package/packages/core/src/storage/SQLiteStorage.ts +0 -414
- package/packages/core/src/storage/hiveSeed.ts +0 -308
- package/packages/core/src/storage/hiveStorage.test.ts +0 -38
- package/packages/core/src/storage/schema.ts +0 -689
- package/packages/core/src/storage/storage.test.ts +0 -37
- package/packages/core/src/swarm/AgentBus.ts +0 -460
- package/packages/core/src/swarm/EventBus.ts +0 -169
- package/packages/core/src/swarm/WorkerPool.ts +0 -236
- package/packages/core/src/tools/bridge-events.ts +0 -26
- package/packages/core/src/tools/canvas/index.ts +0 -375
- package/packages/core/src/tools/codebridge/index.ts +0 -342
- package/packages/core/src/tools/meeting/index.ts +0 -353
- package/packages/core/src/tools/projects/index.ts +0 -37
- package/packages/core/src/tools/projects/project-create.ts +0 -94
- package/packages/core/src/tools/projects/project-done.ts +0 -66
- package/packages/core/src/tools/projects/project-fail.ts +0 -66
- package/packages/core/src/tools/projects/project-list.ts +0 -96
- package/packages/core/src/tools/projects/project-update.ts +0 -72
- package/packages/core/src/tools/projects/task-create.ts +0 -68
- package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
- package/packages/core/src/tools/projects/task-update.ts +0 -93
- package/packages/core/src/tools/voice/index.ts +0 -104
- package/packages/core/src/tools/web/api-request.test.ts +0 -170
- package/packages/core/src/tools/web/api-request.ts +0 -239
- package/test/setup-db.ts +0 -216
- /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import { getDb } from "../storage/SQLiteStorage.ts"
|
|
2
|
-
import { decryptApiKey } from "../storage/crypto.ts"
|
|
3
|
-
import { logger } from "../utils/logger.ts"
|
|
4
|
-
import type { ImageInput, DocumentInput, VisionConfig } from "./types"
|
|
5
|
-
import type { ContentPart } from "./types"
|
|
6
|
-
|
|
7
|
-
const log = logger.child("multimodal")
|
|
8
|
-
|
|
9
|
-
class MultimodalService {
|
|
10
|
-
private static instance: MultimodalService
|
|
11
|
-
|
|
12
|
-
private constructor() {}
|
|
13
|
-
|
|
14
|
-
static getInstance(): MultimodalService {
|
|
15
|
-
if (!MultimodalService.instance) {
|
|
16
|
-
MultimodalService.instance = new MultimodalService()
|
|
17
|
-
}
|
|
18
|
-
return MultimodalService.instance
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getChannelVisionConfig(channelId: string): VisionConfig {
|
|
22
|
-
const db = getDb()
|
|
23
|
-
const result = db.query(`
|
|
24
|
-
SELECT vision_enabled, ocr_provider, vision_provider, vision_model_id
|
|
25
|
-
FROM channels WHERE id = ?
|
|
26
|
-
`).get(channelId) as {
|
|
27
|
-
vision_enabled: number
|
|
28
|
-
ocr_provider: string | null
|
|
29
|
-
vision_provider: string | null
|
|
30
|
-
vision_model_id: string | null
|
|
31
|
-
} | undefined
|
|
32
|
-
|
|
33
|
-
if (!result) {
|
|
34
|
-
return { visionEnabled: false, ocrProvider: null, visionProvider: null, visionModelId: null }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
visionEnabled: result.vision_enabled === 1,
|
|
39
|
-
ocrProvider: result.ocr_provider,
|
|
40
|
-
visionProvider: result.vision_provider,
|
|
41
|
-
visionModelId: result.vision_model_id,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async processImage(image: ImageInput, visionModelId?: string): Promise<ContentPart[]> {
|
|
46
|
-
const parts: ContentPart[] = []
|
|
47
|
-
|
|
48
|
-
if (image.caption) {
|
|
49
|
-
parts.push({ type: "text", text: image.caption })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (image.type === "url") {
|
|
53
|
-
parts.push({ type: "image_url", image_url: { url: image.data as string } })
|
|
54
|
-
} else if (image.type === "base64") {
|
|
55
|
-
parts.push({
|
|
56
|
-
type: "image_base64",
|
|
57
|
-
base64: image.data as string,
|
|
58
|
-
mimeType: image.mimeType || "image/jpeg",
|
|
59
|
-
})
|
|
60
|
-
} else if (image.type === "buffer") {
|
|
61
|
-
const base64 = Buffer.from(image.data as Buffer).toString("base64")
|
|
62
|
-
parts.push({
|
|
63
|
-
type: "image_base64",
|
|
64
|
-
base64,
|
|
65
|
-
mimeType: image.mimeType || "image/jpeg",
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return parts
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async ocrImage(image: ImageInput, providerId?: string): Promise<string> {
|
|
73
|
-
const resolved = providerId || "openai"
|
|
74
|
-
|
|
75
|
-
if (resolved === "openai") {
|
|
76
|
-
return this.ocrWithOpenAI(image)
|
|
77
|
-
} else if (resolved === "gemini") {
|
|
78
|
-
return this.ocrWithGemini(image)
|
|
79
|
-
} else if (resolved === "anthropic") {
|
|
80
|
-
return this.ocrWithAnthropic(image)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
log.warn(`Unknown OCR provider ${resolved}, defaulting to OpenAI`)
|
|
84
|
-
return this.ocrWithOpenAI(image)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
normalizeImageFromChannel(channelType: string, imageData: unknown): ImageInput {
|
|
88
|
-
const data = imageData as { url?: string; base64?: string; buffer?: Buffer; mimeType?: string; caption?: string }
|
|
89
|
-
|
|
90
|
-
if (data.url) {
|
|
91
|
-
return { type: "url", data: data.url, mimeType: data.mimeType, caption: data.caption }
|
|
92
|
-
}
|
|
93
|
-
if (data.base64) {
|
|
94
|
-
return { type: "base64", data: data.base64, mimeType: data.mimeType || "image/jpeg", caption: data.caption }
|
|
95
|
-
}
|
|
96
|
-
if (data.buffer) {
|
|
97
|
-
return { type: "buffer", data: data.buffer, mimeType: data.mimeType || "image/jpeg", caption: data.caption }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
throw new Error(`${channelType} image missing url, base64, or buffer`)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
normalizeDocumentFromChannel(channelType: string, docData: unknown): DocumentInput {
|
|
104
|
-
const data = docData as { url?: string; base64?: string; buffer?: Buffer; mimeType?: string; fileName?: string }
|
|
105
|
-
|
|
106
|
-
if (data.url) {
|
|
107
|
-
return { type: "url", data: data.url, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
|
|
108
|
-
}
|
|
109
|
-
if (data.base64) {
|
|
110
|
-
return { type: "base64", data: data.base64, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
|
|
111
|
-
}
|
|
112
|
-
if (data.buffer) {
|
|
113
|
-
return { type: "buffer", data: data.buffer, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
throw new Error(`${channelType} document missing url, base64, or buffer`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async resolveImageUrl(image: ImageInput): Promise<string> {
|
|
120
|
-
if (image.type === "url") return image.data as string
|
|
121
|
-
if (image.type === "base64") {
|
|
122
|
-
const mime = image.mimeType || "image/jpeg"
|
|
123
|
-
return `data:${mime};base64,${image.data as string}`
|
|
124
|
-
}
|
|
125
|
-
if (image.type === "buffer") {
|
|
126
|
-
const base64 = Buffer.from(image.data as Buffer).toString("base64")
|
|
127
|
-
const mime = image.mimeType || "image/jpeg"
|
|
128
|
-
return `data:${mime};base64,${base64}`
|
|
129
|
-
}
|
|
130
|
-
throw new Error("Cannot resolve image URL")
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private async getProviderApiKey(providerId: string): Promise<string | null> {
|
|
134
|
-
const db = getDb()
|
|
135
|
-
const provider = db.query(`
|
|
136
|
-
SELECT api_key_encrypted, api_key_iv FROM providers WHERE id = ?
|
|
137
|
-
`).get(providerId) as { api_key_encrypted: string; api_key_iv: string } | undefined
|
|
138
|
-
|
|
139
|
-
if (!provider?.api_key_encrypted) return null
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
return await decryptApiKey(provider.api_key_encrypted, provider.api_key_iv)
|
|
143
|
-
} catch (error) {
|
|
144
|
-
log.error(`Failed to decrypt API key for provider ${providerId}: ${(error as Error).message}`)
|
|
145
|
-
return null
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private async ocrWithOpenAI(image: ImageInput): Promise<string> {
|
|
150
|
-
const key = await this.getProviderApiKey("openai") || process.env.OPENAI_API_KEY
|
|
151
|
-
if (!key) throw new Error("OPENAI_API_KEY not configured for OCR")
|
|
152
|
-
|
|
153
|
-
const imageUrl = await this.resolveImageUrl(image)
|
|
154
|
-
|
|
155
|
-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
156
|
-
method: "POST",
|
|
157
|
-
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
|
|
158
|
-
body: JSON.stringify({
|
|
159
|
-
model: "gpt-4o-mini",
|
|
160
|
-
messages: [{
|
|
161
|
-
role: "user",
|
|
162
|
-
content: [
|
|
163
|
-
{ type: "text", text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." },
|
|
164
|
-
{ type: "image_url", image_url: { url: imageUrl } },
|
|
165
|
-
],
|
|
166
|
-
}],
|
|
167
|
-
max_tokens: 1000,
|
|
168
|
-
}),
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
if (!response.ok) {
|
|
172
|
-
const error = await response.text()
|
|
173
|
-
throw new Error(`OpenAI OCR failed: ${error}`)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const data = await response.json() as { choices: Array<{ message: { content: string } }> }
|
|
177
|
-
return data.choices[0]?.message?.content || ""
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private async ocrWithGemini(image: ImageInput): Promise<string> {
|
|
181
|
-
const key = await this.getProviderApiKey("gemini") || process.env.GEMINI_API_KEY
|
|
182
|
-
if (!key) throw new Error("GEMINI_API_KEY not configured for OCR")
|
|
183
|
-
|
|
184
|
-
let imagePart: any
|
|
185
|
-
if (image.type === "url") {
|
|
186
|
-
const imgResponse = await fetch(image.data as string)
|
|
187
|
-
const buffer = Buffer.from(await imgResponse.arrayBuffer())
|
|
188
|
-
imagePart = { inlineData: { data: buffer.toString("base64"), mimeType: image.mimeType || "image/jpeg" } }
|
|
189
|
-
} else if (image.type === "base64") {
|
|
190
|
-
imagePart = { inlineData: { data: image.data as string, mimeType: image.mimeType || "image/jpeg" } }
|
|
191
|
-
} else {
|
|
192
|
-
imagePart = { inlineData: { data: Buffer.from(image.data as Buffer).toString("base64"), mimeType: image.mimeType || "image/jpeg" } }
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const response = await fetch(
|
|
196
|
-
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${key}`,
|
|
197
|
-
{
|
|
198
|
-
method: "POST",
|
|
199
|
-
headers: { "Content-Type": "application/json" },
|
|
200
|
-
body: JSON.stringify({
|
|
201
|
-
contents: [{ parts: [{ text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." }, imagePart] }],
|
|
202
|
-
}),
|
|
203
|
-
},
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
if (!response.ok) {
|
|
207
|
-
const error = await response.text()
|
|
208
|
-
throw new Error(`Gemini OCR failed: ${error}`)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const data = await response.json() as { candidates: Array<{ content: { parts: Array<{ text?: string }> } }> }
|
|
212
|
-
return data.candidates?.[0]?.content?.parts?.[0]?.text || ""
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private async ocrWithAnthropic(image: ImageInput): Promise<string> {
|
|
216
|
-
const key = await this.getProviderApiKey("anthropic") || process.env.ANTHROPIC_API_KEY
|
|
217
|
-
if (!key) throw new Error("ANTHROPIC_API_KEY not configured for OCR")
|
|
218
|
-
|
|
219
|
-
const imageUrl = await this.resolveImageUrl(image)
|
|
220
|
-
|
|
221
|
-
let source: any
|
|
222
|
-
if (imageUrl.startsWith("data:")) {
|
|
223
|
-
const match = imageUrl.match(/^data:([^;]+);base64,(.+)$/)
|
|
224
|
-
if (match) {
|
|
225
|
-
source = { type: "base64", media_type: match[1], data: match[2] }
|
|
226
|
-
} else {
|
|
227
|
-
throw new Error("Invalid base64 data URL")
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
source = { type: "url", url: imageUrl }
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
234
|
-
method: "POST",
|
|
235
|
-
headers: {
|
|
236
|
-
"Content-Type": "application/json",
|
|
237
|
-
"x-api-key": key,
|
|
238
|
-
"anthropic-version": "2023-06-01",
|
|
239
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
240
|
-
},
|
|
241
|
-
body: JSON.stringify({
|
|
242
|
-
model: "claude-haiku-4-5-20251001",
|
|
243
|
-
max_tokens: 1000,
|
|
244
|
-
messages: [{
|
|
245
|
-
role: "user",
|
|
246
|
-
content: [
|
|
247
|
-
{ type: "image", source },
|
|
248
|
-
{ type: "text", text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." },
|
|
249
|
-
],
|
|
250
|
-
}],
|
|
251
|
-
}),
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
const error = await response.text()
|
|
256
|
-
throw new Error(`Anthropic OCR failed: ${error}`)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const data = await response.json() as { content: Array<{ type: string; text?: string }> }
|
|
260
|
-
const textBlock = data.content?.find(b => b.type === "text" && b.text)
|
|
261
|
-
return textBlock?.text || ""
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
getConfiguredVisionProviders(): Record<string, boolean> {
|
|
265
|
-
const db = getDb()
|
|
266
|
-
const hasDbKey = (providerId: string): boolean => {
|
|
267
|
-
const row = db.query(
|
|
268
|
-
`SELECT api_key_encrypted FROM providers WHERE id = ? AND api_key_encrypted IS NOT NULL AND api_key_encrypted != ''`
|
|
269
|
-
).get(providerId) as { api_key_encrypted: string } | undefined
|
|
270
|
-
return !!row
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
openai: hasDbKey("openai") || !!(process.env.OPENAI_API_KEY),
|
|
275
|
-
gemini: hasDbKey("gemini") || !!(process.env.GEMINI_API_KEY),
|
|
276
|
-
anthropic: hasDbKey("anthropic") || !!(process.env.ANTHROPIC_API_KEY),
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
modelSupportsVision(providerId: string, modelId: string): boolean {
|
|
281
|
-
const db = getDb()
|
|
282
|
-
const model = db.query(`SELECT capabilities FROM models WHERE id = ? AND provider_id = ?`).get(modelId, providerId) as { capabilities: string } | undefined
|
|
283
|
-
if (!model?.capabilities) return false
|
|
284
|
-
try {
|
|
285
|
-
const caps = JSON.parse(model.capabilities) as string[]
|
|
286
|
-
return caps.includes("vision")
|
|
287
|
-
} catch {
|
|
288
|
-
return false
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export const multimodalService = MultimodalService.getInstance()
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DAGScheduler — AgentExecutor
|
|
3
|
-
*
|
|
4
|
-
* Bridges the DAGScheduler to the existing runAgentIsolated() call.
|
|
5
|
-
* Adds timeout enforcement via Promise.race().
|
|
6
|
-
*
|
|
7
|
-
* NOTE: There are no Bun Worker threads in Hive OSS. "Workers" are logical
|
|
8
|
-
* agents stored in the DB and executed as async calls in the same process.
|
|
9
|
-
* Parallelism is achieved by launching multiple runAgentIsolated() calls
|
|
10
|
-
* concurrently without awaiting each one serially.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { runAgentIsolated } from "../../agent/AgentRunner"
|
|
14
|
-
import { TaskNode } from "./TaskNode"
|
|
15
|
-
import { TaskTimeoutError } from "./errors"
|
|
16
|
-
|
|
17
|
-
export class AgentExecutor {
|
|
18
|
-
/**
|
|
19
|
-
* Execute a TaskNode.
|
|
20
|
-
* Injects dependency results into the task description as context.
|
|
21
|
-
* Returns the final text output from the agent.
|
|
22
|
-
*/
|
|
23
|
-
async execute(
|
|
24
|
-
node: TaskNode,
|
|
25
|
-
depResults: Record<string, string>,
|
|
26
|
-
threadId: string
|
|
27
|
-
): Promise<string> {
|
|
28
|
-
const hasDeps = Object.keys(depResults).length > 0
|
|
29
|
-
const contextBlock = hasDeps
|
|
30
|
-
? `\n\n---\nContext from completed dependencies:\n${JSON.stringify(depResults, null, 2)}\n---`
|
|
31
|
-
: ""
|
|
32
|
-
|
|
33
|
-
const taskDescription = node.taskDescription + contextBlock
|
|
34
|
-
|
|
35
|
-
const agentPromise = runAgentIsolated({
|
|
36
|
-
agentId: node.agentId,
|
|
37
|
-
taskDescription,
|
|
38
|
-
threadId,
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
42
|
-
const t = setTimeout(() => {
|
|
43
|
-
reject(new TaskTimeoutError(node.id, node.timeout))
|
|
44
|
-
}, node.timeout)
|
|
45
|
-
// Ensure the timeout timer doesn't prevent process exit
|
|
46
|
-
if (typeof t === "object" && t !== null && "unref" in t) {
|
|
47
|
-
(t as any).unref()
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
return Promise.race([agentPromise, timeoutPromise])
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
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"
|
|
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
|
-
|
|
72
|
-
const bridge = new EventBridge(swarmId, projectId, coordinatorId)
|
|
73
|
-
|
|
74
|
-
// Allow strategy to precompute (e.g. critical path)
|
|
75
|
-
if (this.strategy.initialize) {
|
|
76
|
-
this.strategy.initialize(graph.nodes)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
bridge.onSwarmStarted(graph.nodes.size)
|
|
80
|
-
this.logState(swarmId, graph, startedAt, silent, swarmId)
|
|
81
|
-
|
|
82
|
-
// Seed the READY queue with nodes that have no dependencies
|
|
83
|
-
const readyQueue: TaskNode[] = []
|
|
84
|
-
const completedIds = graph.getCompletedIds()
|
|
85
|
-
|
|
86
|
-
for (const node of graph.nodes.values()) {
|
|
87
|
-
if (node.deps.length === 0) {
|
|
88
|
-
node.markReady()
|
|
89
|
-
readyQueue.push(node)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Active promise set — we track them with a wrapper so we can drain
|
|
94
|
-
const running = new Set<Promise<void>>()
|
|
95
|
-
|
|
96
|
-
const launchNode = (node: TaskNode): void => {
|
|
97
|
-
if (this.aborted) return
|
|
98
|
-
|
|
99
|
-
node.markRunning()
|
|
100
|
-
bridge.onTaskStarted(node)
|
|
101
|
-
this.logState(swarmId, graph, startedAt, silent, swarmId)
|
|
102
|
-
|
|
103
|
-
const depResults = graph.getDepResults(node.id)
|
|
104
|
-
const threadId = `dag-${swarmId}-${node.id}`
|
|
105
|
-
|
|
106
|
-
const p: Promise<void> = this.executor
|
|
107
|
-
.execute(node, depResults, threadId)
|
|
108
|
-
.then(result => {
|
|
109
|
-
node.markCompleted(result)
|
|
110
|
-
log.info(`[DAG] ${node.name} COMPLETED in ${node.elapsedSeconds()}s`)
|
|
111
|
-
bridge.onTaskCompleted(node, graph.getProgress())
|
|
112
|
-
this.logState(swarmId, graph, startedAt, silent, swarmId)
|
|
113
|
-
|
|
114
|
-
// Unlock dependent nodes
|
|
115
|
-
const newlyReady = graph.getNewlyReadyNodes(graph.getCompletedIds())
|
|
116
|
-
for (const n of newlyReady) {
|
|
117
|
-
n.markReady()
|
|
118
|
-
readyQueue.push(n)
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.catch(err => {
|
|
122
|
-
const error = err instanceof Error ? err.message : String(err)
|
|
123
|
-
|
|
124
|
-
if (node.canRetry()) {
|
|
125
|
-
node.retryCount++
|
|
126
|
-
log.warn(`[DAG] ${node.name} failed (retry ${node.retryCount}/${node.maxRetries}): ${error}`)
|
|
127
|
-
node.status = "PENDING"
|
|
128
|
-
node.markReady()
|
|
129
|
-
readyQueue.push(node)
|
|
130
|
-
} else {
|
|
131
|
-
node.markFailed(error)
|
|
132
|
-
log.error(`[DAG] ${node.name} FAILED permanently: ${error}`)
|
|
133
|
-
bridge.onTaskFailed(node, graph.getProgress())
|
|
134
|
-
graph.propagateFailure(node.id, error)
|
|
135
|
-
this.logState(swarmId, graph, startedAt, silent, swarmId)
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
.finally(() => {
|
|
139
|
-
running.delete(p)
|
|
140
|
-
drain()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
running.add(p)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Drain the ready queue into available worker slots
|
|
147
|
-
const drain = (): void => {
|
|
148
|
-
while (readyQueue.length > 0 && running.size < this.maxConcurrentWorkers && !this.aborted) {
|
|
149
|
-
const node = this.strategy.pick(readyQueue)
|
|
150
|
-
if (!node) break
|
|
151
|
-
launchNode(node)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Start initial drain
|
|
156
|
-
drain()
|
|
157
|
-
|
|
158
|
-
// Wait until the graph is complete
|
|
159
|
-
while (!graph.isComplete() && !this.aborted) {
|
|
160
|
-
if (running.size === 0 && readyQueue.length === 0) {
|
|
161
|
-
// Deadlock guard: no running, nothing ready, but graph not done
|
|
162
|
-
// This can happen if all remaining nodes are FAILED
|
|
163
|
-
break
|
|
164
|
-
}
|
|
165
|
-
// Wait for any active promise to settle
|
|
166
|
-
if (running.size > 0) {
|
|
167
|
-
await Promise.race([...running])
|
|
168
|
-
drain()
|
|
169
|
-
} else {
|
|
170
|
-
// Brief yield to let microtasks settle
|
|
171
|
-
await new Promise(resolve => setTimeout(resolve, 10))
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Collect results
|
|
176
|
-
const completed: NodeSummary[] = []
|
|
177
|
-
const failed: NodeSummary[] = []
|
|
178
|
-
|
|
179
|
-
for (const node of graph.nodes.values()) {
|
|
180
|
-
const summary: NodeSummary = {
|
|
181
|
-
id: node.id,
|
|
182
|
-
name: node.name,
|
|
183
|
-
status: node.status === "COMPLETED" ? "COMPLETED" : "FAILED",
|
|
184
|
-
durationMs: node.startedAt ? (node.completedAt ?? Date.now()) - node.startedAt : 0,
|
|
185
|
-
result: node.result,
|
|
186
|
-
error: node.error,
|
|
187
|
-
retries: node.retryCount,
|
|
188
|
-
}
|
|
189
|
-
if (node.status === "COMPLETED") completed.push(summary)
|
|
190
|
-
else failed.push(summary)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const result: DAGResult = {
|
|
194
|
-
swarmId,
|
|
195
|
-
totalDurationMs: Date.now() - startedAt,
|
|
196
|
-
completed,
|
|
197
|
-
failed,
|
|
198
|
-
success: failed.length === 0,
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
bridge.onSwarmCompleted(result)
|
|
202
|
-
this.logState(swarmId, graph, startedAt, silent, swarmId)
|
|
203
|
-
|
|
204
|
-
log.info(`[DAG] swarm ${swarmId} finished. ${completed.length} completed, ${failed.length} failed. Total: ${Math.round(result.totalDurationMs / 1000)}s`)
|
|
205
|
-
|
|
206
|
-
return result
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ─── ASCII log ───────────────────────────────────────────────────────────────
|
|
210
|
-
|
|
211
|
-
private logState(
|
|
212
|
-
swarmId: string,
|
|
213
|
-
graph: TaskGraph,
|
|
214
|
-
startedAt: number,
|
|
215
|
-
silent: boolean,
|
|
216
|
-
sessionId: string
|
|
217
|
-
): void {
|
|
218
|
-
const elapsed = Math.round((Date.now() - startedAt) / 1000)
|
|
219
|
-
const lines: string[] = [`[DAG] swarm:${swarmId.slice(0, 8)} T+${elapsed}s`]
|
|
220
|
-
|
|
221
|
-
for (const node of graph.nodes.values()) {
|
|
222
|
-
const icon =
|
|
223
|
-
node.status === "COMPLETED" ? "✓" :
|
|
224
|
-
node.status === "FAILED" ? "✗" :
|
|
225
|
-
node.status === "RUNNING" ? "●" : "○"
|
|
226
|
-
|
|
227
|
-
const depStr = node.deps.length > 0 ? ` (deps: ${node.deps.join(", ")})` : ""
|
|
228
|
-
const timeStr = node.startedAt ? ` (${node.elapsedSeconds()}s)` : ""
|
|
229
|
-
const statusLabel = node.status.padEnd(10)
|
|
230
|
-
|
|
231
|
-
lines.push(` ${icon} ${node.name.padEnd(24)} ${statusLabel}${timeStr}${depStr}`)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const output = lines.join("\n")
|
|
235
|
-
|
|
236
|
-
if (!silent) {
|
|
237
|
-
// Write to log file (never committed — in .gitignore)
|
|
238
|
-
try {
|
|
239
|
-
const logDir = path.join(process.cwd(), "packages", "core", "logs")
|
|
240
|
-
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true })
|
|
241
|
-
const logFile = path.join(logDir, `dag-${sessionId.slice(0, 8)}.log`)
|
|
242
|
-
writeFileSync(logFile, output + "\n\n", { flag: "a" })
|
|
243
|
-
} catch {
|
|
244
|
-
// Non-critical — never throw for logging
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
log.debug(output)
|
|
249
|
-
}
|
|
250
|
-
}
|