@@ -15,12 +15,21 @@ import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js';
1515 * - TOOLS.md (tool definitions)
1616 * - skills/<name>/SKILL.md (skill files, passed through)
1717 */
18+ export interface SubAgentExport {
19+ name : string ;
20+ soulMd : string ;
21+ agentsMd : string ;
22+ toolsMd : string ;
23+ skills : Array < { name : string ; content : string } > ;
24+ }
25+
1826export interface OpenClawExport {
1927 config : object ;
2028 agentsMd : string ;
2129 soulMd : string ;
2230 toolsMd : string ;
2331 skills : Array < { name : string ; content : string } > ;
32+ subAgents : SubAgentExport [ ] ;
2433}
2534
2635export function exportToOpenClaw ( dir : string ) : OpenClawExport {
@@ -42,7 +51,10 @@ export function exportToOpenClaw(dir: string): OpenClawExport {
4251 // --- Skills (passthrough SKILL.md files) ---
4352 const skills = collectSkills ( agentDir ) ;
4453
45- return { config, agentsMd, soulMd, toolsMd, skills } ;
54+ // --- Sub-agents (separate workspaces) ---
55+ const subAgents = exportSubAgents ( agentDir , manifest ) ;
56+
57+ return { config, agentsMd, soulMd, toolsMd, skills, subAgents } ;
4658}
4759
4860/**
@@ -52,40 +64,99 @@ export function exportToOpenClaw(dir: string): OpenClawExport {
5264export function exportToOpenClawString ( dir : string ) : string {
5365 const exp = exportToOpenClaw ( dir ) ;
5466 const parts : string [ ] = [ ] ;
67+ const hasSubAgents = exp . subAgents . length > 0 ;
68+ const mainPrefix = hasSubAgents ? `workspace-${ exp . config && ( exp . config as Record < string , Record < string , string [ ] > > ) . agents ?. list ?. [ 0 ] || 'main' } ` : 'workspace' ;
5569
5670 parts . push ( '# === openclaw.json ===' ) ;
5771 parts . push ( JSON . stringify ( exp . config , null , 2 ) ) ;
5872
59- parts . push ( ' \n# === workspace /AGENTS.md ===' ) ;
73+ parts . push ( ` \n# === ${ mainPrefix } /AGENTS.md ===` ) ;
6074 parts . push ( exp . agentsMd ) ;
6175
62- parts . push ( ' \n# === workspace /SOUL.md ===' ) ;
76+ parts . push ( ` \n# === ${ mainPrefix } /SOUL.md ===` ) ;
6377 parts . push ( exp . soulMd ) ;
6478
6579 if ( exp . toolsMd ) {
66- parts . push ( ' \n# === workspace /TOOLS.md ===' ) ;
80+ parts . push ( ` \n# === ${ mainPrefix } /TOOLS.md ===` ) ;
6781 parts . push ( exp . toolsMd ) ;
6882 }
6983
7084 for ( const skill of exp . skills ) {
71- parts . push ( `\n# === workspace /skills/${ skill . name } /SKILL.md ===` ) ;
85+ parts . push ( `\n# === ${ mainPrefix } /skills/${ skill . name } /SKILL.md ===` ) ;
7286 parts . push ( skill . content ) ;
7387 }
7488
89+ // Sub-agent workspaces
90+ for ( const sub of exp . subAgents ) {
91+ const prefix = `workspace-${ sub . name } ` ;
92+
93+ parts . push ( `\n# === ${ prefix } /SOUL.md ===` ) ;
94+ parts . push ( sub . soulMd ) ;
95+
96+ parts . push ( `\n# === ${ prefix } /AGENTS.md ===` ) ;
97+ parts . push ( sub . agentsMd ) ;
98+
99+ if ( sub . toolsMd ) {
100+ parts . push ( `\n# === ${ prefix } /TOOLS.md ===` ) ;
101+ parts . push ( sub . toolsMd ) ;
102+ }
103+
104+ for ( const skill of sub . skills ) {
105+ parts . push ( `\n# === ${ prefix } /skills/${ skill . name } /SKILL.md ===` ) ;
106+ parts . push ( skill . content ) ;
107+ }
108+ }
109+
75110 return parts . join ( '\n' ) ;
76111}
77112
78113function buildOpenClawConfig ( agentDir : string , manifest : ReturnType < typeof loadAgentManifest > ) : object {
114+ const mainModel = mapModelName ( manifest . model ?. preferred ?? 'anthropic/claude-sonnet-4-5-20250929' ) ;
115+
116+ // Check for sub-agents → multi-agent config
117+ if ( manifest . agents && Object . keys ( manifest . agents ) . length > 0 ) {
118+ const agentNames = [ 'main' , ...Object . keys ( manifest . agents ) ] ;
119+ const agents : Record < string , unknown > = {
120+ list : agentNames ,
121+ main : buildAgentConfig ( mainModel , `~/.openclaw/workspace-${ manifest . name } ` , manifest ) ,
122+ } ;
123+
124+ for ( const name of Object . keys ( manifest . agents ) ) {
125+ const subDir = join ( agentDir , 'agents' , name ) ;
126+ let subModel = mainModel ;
127+ if ( existsSync ( join ( subDir , 'agent.yaml' ) ) ) {
128+ try {
129+ const subManifest = loadAgentManifest ( subDir ) ;
130+ if ( subManifest . model ?. preferred ) {
131+ subModel = mapModelName ( subManifest . model . preferred ) ;
132+ }
133+ } catch { /* use parent model */ }
134+ }
135+ agents [ name ] = {
136+ model : subModel ,
137+ workspace : `~/.openclaw/workspace-${ name } ` ,
138+ } ;
139+ }
140+
141+ return { agents } ;
142+ }
143+
144+ // Single-agent config (unchanged)
79145 const config : Record < string , unknown > = {
80- agent : {
81- model : mapModelName ( manifest . model ?. preferred ?? 'anthropic/claude-sonnet-4-5-20250929' ) ,
82- workspace : '~/.openclaw/workspace' ,
83- } ,
146+ agent : buildAgentConfig ( mainModel , '~/.openclaw/workspace' , manifest ) ,
84147 } ;
85148
86- // Map runtime settings
149+ return config ;
150+ }
151+
152+ function buildAgentConfig (
153+ model : string ,
154+ workspace : string ,
155+ manifest : ReturnType < typeof loadAgentManifest > ,
156+ ) : Record < string , unknown > {
157+ const agentConfig : Record < string , unknown > = { model, workspace } ;
158+
87159 if ( manifest . runtime ) {
88- const agentConfig = config . agent as Record < string , unknown > ;
89160 if ( manifest . runtime . temperature !== undefined ) {
90161 agentConfig . temperature = manifest . runtime . temperature ;
91162 }
@@ -94,12 +165,11 @@ function buildOpenClawConfig(agentDir: string, manifest: ReturnType<typeof loadA
94165 }
95166 }
96167
97- // Map model constraints
98168 if ( manifest . model ?. constraints ?. max_tokens ) {
99- ( config . agent as Record < string , unknown > ) . maxTokens = manifest . model . constraints . max_tokens ;
169+ agentConfig . maxTokens = manifest . model . constraints . max_tokens ;
100170 }
101171
102- return config ;
172+ return agentConfig ;
103173}
104174
105175/**
@@ -246,6 +316,32 @@ function buildToolsMd(agentDir: string): string {
246316 return parts . join ( '\n' ) ;
247317}
248318
319+ function exportSubAgents (
320+ agentDir : string ,
321+ manifest : ReturnType < typeof loadAgentManifest > ,
322+ ) : SubAgentExport [ ] {
323+ if ( ! manifest . agents ) return [ ] ;
324+
325+ const subAgents : SubAgentExport [ ] = [ ] ;
326+
327+ for ( const name of Object . keys ( manifest . agents ) ) {
328+ const subDir = join ( agentDir , 'agents' , name ) ;
329+ if ( ! existsSync ( subDir ) ) continue ;
330+
331+ try {
332+ const subManifest = loadAgentManifest ( subDir ) ;
333+ const soulMd = loadFileIfExists ( join ( subDir , 'SOUL.md' ) ) ?? `# ${ subManifest . name } \n${ subManifest . description } ` ;
334+ const agentsMd = buildAgentsMd ( subDir , subManifest ) ;
335+ const toolsMd = buildToolsMd ( subDir ) ;
336+ const skills = collectSkills ( subDir ) ;
337+
338+ subAgents . push ( { name, soulMd, agentsMd, toolsMd, skills } ) ;
339+ } catch { /* skip malformed sub-agents */ }
340+ }
341+
342+ return subAgents ;
343+ }
344+
249345function collectSkills ( agentDir : string ) : Array < { name : string ; content : string } > {
250346 const skills : Array < { name : string ; content : string } > = [ ] ;
251347 const skillsDir = join ( agentDir , 'skills' ) ;
0 commit comments