@bahulam/code 2.6.0
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/README.md +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Agent Workflow Loader — loads multi-agent workflow YAML files
|
|
3
|
+
* from .bahulam/workflows/ and produces API payloads for POST /api/workflows.
|
|
4
|
+
*
|
|
5
|
+
* Multi-agent workflow YAML schema:
|
|
6
|
+
*
|
|
7
|
+
* ```yaml
|
|
8
|
+
* metadata:
|
|
9
|
+
* name: Code Review Pipeline
|
|
10
|
+
* description: Sequential review by specialist agents
|
|
11
|
+
*
|
|
12
|
+
* orchestration:
|
|
13
|
+
* pattern: sequential # sequential | parallel
|
|
14
|
+
* instruction: "Review the PR"
|
|
15
|
+
*
|
|
16
|
+
* agents:
|
|
17
|
+
* - slug: code-explorer
|
|
18
|
+
* label: Explore Codebase
|
|
19
|
+
* config:
|
|
20
|
+
* depth: full
|
|
21
|
+
* - slug: security-reviewer
|
|
22
|
+
* label: Security Audit
|
|
23
|
+
* config: {}
|
|
24
|
+
*
|
|
25
|
+
* edges:
|
|
26
|
+
* - source: trigger
|
|
27
|
+
* target: code-explorer
|
|
28
|
+
* - source: code-explorer
|
|
29
|
+
* target: security-reviewer
|
|
30
|
+
* - source: security-reviewer
|
|
31
|
+
* target: output
|
|
32
|
+
*
|
|
33
|
+
* global_params:
|
|
34
|
+
* repo_url: ""
|
|
35
|
+
* pr_number: ""
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The loader converts this into a graph structure matching the canvas
|
|
39
|
+
* format: { nodes: [...], edges: [...] } with node types trigger/agent/output.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import fs from 'fs';
|
|
43
|
+
import path from 'path';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Load a multi-agent workflow YAML file and return an API-ready payload.
|
|
47
|
+
* @param {string} filePath - absolute path to .yaml file
|
|
48
|
+
* @returns {object} payload ready for POST /api/workflows
|
|
49
|
+
*/
|
|
50
|
+
export function loadMultiWorkflowFromFile(filePath) {
|
|
51
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
52
|
+
if (!['.yaml', '.yml'].includes(ext)) {
|
|
53
|
+
throw new Error(`Unsupported workflow format: ${ext}. Use .yaml or .yml`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
const parsed = parseWorkflowYaml(content);
|
|
58
|
+
|
|
59
|
+
if (!parsed) {
|
|
60
|
+
throw new Error('Empty or invalid YAML file');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return multiWorkflowToApiPayload(parsed, filePath);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Convert a parsed multi-agent workflow YAML into the API payload.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} parsed - parsed YAML content
|
|
70
|
+
* @param {string} sourcePath - file path for slug derivation
|
|
71
|
+
* @returns {object} { name, description, graph, global_params, pattern }
|
|
72
|
+
*/
|
|
73
|
+
export function multiWorkflowToApiPayload(parsed, sourcePath) {
|
|
74
|
+
const metadata = parsed.metadata || {};
|
|
75
|
+
const orchestration = parsed.orchestration || {};
|
|
76
|
+
const agents = parsed.agents || [];
|
|
77
|
+
const edges = parsed.edges || [];
|
|
78
|
+
const globalParams = parsed.global_params || {};
|
|
79
|
+
|
|
80
|
+
// Validate
|
|
81
|
+
if (!metadata.name && !sourcePath) {
|
|
82
|
+
throw new Error('Workflow must have a name in metadata or a filename');
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(agents) || agents.length === 0) {
|
|
85
|
+
throw new Error('Workflow must define at least one agent in the agents list');
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(edges) || edges.length === 0) {
|
|
88
|
+
throw new Error('Workflow must define at least one edge connecting nodes');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const name = metadata.name || path.basename(sourcePath, path.extname(sourcePath));
|
|
92
|
+
const description = metadata.description || '';
|
|
93
|
+
|
|
94
|
+
// Build graph nodes
|
|
95
|
+
// 1. Trigger node
|
|
96
|
+
const nodes = [
|
|
97
|
+
{
|
|
98
|
+
id: 'trigger',
|
|
99
|
+
type: 'trigger',
|
|
100
|
+
position: { x: 0, y: 120 },
|
|
101
|
+
data: {
|
|
102
|
+
label: 'Manual Run',
|
|
103
|
+
icon: 'trigger',
|
|
104
|
+
status: 'idle',
|
|
105
|
+
nodeType: 'trigger',
|
|
106
|
+
triggerType: 'manual',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// 2. Agent nodes from the agents list
|
|
112
|
+
for (const agent of agents) {
|
|
113
|
+
const slug = agent.slug || agent.name || 'agent';
|
|
114
|
+
const label = agent.label || slug;
|
|
115
|
+
// Position agents in a vertical column or horizontal row
|
|
116
|
+
const index = agents.indexOf(agent);
|
|
117
|
+
nodes.push({
|
|
118
|
+
id: slug,
|
|
119
|
+
type: 'agent',
|
|
120
|
+
position: { x: 250, y: 120 + index * 160 },
|
|
121
|
+
data: {
|
|
122
|
+
label,
|
|
123
|
+
subtitle: '',
|
|
124
|
+
icon: 'agent',
|
|
125
|
+
status: 'idle',
|
|
126
|
+
nodeType: 'agent',
|
|
127
|
+
agent_source: 'user',
|
|
128
|
+
agent_slug: slug,
|
|
129
|
+
user_agent_slug: slug,
|
|
130
|
+
model: agent.model || 'auto',
|
|
131
|
+
tools: agent.tools || [],
|
|
132
|
+
config: {
|
|
133
|
+
...(agent.config || {}),
|
|
134
|
+
agent_slug: slug,
|
|
135
|
+
user_agent_slug: slug,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 3. Output node
|
|
142
|
+
nodes.push({
|
|
143
|
+
id: 'output',
|
|
144
|
+
type: 'output',
|
|
145
|
+
position: { x: 500, y: 120 + (agents.length - 1) * 80 },
|
|
146
|
+
data: {
|
|
147
|
+
label: 'Results',
|
|
148
|
+
icon: 'review',
|
|
149
|
+
status: 'idle',
|
|
150
|
+
nodeType: 'output',
|
|
151
|
+
outputType: 'display',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Build graph edges
|
|
156
|
+
// Use explicit edges from YAML, or auto-wire: trigger → first agent → ... → last agent → output
|
|
157
|
+
let graphEdges;
|
|
158
|
+
if (edges.length > 0) {
|
|
159
|
+
graphEdges = edges.map((e, i) => ({
|
|
160
|
+
id: `e-${i}`,
|
|
161
|
+
source: e.source,
|
|
162
|
+
target: e.target,
|
|
163
|
+
}));
|
|
164
|
+
} else {
|
|
165
|
+
// Auto-wire sequential
|
|
166
|
+
graphEdges = [];
|
|
167
|
+
const agentIds = agents.map(a => a.slug || a.name || 'agent');
|
|
168
|
+
graphEdges.push({ id: 'e-trigger-first', source: 'trigger', target: agentIds[0] });
|
|
169
|
+
for (let i = 0; i < agentIds.length - 1; i++) {
|
|
170
|
+
graphEdges.push({ id: `e-${i}-${i + 1}`, source: agentIds[i], target: agentIds[i + 1] });
|
|
171
|
+
}
|
|
172
|
+
graphEdges.push({ id: 'e-last-output', source: agentIds[agentIds.length - 1], target: 'output' });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const graph = {
|
|
176
|
+
nodes,
|
|
177
|
+
edges: graphEdges,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const pattern = orchestration.pattern || 'sequential';
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
name,
|
|
184
|
+
description,
|
|
185
|
+
graph,
|
|
186
|
+
global_params: globalParams,
|
|
187
|
+
orchestration_pattern: pattern,
|
|
188
|
+
pattern,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function stripComment(line) {
|
|
193
|
+
let quote = null;
|
|
194
|
+
for (let i = 0; i < line.length; i++) {
|
|
195
|
+
const ch = line[i];
|
|
196
|
+
if ((ch === '"' || ch === "'") && line[i - 1] !== '\\') {
|
|
197
|
+
quote = quote === ch ? null : quote || ch;
|
|
198
|
+
}
|
|
199
|
+
if (ch === '#' && !quote) return line.slice(0, i);
|
|
200
|
+
}
|
|
201
|
+
return line;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseScalar(value) {
|
|
205
|
+
const raw = stripComment(String(value || '')).trim();
|
|
206
|
+
if (raw === '{}') return {};
|
|
207
|
+
if (raw === '[]') return [];
|
|
208
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
209
|
+
return raw.slice(1, -1);
|
|
210
|
+
}
|
|
211
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
212
|
+
return raw.slice(1, -1).split(',').map(s => parseScalar(s)).filter(v => v !== '');
|
|
213
|
+
}
|
|
214
|
+
if (raw === 'true') return true;
|
|
215
|
+
if (raw === 'false') return false;
|
|
216
|
+
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
|
|
217
|
+
return raw;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function assignNested(target, key, value) {
|
|
221
|
+
if (key.includes('.')) {
|
|
222
|
+
const parts = key.split('.');
|
|
223
|
+
let cursor = target;
|
|
224
|
+
for (const part of parts.slice(0, -1)) {
|
|
225
|
+
cursor[part] ||= {};
|
|
226
|
+
cursor = cursor[part];
|
|
227
|
+
}
|
|
228
|
+
cursor[parts[parts.length - 1]] = value;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
target[key] = value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseWorkflowYaml(content) {
|
|
235
|
+
const root = {};
|
|
236
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
237
|
+
let section = null;
|
|
238
|
+
let currentItem = null;
|
|
239
|
+
let currentNested = null;
|
|
240
|
+
|
|
241
|
+
for (const originalLine of lines) {
|
|
242
|
+
if (!originalLine.trim() || originalLine.trimStart().startsWith('#')) continue;
|
|
243
|
+
const line = stripComment(originalLine);
|
|
244
|
+
if (!line.trim()) continue;
|
|
245
|
+
|
|
246
|
+
const top = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
247
|
+
if (top) {
|
|
248
|
+
const key = top[1];
|
|
249
|
+
const value = top[2].trim();
|
|
250
|
+
section = key;
|
|
251
|
+
currentItem = null;
|
|
252
|
+
currentNested = null;
|
|
253
|
+
if (value) root[key] = parseScalar(value);
|
|
254
|
+
else root[key] = key === 'agents' || key === 'edges' ? [] : {};
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!section) continue;
|
|
259
|
+
|
|
260
|
+
const listItem = line.match(/^\s{2}-\s*([^:]+):\s*(.*)$/);
|
|
261
|
+
if (listItem && Array.isArray(root[section])) {
|
|
262
|
+
currentItem = {};
|
|
263
|
+
currentNested = null;
|
|
264
|
+
assignNested(currentItem, listItem[1].trim(), parseScalar(listItem[2]));
|
|
265
|
+
root[section].push(currentItem);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const listScalar = line.match(/^\s{2}-\s*(.+)$/);
|
|
270
|
+
if (listScalar && Array.isArray(root[section])) {
|
|
271
|
+
root[section].push(parseScalar(listScalar[1]));
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const nestedField = line.match(/^\s{4}([^:]+):\s*(.*)$/);
|
|
276
|
+
if (nestedField && currentNested) {
|
|
277
|
+
assignNested(currentNested, nestedField[1].trim(), parseScalar(nestedField[2]));
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const objectField = line.match(/^\s{2}([^:]+):\s*(.*)$/);
|
|
282
|
+
if (objectField) {
|
|
283
|
+
const key = objectField[1].trim();
|
|
284
|
+
const value = objectField[2].trim();
|
|
285
|
+
if (currentItem) {
|
|
286
|
+
if (!value) {
|
|
287
|
+
currentItem[key] = {};
|
|
288
|
+
currentNested = currentItem[key];
|
|
289
|
+
} else {
|
|
290
|
+
assignNested(currentItem, key, parseScalar(value));
|
|
291
|
+
currentNested = null;
|
|
292
|
+
}
|
|
293
|
+
} else if (typeof root[section] === 'object' && !Array.isArray(root[section])) {
|
|
294
|
+
assignNested(root[section], key, parseScalar(value));
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return root;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Scan a directory for multi-agent workflow YAML files.
|
|
305
|
+
* @param {string} dir - directory path
|
|
306
|
+
* @returns {Array<{file: string, payload: object}>}
|
|
307
|
+
*/
|
|
308
|
+
export function loadMultiWorkflowsFromDir(dir) {
|
|
309
|
+
const results = [];
|
|
310
|
+
try {
|
|
311
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (!entry.isFile()) continue;
|
|
314
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
315
|
+
if (ext !== '.yaml' && ext !== '.yml') continue;
|
|
316
|
+
const filePath = path.join(dir, entry.name);
|
|
317
|
+
try {
|
|
318
|
+
const payload = loadMultiWorkflowFromFile(filePath);
|
|
319
|
+
results.push({ file: filePath, payload });
|
|
320
|
+
} catch (err) {
|
|
321
|
+
if (process.env.DEBUG) {
|
|
322
|
+
console.error(`[multi-workflow-loader] Failed to load ${filePath}: ${err.message}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
// Directory does not exist
|
|
328
|
+
}
|
|
329
|
+
return results;
|
|
330
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Parser — parses agent definitions from native YAML, JSON, and Markdown.
|
|
3
|
+
*
|
|
4
|
+
* JSON format:
|
|
5
|
+
* {
|
|
6
|
+
* "name": "my-agent",
|
|
7
|
+
* "description": "Does things",
|
|
8
|
+
* "model": "claude-sonnet-4-6",
|
|
9
|
+
* "tools": ["Bash", "Read", "Write"],
|
|
10
|
+
* "hooks": { ... },
|
|
11
|
+
* "prompt": "You are a specialized agent..."
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* Markdown format (YAML frontmatter):
|
|
15
|
+
* ---
|
|
16
|
+
* name: my-agent
|
|
17
|
+
* description: Does things
|
|
18
|
+
* model: claude-sonnet-4-6
|
|
19
|
+
* tools: [Bash, Read, Write]
|
|
20
|
+
* ---
|
|
21
|
+
* You are a specialized agent...
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse an agent definition from file content.
|
|
26
|
+
* @param {string} content - file content
|
|
27
|
+
* @param {string} ext - file extension (.yaml, .yml, .json, or .md)
|
|
28
|
+
* @returns {object} agent definition
|
|
29
|
+
*/
|
|
30
|
+
export function parseAgentDefinition(content, ext) {
|
|
31
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
32
|
+
return parseYamlAgent(content);
|
|
33
|
+
}
|
|
34
|
+
if (ext === '.json') {
|
|
35
|
+
return parseJsonAgent(content);
|
|
36
|
+
}
|
|
37
|
+
if (ext === '.md') {
|
|
38
|
+
return parseMarkdownAgent(content);
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Unsupported agent format: ${ext}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseJsonAgent(content) {
|
|
44
|
+
const data = JSON.parse(content);
|
|
45
|
+
return normalizeAgent(data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseMarkdownAgent(content) {
|
|
49
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
50
|
+
return normalizeAgent({ ...frontmatter, prompt: body });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseYamlAgent(content) {
|
|
54
|
+
const config = parseKeplerSubAgentYaml(content);
|
|
55
|
+
return normalizeAgent({
|
|
56
|
+
raw_config: config,
|
|
57
|
+
apiVersion: config.apiVersion,
|
|
58
|
+
kind: config.kind,
|
|
59
|
+
...(config.metadata || {}),
|
|
60
|
+
...(config.agent?.model ? { model: config.agent.model } : {}),
|
|
61
|
+
...(config.agent?.models ? { models: config.agent.models } : {}),
|
|
62
|
+
max_iterations: config.agent?.max_iterations,
|
|
63
|
+
max_tokens: config.agent?.max_tokens,
|
|
64
|
+
prompt: config.agent?.system_prompt || '',
|
|
65
|
+
tools: config.tools || [],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseScalar(value) {
|
|
70
|
+
const raw = String(value || '').trim();
|
|
71
|
+
if (raw.startsWith('"') && raw.endsWith('"')) return raw.slice(1, -1);
|
|
72
|
+
if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
73
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
74
|
+
return raw.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
if (raw === 'true') return true;
|
|
77
|
+
if (raw === 'false') return false;
|
|
78
|
+
if (/^\d+$/.test(raw)) return Number.parseInt(raw, 10);
|
|
79
|
+
return raw;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseKeplerSubAgentYaml(content) {
|
|
83
|
+
const root = {};
|
|
84
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
85
|
+
let section = root;
|
|
86
|
+
let sectionName = null;
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
const line = lines[i];
|
|
89
|
+
if (!line.trim() || line.trimStart().startsWith('#')) continue;
|
|
90
|
+
if (!line.startsWith(' ')) {
|
|
91
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
92
|
+
if (!match) continue;
|
|
93
|
+
const key = match[1].trim();
|
|
94
|
+
const value = match[2].trim();
|
|
95
|
+
if (value === '') {
|
|
96
|
+
root[key] = key === 'tools' ? [] : {};
|
|
97
|
+
section = root[key];
|
|
98
|
+
sectionName = key;
|
|
99
|
+
} else {
|
|
100
|
+
root[key] = parseScalar(value);
|
|
101
|
+
section = root;
|
|
102
|
+
sectionName = null;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (sectionName === 'tools') {
|
|
108
|
+
const item = line.trim().match(/^-\s*(.+)$/);
|
|
109
|
+
if (item) section.push(parseScalar(item[1]));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const nested = line.match(/^\s{2}([^:]+):\s*(.*)$/);
|
|
114
|
+
if (!nested || typeof section !== 'object' || Array.isArray(section)) continue;
|
|
115
|
+
const key = nested[1].trim();
|
|
116
|
+
const value = nested[2].trim();
|
|
117
|
+
if (value === '|') {
|
|
118
|
+
const block = [];
|
|
119
|
+
const baseIndent = (lines[i + 1]?.match(/^(\s*)/)?.[1]?.length ?? 4);
|
|
120
|
+
while (i + 1 < lines.length) {
|
|
121
|
+
const next = lines[i + 1];
|
|
122
|
+
if (!next.trim()) {
|
|
123
|
+
block.push('');
|
|
124
|
+
i++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const indent = next.match(/^(\s*)/)?.[1]?.length ?? 0;
|
|
128
|
+
if (indent < baseIndent) break;
|
|
129
|
+
block.push(next.slice(baseIndent));
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
section[key] = block.join('\n').trimEnd();
|
|
133
|
+
} else if (value === '') {
|
|
134
|
+
section[key] = {};
|
|
135
|
+
} else {
|
|
136
|
+
section[key] = parseScalar(value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return root;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse YAML-like frontmatter from markdown.
|
|
144
|
+
* Simple key-value parser (no full YAML dependency).
|
|
145
|
+
*/
|
|
146
|
+
function parseFrontmatter(content) {
|
|
147
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
148
|
+
if (!match) {
|
|
149
|
+
return { frontmatter: {}, body: content };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const yamlBlock = match[1];
|
|
153
|
+
const body = match[2].trim();
|
|
154
|
+
const frontmatter = {};
|
|
155
|
+
|
|
156
|
+
for (const line of yamlBlock.split('\n')) {
|
|
157
|
+
const colonIdx = line.indexOf(':');
|
|
158
|
+
if (colonIdx === -1) continue;
|
|
159
|
+
|
|
160
|
+
const key = line.slice(0, colonIdx).trim();
|
|
161
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
162
|
+
|
|
163
|
+
// Parse arrays: [a, b, c]
|
|
164
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
165
|
+
value = value.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
|
|
166
|
+
}
|
|
167
|
+
// Parse booleans
|
|
168
|
+
else if (value === 'true') value = true;
|
|
169
|
+
else if (value === 'false') value = false;
|
|
170
|
+
// Parse numbers
|
|
171
|
+
else if (/^\d+$/.test(value)) value = parseInt(value, 10);
|
|
172
|
+
|
|
173
|
+
frontmatter[key] = value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { frontmatter, body };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeAgent(data) {
|
|
180
|
+
const config = data.raw_config || null;
|
|
181
|
+
const metadata = config?.metadata || {};
|
|
182
|
+
const agent = config?.agent || {};
|
|
183
|
+
return {
|
|
184
|
+
slug: data.slug || data.id || metadata.slug || metadata.name || data.name || 'unnamed',
|
|
185
|
+
id: data.id || data.slug || metadata.slug || metadata.name || data.name || 'unnamed',
|
|
186
|
+
name: data.name || metadata.name || 'unnamed',
|
|
187
|
+
description: data.description || metadata.description || '',
|
|
188
|
+
role: data.role || metadata.role || 'specialist',
|
|
189
|
+
model: data.model || agent.model || null,
|
|
190
|
+
models: data.models && typeof data.models === 'object' ? data.models : {},
|
|
191
|
+
tools: Array.isArray(data.tools) ? data.tools : Array.isArray(config?.tools) ? config.tools : [],
|
|
192
|
+
capabilities: Array.isArray(data.capabilities) ? data.capabilities : Array.isArray(metadata.capabilities) ? metadata.capabilities : [],
|
|
193
|
+
domains: Array.isArray(data.domains) ? data.domains : Array.isArray(metadata.domains) ? metadata.domains : [],
|
|
194
|
+
hooks: data.hooks || {},
|
|
195
|
+
prompt: data.prompt || agent.system_prompt || '',
|
|
196
|
+
maxTokens: data.max_tokens || agent.max_tokens || 4096,
|
|
197
|
+
maxTurns: data.maxTurns || data.max_turns || data.max_iterations || agent.max_iterations || 10,
|
|
198
|
+
max_iterations: data.max_iterations || data.maxTurns || data.max_turns || agent.max_iterations || 10,
|
|
199
|
+
can_delegate: data.can_delegate ?? false,
|
|
200
|
+
can_be_delegated_to: data.can_be_delegated_to ?? true,
|
|
201
|
+
temperature: data.temperature || undefined,
|
|
202
|
+
raw_config: config,
|
|
203
|
+
config,
|
|
204
|
+
};
|
|
205
|
+
}
|