@@ -6,13 +6,13 @@ dotenv.config();
66const client = new OpenAI ( { apiKey : process . env . OPENAI_API_KEY } ) ;
77
88// Helper: call MCP routes dynamically, with error handling
9- async function callMCPTool ( tool , input ) {
9+ async function callMCPTool ( tool , input , cookie ) {
1010 try {
1111 const response = await fetch ( `http://localhost:3000/mcp/v1/${ tool } ` , {
1212 method : "POST" ,
1313 headers : {
1414 "Content-Type" : "application/json" ,
15- "Authorization " : `Bearer ${ process . env . MCP_SESSION_TOKEN } `,
15+ "Cookie " : cookie || ( process . env . MCP_SESSION_TOKEN ? `mcp_session= ${ process . env . MCP_SESSION_TOKEN } ` : "" ) ,
1616 } ,
1717 body : JSON . stringify ( input ) ,
1818 } ) ;
@@ -25,6 +25,13 @@ async function callMCPTool(tool, input) {
2525
2626// Wizard Agent Core
2727export async function runWizardAgent ( userPrompt ) {
28+ // Normalize userPrompt into a consistent text form + extract cookie
29+ const userPromptText =
30+ typeof userPrompt === "string"
31+ ? userPrompt
32+ : userPrompt ?. prompt || "" ;
33+
34+ const cookie = userPrompt ?. cookie || "" ;
2835 const systemPrompt = `
2936 You are the MCP Wizard Agent.
3037 You have full access to the following connected tools and APIs:
@@ -43,55 +50,68 @@ export async function runWizardAgent(userPrompt) {
4350 - “Show recent commits for [username/repo]” → use \`github_adapter\` with \`{ action: "commits", repo: "[username/repo]" }\`
4451 - “List workflows for [username/repo]” → use \`github_adapter\` with \`{ action: "workflows", repo: "[username/repo]" }\`
4552 - “List repos”, “List repositories”, or “repositories” → use \`repo_reader\` with optional \`{ username: "...", user_id: "..." }\`
53+ Valid CI/CD template types are ONLY:
54+ - node_app
55+ - python_app
56+ - container_service
57+
58+ When selecting or generating a pipeline template, you MUST return one of these exact values.
59+ Never invent new template names. If unsure, default to "node_app".
4660 ` ;
4761
4862 const completion = await client . chat . completions . create ( {
4963 model : "gpt-4o-mini" ,
5064 messages : [
5165 { role : "system" , content : systemPrompt } ,
52- { role : "user" , content : userPrompt } ,
66+ { role : "user" , content : typeof userPrompt === "string" ? userPrompt : userPrompt . prompt } ,
5367 ] ,
5468 } ) ;
5569
5670 const decision = completion . choices [ 0 ] . message . content ;
5771 console . log ( "\n🤖 Agent decided:" , decision ) ;
5872
73+ let agentMeta = {
74+ agent_decision : decision ,
75+ tool_called : null ,
76+ } ;
77+
5978 // Tool mapping using regex patterns
6079 const toolMap = {
6180 repo_reader : / \b ( l i s t r e p o s | l i s t r e p o s i t o r i e s | r e p o s i t o r i e s | r e p o _ r e a d e r ) \b / i,
6281 pipeline_generator : / \b p i p e l i n e \b / i,
82+ pipeline_commit : / \b ( y e s c o m m i t | c o m m i t ( t h e ) ? ( p i p e l i n e | w o r k f l o w | f i l e ) | a p p l y ( t h e ) ? ( p i p e l i n e | w o r k f l o w ) | s a v e ( t h e ) ? ( p i p e l i n e | w o r k f l o w ) | p u s h ( t h e ) ? ( p i p e l i n e | w o r k f l o w ) ) \b / i,
6383 oidc_adapter : / \b ( r o l e | j e n k i n s ) \b / i,
6484 github_adapter : / \b ( g i t h u b | r e p o i n f o | r e p o s i t o r y | [ \w - ] + \/ [ \w - ] + ) \b / i,
6585 } ;
6686
6787 for ( const [ toolName , pattern ] of Object . entries ( toolMap ) ) {
68- if ( pattern . test ( decision ) ) {
88+ if ( pattern . test ( decision ) || pattern . test ( userPromptText ) ) {
6989 console . log ( '🔧 Triggering MCP tool:' , toolName ) ;
7090
7191 // --- Extract context dynamically from userPrompt or decision ---
7292 // Prefer explicit labels like: "repo owner/name", "template node_app", "provider aws"
73- const labeledRepo = userPrompt . match ( / \b r e p o \s + ( [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + ) \b / i)
93+ const labeledRepo = userPromptText . match ( / \b r e p o \s + ( [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + ) \b / i)
7494 || decision . match ( / \b r e p o \s + ( [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + ) \b / i) ;
75- const genericRepo = ( userPrompt + " " + decision ) . match ( / \b (? ! c i \/ c d \b ) ( [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + ) \b / ) ;
95+ const genericRepo = ( userPromptText + " " + decision ) . match ( / \b (? ! c i \/ c d \b ) ( [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + ) \b / ) ;
7696 const repo = ( labeledRepo ?. [ 1 ] || genericRepo ?. [ 1 ] || null ) ;
7797
78- const labeledProvider = userPrompt . match ( / \b p r o v i d e r \s + ( a w s | j e n k i n s | g c p | a z u r e ) \b / i)
98+ const labeledProvider = userPromptText . match ( / \b p r o v i d e r \s + ( a w s | j e n k i n s | g c p | a z u r e ) \b / i)
7999 || decision . match ( / \b p r o v i d e r \s + ( a w s | j e n k i n s | g c p | a z u r e ) \b / i) ;
80- const genericProvider = userPrompt . match ( / \b ( a w s | j e n k i n s | g i t h u b a c t i o n s | g c p | a z u r e ) \b / i)
100+ const genericProvider = userPromptText . match ( / \b ( a w s | j e n k i n s | g i t h u b a c t i o n s | g c p | a z u r e ) \b / i)
81101 || decision . match ( / \b ( a w s | j e n k i n s | g i t h u b a c t i o n s | g c p | a z u r e ) \b / i) ;
82102 const provider = ( labeledProvider ?. [ 1 ] || genericProvider ?. [ 1 ] || null ) ?. toLowerCase ( ) . replace ( / \s + / g, ' ' ) ;
83103
84- const labeledTemplate = userPrompt . match ( / \b t e m p l a t e \s + ( [ a - z _ ] [ a - z 0 - 9 _ ] + ) \b / i)
104+ const labeledTemplate = userPromptText . match ( / \b t e m p l a t e \s + ( [ a - z _ ] [ a - z 0 - 9 _ ] + ) \b / i)
85105 || decision . match ( / \b t e m p l a t e \s + ( [ a - z _ ] [ a - z 0 - 9 _ ] + ) \b / i) ;
86- const genericTemplate = userPrompt . match ( / \b ( n o d e _ a p p | p y t h o n _ a p p | c o n t a i n e r _ s e r v i c e | n o d e | p y t h o n | r e a c t | e x p r e s s | d j a n g o | f l a s k | j a v a | g o ) \b / i)
106+ const genericTemplate = userPromptText . match ( / \b ( n o d e _ a p p | p y t h o n _ a p p | c o n t a i n e r _ s e r v i c e | n o d e | p y t h o n | r e a c t | e x p r e s s | d j a n g o | f l a s k | j a v a | g o ) \b / i)
87107 || decision . match ( / \b ( n o d e _ a p p | p y t h o n _ a p p | c o n t a i n e r _ s e r v i c e | n o d e | p y t h o n | r e a c t | e x p r e s s | d j a n g o | f l a s k | j a v a | g o ) \b / i) ;
88108 const template = ( labeledTemplate ?. [ 1 ] || genericTemplate ?. [ 1 ] || null ) ?. toLowerCase ( ) ;
89109
90110 if ( toolName === "repo_reader" ) {
91111 // Extract optional username, user_id, and repo info
92- const usernameMatch = userPrompt . match ( / \b u s e r n a m e [: = ] ? \s * ( [ \w - ] + ) \b / i) ;
93- const userIdMatch = userPrompt . match ( / \b u s e r [ _ ] ? i d [: = ] ? \s * ( [ \w - ] + ) \b / i) ;
94- const repoMatch = userPrompt . match ( / \b ( [ \w - ] + \/ [ \w - ] + ) \b / ) ;
112+ const usernameMatch = userPromptText . match ( / \b u s e r n a m e [: = ] ? \s * ( [ \w - ] + ) \b / i) ;
113+ const userIdMatch = userPromptText . match ( / \b u s e r [ _ ] ? i d [: = ] ? \s * ( [ \w - ] + ) \b / i) ;
114+ const repoMatch = userPromptText . match ( / \b ( [ \w - ] + \/ [ \w - ] + ) \b / ) ;
95115
96116 const payload = { } ;
97117 if ( usernameMatch ) payload . username = usernameMatch [ 1 ] ;
@@ -102,7 +122,14 @@ export async function runWizardAgent(userPrompt) {
102122 payload . repo = `${ username } /${ repo } ` ;
103123 }
104124
105- return await callMCPTool ( "repo_reader" , payload ) ;
125+ agentMeta . tool_called = "repo_reader" ;
126+ const output = await callMCPTool ( "repo_reader" , payload , cookie ) ;
127+ return {
128+ success : true ,
129+ agent_decision : agentMeta . agent_decision ,
130+ tool_called : agentMeta . tool_called ,
131+ tool_output : output
132+ } ;
106133 }
107134
108135 if ( toolName === "pipeline_generator" ) {
@@ -121,7 +148,7 @@ export async function runWizardAgent(userPrompt) {
121148 // Fetch GitHub repo details before pipeline generation
122149 let repoInfo = null ;
123150 try {
124- const info = await callMCPTool ( "github_adapter" , { action : "info" , repo } ) ;
151+ const info = await callMCPTool ( "github_adapter" , { action : "info" , repo } , cookie ) ;
125152 if ( info ?. data ?. success ) {
126153 repoInfo = info . data ;
127154 console . log ( `📦 Retrieved repo info from GitHub:` , repoInfo ) ;
@@ -151,6 +178,13 @@ export async function runWizardAgent(userPrompt) {
151178 if ( payload . template === "python" ) payload . template = "python_app" ;
152179 if ( payload . template === "container" ) payload . template = "container_service" ;
153180
181+ // --- Validate template against allowed values ---
182+ const allowedTemplates = [ "node_app" , "python_app" , "container_service" ] ;
183+ if ( ! allowedTemplates . includes ( payload . template ) ) {
184+ console . warn ( "⚠ Invalid template inferred:" , payload . template , "— auto-correcting to node_app." ) ;
185+ payload . template = "node_app" ;
186+ }
187+
154188 // --- Preserve repo context globally ---
155189 if ( ! payload . repo && globalThis . LAST_REPO_USED ) {
156190 payload . repo = globalThis . LAST_REPO_USED ;
@@ -166,17 +200,126 @@ export async function runWizardAgent(userPrompt) {
166200 }
167201
168202 console . log ( "🧩 Final payload to pipeline_generator:" , payload ) ;
169- return await callMCPTool ( "pipeline_generator" , payload ) ;
203+ agentMeta . tool_called = "pipeline_generator" ;
204+ const output = await callMCPTool ( "pipeline_generator" , payload , cookie ) ;
205+
206+ // Extract YAML for confirmation step
207+ const generatedYaml =
208+ output ?. data ?. data ?. generated_yaml ||
209+ output ?. tool_output ?. data ?. generated_yaml ||
210+ null ;
211+
212+ // Store YAML globally for future commit step
213+ globalThis . LAST_GENERATED_YAML = generatedYaml ;
214+
215+ // Return confirmation-required structure
216+ return {
217+ success : true ,
218+ requires_confirmation : true ,
219+ message : "A pipeline has been generated. Would you like me to commit this workflow file to your repository?" ,
220+ agent_decision : agentMeta . agent_decision ,
221+ tool_called : agentMeta . tool_called ,
222+ generated_yaml : generatedYaml ,
223+ pipeline_metadata : output
224+ } ;
225+ }
226+
227+ if ( toolName === "pipeline_commit" ) {
228+ console . log ( "📝 Commit intent detected." ) ;
229+
230+ // ❗ Guard: Prevent confusing "repo commit history" with "pipeline commit"
231+ if ( / r e c e n t c o m m i t s | c o m m i t h i s t o r y | s e e c o m m i t s | s h o w c o m m i t s | v i e w c o m m i t s / i. test ( decision + " " + userPromptText ) ) {
232+ console . log ( "⚠ Not pipeline commit. Detected intention to view repo commit history." ) ;
233+ agentMeta . tool_called = "github_adapter" ;
234+
235+ const repoForCommits = repo || globalThis . LAST_REPO_USED ;
236+ if ( ! repoForCommits ) {
237+ return {
238+ success : false ,
239+ error : "Please specify a repository, e.g. 'show commits for user/repo'."
240+ } ;
241+ }
242+
243+ const output = await callMCPTool ( "github_adapter" , { action : "commits" , repo : repoForCommits } , cookie ) ;
244+
245+ return {
246+ success : true ,
247+ agent_decision : agentMeta . agent_decision ,
248+ tool_called : agentMeta . tool_called ,
249+ tool_output : output
250+ } ;
251+ }
252+
253+ // Ensure we have a repo
254+ const commitRepo = repo || globalThis . LAST_REPO_USED ;
255+ if ( ! commitRepo ) {
256+ return {
257+ success : false ,
258+ error : "I don’t know which repository to commit to. Please specify the repo (e.g., 'commit to user/repo')."
259+ } ;
260+ }
261+
262+ // Extract YAML from userPrompt or fallback to last generated YAML
263+ const yamlMatch = userPromptText . match ( / ` ` ` y a m l ( [ \s \S ] * ?) ` ` ` / i) ;
264+ const yamlFromPrompt = yamlMatch ? yamlMatch [ 1 ] . trim ( ) : null ;
265+
266+ const yaml =
267+ yamlFromPrompt ||
268+ globalThis . LAST_GENERATED_YAML ||
269+ null ;
270+
271+ if ( ! yaml ) {
272+ return {
273+ success : false ,
274+ error : "I don’t have a pipeline YAML to commit. Please generate one first."
275+ } ;
276+ }
277+
278+ // Save YAML globally for future edits
279+ globalThis . LAST_GENERATED_YAML = yaml ;
280+
281+ const commitPayload = {
282+ repoFullName : commitRepo ,
283+ yaml,
284+ branch : "main" ,
285+ path : ".github/workflows/ci.yml"
286+ } ;
287+
288+ agentMeta . tool_called = "pipeline_commit" ;
289+ const output = await callMCPTool ( "pipeline_commit" , commitPayload , cookie ) ;
290+
291+ return {
292+ success : true ,
293+ agent_decision : agentMeta . agent_decision ,
294+ tool_called : agentMeta . tool_called ,
295+ committed_repo : commitRepo ,
296+ committed_path : ".github/workflows/ci.yml" ,
297+ tool_output : output
298+ } ;
170299 }
171300
172301 if ( toolName === "oidc_adapter" ) {
173302 const payload = provider ? { provider } : { } ;
174- return await callMCPTool ( "oidc_adapter" , payload ) ;
303+ agentMeta . tool_called = "oidc_adapter" ;
304+ const output = await callMCPTool ( "oidc_adapter" , payload , cookie ) ;
305+ return {
306+ success : true ,
307+ agent_decision : agentMeta . agent_decision ,
308+ tool_called : agentMeta . tool_called ,
309+ tool_output : output
310+ } ;
175311 }
176312
177313 if ( toolName === "github_adapter" ) {
178314 if ( repo ) {
179- return await callMCPTool ( "github/info" , { repo } ) ;
315+ agentMeta . tool_called = "github_adapter" ;
316+ const output = await callMCPTool ( "github/info" , { repo } , cookie ) ;
317+ return {
318+ success : true ,
319+ agent_decision : agentMeta . agent_decision ,
320+ tool_called : agentMeta . tool_called ,
321+ tool_output : output
322+ } ;
180323 } else {
181324 console . warn ( "⚠️ Missing repo for GitHub info retrieval." ) ;
182325 return {
@@ -188,7 +331,12 @@ export async function runWizardAgent(userPrompt) {
188331 }
189332 }
190333
191- return { message : "No matching tool found. Try asking about a repo, pipeline, or AWS role." } ;
334+ return {
335+ success : false ,
336+ agent_decision : agentMeta . agent_decision ,
337+ tool_called : null ,
338+ message : "No matching tool found. Try asking about a repo, pipeline, or AWS role."
339+ } ;
192340}
193341
194342// Example local test (can comment out for production)
0 commit comments