@zibby/core 0.1.30 → 0.1.33

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.
@@ -1,5 +1,5 @@
1
- import{WorkflowState as ee}from"./state.js";import{Node as L,ConditionalNode as te}from"./node.js";import{ContextLoader as oe}from"./context-loader.js";import{mkdirSync as W,existsSync as b,writeFileSync as z,unlinkSync as se}from"fs";import{join as E,resolve as G}from"path";import{config as ne}from"dotenv";import{zodToJsonSchema as H}from"zod-to-json-schema";import re from"handlebars";import{DEFAULT_OUTPUT_BASE as C,SESSIONS_DIR as ie,SESSION_INFO_FILE as V,CI_ENV_VARS as ce,STUDIO_STOP_REQUEST_FILE as ae}from"./constants.js";import{timeline as S}from"../utils/timeline.js";function de({traceFrom:f,sessionId:e,sessionPath:t,idSource:s,mkdirFresh:o}){if(process.env.ZIBBY_SESSION_LOG==="0"||process.env.ZIBBY_SESSION_LOG==="false")return;const n=typeof process.ppid=="number"?process.ppid:"n/a",r=`[zibby:session] from=${f} pid=${process.pid} ppid=${n} sessionId=${e} source=${s} mkdir=${o?"yes":"no"} path=${t}`;if(console.log(r),(process.env.ZIBBY_TRACE_SESSION==="1"||process.env.ZIBBY_TRACE_SESSION==="true")&&process.env.ZIBBY_SESSION_LOG!=="0"&&process.env.ZIBBY_SESSION_LOG!=="false"){const i=(new Error("session trace").stack||"").split(`
1
+ import{WorkflowState as ee}from"./state.js";import{Node as L,ConditionalNode as te}from"./node.js";import{ContextLoader as oe}from"./context-loader.js";import{mkdirSync as W,existsSync as b,writeFileSync as z,unlinkSync as se}from"fs";import{join as E,resolve as G}from"path";import{config as ne}from"dotenv";import{zodToJsonSchema as H}from"zod-to-json-schema";import re from"handlebars";import{DEFAULT_OUTPUT_BASE as x,SESSIONS_DIR as ie,SESSION_INFO_FILE as V,CI_ENV_VARS as ce,STUDIO_STOP_REQUEST_FILE as ae}from"./constants.js";import{timeline as S}from"../utils/timeline.js";function de({traceFrom:f,sessionId:e,sessionPath:t,idSource:s,mkdirFresh:o}){if(process.env.ZIBBY_SESSION_LOG==="0"||process.env.ZIBBY_SESSION_LOG==="false")return;const n=typeof process.ppid=="number"?process.ppid:"n/a",r=`[zibby:session] from=${f} pid=${process.pid} ppid=${n} sessionId=${e} source=${s} mkdir=${o?"yes":"no"} path=${t}`;if(console.log(r),(process.env.ZIBBY_TRACE_SESSION==="1"||process.env.ZIBBY_TRACE_SESSION==="true")&&process.env.ZIBBY_SESSION_LOG!=="0"&&process.env.ZIBBY_SESSION_LOG!=="false"){const i=(new Error("session trace").stack||"").split(`
2
2
  `).slice(2,14).join(`
3
3
  `);console.log(`[zibby:session] stack (${f}):
4
- ${i}`)}}function le(){return process.env.ZIBBY_RUN_SOURCE==="studio"||process.env.ZIBBY_KEEP_SESSION_ENV==="1"||process.env.ZIBBY_KEEP_SESSION_ENV==="true"}function pe(){if(process.env.ZIBBY_RUN_SOURCE!=="studio")return;const f=process.env.ZIBBY_SESSION_PATH;if(!(f==null||String(f).trim()===""))try{return G(String(f).trim())}catch{return String(f).trim()}}function fe(){le()||(delete process.env.ZIBBY_SESSION_PATH,delete process.env.ZIBBY_SESSION_ID)}function ue({sessionPath:f,sessionId:e}){f&&typeof f=="string"&&(process.env.ZIBBY_SESSION_PATH=f),e!=null&&String(e).trim()!==""&&(process.env.ZIBBY_SESSION_ID=String(e).trim())}function he(f={}){const e=ce.map(n=>process.env[n]).find(Boolean),t=Math.random().toString(36).slice(2,6),s=e||`${Date.now()}_${t}`,o=f.paths?.sessionPrefix;return o?`${o}_${s}`:s}function me({cwd:f=process.cwd(),config:e={},initialState:t={},traceFrom:s="resolveWorkflowSession"}={}){let o=t.sessionPath,n=t.sessionTimestamp,r="initialState.sessionPath";if(!o&&process.env.ZIBBY_SESSION_PATH)try{const i=G(String(process.env.ZIBBY_SESSION_PATH));i&&(o=i,r="ZIBBY_SESSION_PATH")}catch{}let c;if(o)c=String(o).split(/[/\\]/).filter(Boolean).pop(),n==null&&(n=Date.now());else{const i=process.env.ZIBBY_SESSION_ID&&String(process.env.ZIBBY_SESSION_ID).trim();if(i)c=i,r="ZIBBY_SESSION_ID";else{const g=e.sessionId!=null?String(e.sessionId).trim():"";g&&g!=="last"?(c=g,r="config.sessionId"):(c=he(e),r="generated")}n=n??Date.now();const u=e.paths?.output||C;o=E(f,u,ie,c)}const p=!b(o);return p&&W(o,{recursive:!0}),de({traceFrom:s,sessionId:c,sessionPath:o,idSource:r,mkdirFresh:p}),ue({sessionPath:o,sessionId:c}),{sessionPath:o,sessionId:c,sessionTimestamp:n}}class Oe{constructor(e={}){this.nodes=new Map,this.edges=new Map,this.entryPoint=null,this.middleware=Array.isArray(e.middleware)?[...e.middleware]:[],e.nodeMiddleware&&this.middleware.push(e.nodeMiddleware),this.nodeTypeMap=new Map,this.conditionalCodeMap=new Map,this.stateSchema=e.stateSchema||null,this.nodePrompts=new Map,this.nodeOptions=new Map,this._invokeAgent=e.invokeAgent||null}setStateSchema(e){return this.stateSchema=e,this}getStateSchema(){return this.stateSchema}addNode(e,t,s={}){const o=t instanceof L?t:new L(t);return o.name=e,this.nodes.set(e,o),s.prompt&&this.nodePrompts.set(e,s.prompt),Object.keys(s).length>0&&this.nodeOptions.set(e,s),this}addConditionalNode(e,t){const s=new te({...t,name:e});return this.nodes.set(e,s),this}addEdge(e,t){return this.edges.set(e,t),this}setNodeType(e,t){return this.nodeTypeMap.set(e,t),this}addConditionalEdges(e,t,{labels:s}={}){return this.edges.set(e,{conditional:!0,routes:t,labels:s}),typeof t=="function"&&this.conditionalCodeMap.set(e,t.toString()),this}setEntryPoint(e){return this.entryPoint=e,this}use(e){return typeof e=="function"&&this.middleware.push(e),this}_composeMiddleware(e,t,s,o,n){let r=s;for(let c=e.length-1;c>=0;c--){const p=e[c],i=r;r=()=>p(t,i,o,n)}return r()}serialize(){const e=[],t={};for(const[n,r]of this.nodes){const c=this.nodeTypeMap.get(n)||n;e.push({id:n,type:c,data:{nodeType:c,label:n}});const p=r._isCustomCode||!1,i={};p&&typeof r.execute=="function"&&(i.customCode=r.execute.toString());const u=this.nodePrompts.get(n);if(u&&(i.prompt=u),typeof r.customExecute=="function"&&(i.executeCode=r.customExecute.toString()),r.outputSchema)try{if(typeof r.outputSchema._def<"u"){const P=H(r.outputSchema,{target:"openApi3"}),d=this._flattenJsonSchemaToVariables(P);i.outputSchema={jsonSchema:P,variables:d}}else i.outputSchema={schema:r.outputSchema}}catch(v){console.warn(`Failed to convert schema for ${n}:`,v.message)}const g=(this.resolvedToolsMap||{})[n];g?.toolIds&&(i.tools=g.toolIds),Object.keys(i).length>0&&(t[n]=i)}const s=[];for(const[n,r]of this.edges)if(typeof r=="string")s.push({source:n,target:r});else if(r.conditional){const c=this.conditionalCodeMap.get(n)||r.routes.toString(),p=this._inferConditionalTargets(r.routes),i=r.labels||{};for(const u of p){const g={source:n,target:u,data:{conditionalCode:c}};i[u]&&(g.label=i[u]),s.push(g)}}let o=null;if(this.stateSchema)try{o=H(this.stateSchema,{target:"openApi3"})}catch{o=this.stateSchema}return{nodes:e,edges:s,nodeConfigs:t,stateSchema:o}}_inferConditionalTargets(e){const t=e.toString(),s=new Set,o=/return\s+['"]([^'"]+)['"]/g;let n;for(;(n=o.exec(t))!==null;)s.add(n[1]);return[...s]}_flattenJsonSchemaToVariables(e,t=""){let s=e;if(e.$ref&&e.definitions){const o=e.$ref.replace("#/definitions/","");s=e.definitions[o]||e}return this._flattenSchema(s,t)}_flattenSchema(e,t=""){if(!e||typeof e!="object")return[];const s=[],o=e.properties||{},n=e.required||[];for(const[r,c]of Object.entries(o)){const p=t?`${t}.${r}`:r,i=!n.includes(r);if(s.push({path:p,type:c.type||"unknown",label:c.description||this._formatLabel(r),optional:i}),c.type==="object"&&c.properties){const u=this._flattenSchema(c,p);s.push(...u)}if(c.type==="array"&&c.items?.type==="object"&&c.items.properties){const u=this._flattenSchema(c.items,`${p}[]`);s.push(...u)}}return s}_formatLabel(e){return e.replace(/([A-Z])/g," $1").replace(/^./,t=>t.toUpperCase()).trim()}_summarizeNodeOutput(e,t){if(!t||typeof t!="object")return[];const s=[];t.success!==void 0&&s.push(`Result: ${t.success?"passed":"failed"}`);for(const[o,n]of Object.entries(t))if(!(o==="success"||o==="raw"||o==="nextNode")){if(typeof n=="string"&&n.length<=80)s.push(`${o}: ${n}`);else if(Array.isArray(n)){const r=n.length,c=n.filter(i=>i?.passed===!0).length;if(n.some(i=>i?.passed!==void 0)){const i=r-c;s.push(`${o}: ${c}/${r} passed${i?`, ${i} failed`:""}`)}else s.push(`${o}: ${r} items`)}if(s.length>=4)break}return s}async run(e,t={}){if(!this.entryPoint)throw new Error("No entry point set for graph");const s=t.cwd||process.cwd();ne({path:E(s,".env")});let o=t.config||{};if(!o||Object.keys(o).length===0)try{const l=E(s,".zibby.config.js");b(l)&&(o=(await import(l)).default||{})}catch{}process.env.EXECUTION_ID&&!o.agent?.strictMode&&(o.agent={...o.agent,strictMode:!0});let n=t.agentType;if(!n){const l=o?.agent;l?.provider?n=l.provider:l?.claude?n="claude":l?.cursor?n="cursor":n=process.env.AGENT_TYPE||"cursor"}const r=t.contextConfig||e?.config?.contextConfig||e?.config?.context||o?.context||{};if(this.stateSchema){const l=this.stateSchema.safeParse(t);if(!l.success){const m=l.error.issues.map(w=>`${w.path.join(".")}: ${w.message}`);throw console.error("\u274C Initial state validation failed:"),m.forEach(w=>console.error(` - ${w}`)),new Error(`State validation failed: ${m.join(", ")}`)}S.step("State validated against schema")}const c=pe(),p=t.sessionPath||c;p||fe();const{sessionPath:i,sessionTimestamp:u,sessionId:g}=me({cwd:s,config:o,traceFrom:"WorkflowGraph.run",initialState:{sessionPath:p,sessionTimestamp:t.sessionTimestamp}});S.step(`Session ${g}`);const v=await oe.loadContext(t.specPath||"",s,r);Object.keys(v).length>0&&S.step(`Context loaded: ${Object.keys(v).join(", ")}`);let P=t.outputPath;!P&&t.specPath&&(e?.calculateOutputPath?P=e.calculateOutputPath(t.specPath):console.warn(`\u26A0\uFE0F outputPath not resolved (specPath=${t.specPath})`));const d=new ee({...t,config:o,agentType:n,outputPath:P,sessionPath:i,sessionTimestamp:u,context:v,resolvedTools:this.resolvedToolsMap||{}}),x=new Map;try{await import("@zibby/skills")}catch{}const{getSkill:J}=await import("./skill-registry.js"),M=new Set;for(const[,l]of this.nodes)for(const m of l.config?.skills||[])M.add(m);for(const l of M){const m=J(l);if(typeof m?.middleware=="function")try{const w=await m.middleware();typeof w=="function"&&x.set(l,w)}catch{}}let a=this.entryPoint;const O=[];for(;a&&a!=="END";){const l=E(i,ae);if(b(l)){console.warn(`
5
- \u{1F6D1} Studio stop requested \u2014 ending workflow.`);try{se(l)}catch{}if(e&&typeof e.cleanup=="function")try{await e.cleanup()}catch{}return S.step("Workflow stopped by Studio"),{success:!0,state:d.getAll(),executionLog:O,stoppedByStudio:!0}}const m=this.nodes.get(a);if(!m)throw new Error(`Node '${a}' not found in graph`);const w=JSON.stringify({sessionPath:i,sessionTimestamp:u,currentNode:a,createdAt:new Date().toISOString(),config:d.get("config")}),q=E(i,V);z(q,w,"utf-8");const D=d.get("config")?.paths?.output||C,K=E(s,D,V);W(E(s,D),{recursive:!0});try{z(K,w,"utf-8")}catch{}const Y=t.onPipelineProgress;if(typeof Y=="function")try{Y({cwd:s,sessionPath:i,sessionId:g,outputBase:d.get("config")?.paths?.output||C,currentNode:a})}catch{}const Q=(this.resolvedToolsMap||{})[a]||null;d.set("_currentNodeTools",Q);const X=d.get("nodeConfigs")||{};d.set("_currentNodeConfig",X[a]||{}),S.nodeStart(a);const R=Date.now(),T=this.nodePrompts.get(a);if(!this._invokeAgent){const _=await import("./agents/index.js");this._invokeAgent=_.invokeAgent}const j=this._invokeAgent,F={state:d,invokeAgent:async(_={},I={})=>{let h=I.prompt||"";if(T)try{h=re.compile(T,{noEscape:!0})(_)}catch(y){throw console.error(`\u274C Template rendering failed for node '${a}':`,y.message),new Error(`Template rendering failed: ${y.message}`,{cause:y})}else if(!h)throw new Error(`No prompt template configured for node '${a}' and no prompt provided in options`);const B={state:d.getAll(),images:I.images||[]},A={model:I.model||d.get("model"),workspace:d.get("workspace"),schema:I.schema,...I};return j(h,B,A)},_coreInvokeAgent:j,agent:e,nodeId:a,promptTemplate:T,getPromptTemplate:()=>T,...d.getAll()};try{const _=(m.config?.skills||[]).map($=>x.get($)).filter(Boolean),I=[...this.middleware,..._];let h;I.length>0?h=await this._composeMiddleware(I,a,async()=>m.execute(F,d),d.getAll(),d):h=await m.execute(F,d);const B=Date.now()-R;if(O.push({node:a,success:h.success,duration:B,timestamp:new Date().toISOString()}),!h.success){if(String(h.error||"").includes("Stopped from Zibby Studio")){if(S.step("Workflow stopped by Studio"),d.set("stoppedByStudio",!0),e&&typeof e.cleanup=="function")try{await e.cleanup()}catch{}return{success:!0,state:d.getAll(),executionLog:O,stoppedByStudio:!0}}d.append("errors",{node:a,error:h.error});const N=m.config?.retries||0,U=`${a}_retries`,k=d.getAll()[U]||0;if(k<N){S.stepInfo(`Retrying (attempt ${k+1}/${N})`),d.update({[U]:k+1,[`${a}_raw`]:h.raw});continue}throw S.nodeFailed(a,h.error,{duration:B}),new Error(`Node '${a}' failed after ${k} attempts: ${h.error}`)}d.update({[a]:h.output});const A=this._summarizeNodeOutput(a,h.output);S.nodeComplete(a,{duration:B,details:A});const y=this.edges.get(a);if(!y)a="END";else if(y.conditional){const $=d.getAll(),N=y.routes($);S.route(a,N),a=N}else a=y}catch(_){throw S.isInsideNode&&S.nodeFailed(a,_.message,{duration:Date.now()-R}),d.set("failed",!0),d.set("failedAt",a),_}}S.graphComplete();const Z={success:!0,state:d.getAll(),executionLog:O};return e&&typeof e.onComplete=="function"&&await e.onComplete(Z),Z}}export{Oe as WorkflowGraph,fe as clearInheritedSessionEnvForFreshRun,he as generateWorkflowSessionId,pe as readStudioPinnedSessionPathFromEnv,me as resolveWorkflowSession,le as shouldTrustInheritedSessionEnv,ue as syncProcessEnvToSession};
4
+ ${i}`)}}function le(){return process.env.ZIBBY_RUN_SOURCE==="studio"||process.env.ZIBBY_KEEP_SESSION_ENV==="1"||process.env.ZIBBY_KEEP_SESSION_ENV==="true"}function pe(){if(process.env.ZIBBY_RUN_SOURCE!=="studio")return;const f=process.env.ZIBBY_SESSION_PATH;if(!(f==null||String(f).trim()===""))try{return G(String(f).trim())}catch{return String(f).trim()}}function fe(){le()||(delete process.env.ZIBBY_SESSION_PATH,delete process.env.ZIBBY_SESSION_ID)}function ue({sessionPath:f,sessionId:e}){f&&typeof f=="string"&&(process.env.ZIBBY_SESSION_PATH=f),e!=null&&String(e).trim()!==""&&(process.env.ZIBBY_SESSION_ID=String(e).trim())}function he(f={}){const e=ce.map(n=>process.env[n]).find(Boolean),t=Math.random().toString(36).slice(2,6),s=e||`${Date.now()}_${t}`,o=f.paths?.sessionPrefix;return o?`${o}_${s}`:s}function me({cwd:f=process.cwd(),config:e={},initialState:t={},traceFrom:s="resolveWorkflowSession"}={}){let o=t.sessionPath,n=t.sessionTimestamp,r="initialState.sessionPath";if(!o&&process.env.ZIBBY_SESSION_PATH)try{const i=G(String(process.env.ZIBBY_SESSION_PATH));i&&(o=i,r="ZIBBY_SESSION_PATH")}catch{}let c;if(o)c=String(o).split(/[/\\]/).filter(Boolean).pop(),n==null&&(n=Date.now());else{const i=process.env.ZIBBY_SESSION_ID&&String(process.env.ZIBBY_SESSION_ID).trim();if(i)c=i,r="ZIBBY_SESSION_ID";else{const g=e.sessionId!=null?String(e.sessionId).trim():"";g&&g!=="last"?(c=g,r="config.sessionId"):(c=he(e),r="generated")}n=n??Date.now();const u=e.paths?.output||x;o=E(f,u,ie,c)}const p=!b(o);return p&&W(o,{recursive:!0}),de({traceFrom:s,sessionId:c,sessionPath:o,idSource:r,mkdirFresh:p}),ue({sessionPath:o,sessionId:c}),{sessionPath:o,sessionId:c,sessionTimestamp:n}}class Oe{constructor(e={}){this.nodes=new Map,this.edges=new Map,this.entryPoint=null,this.middleware=Array.isArray(e.middleware)?[...e.middleware]:[],e.nodeMiddleware&&this.middleware.push(e.nodeMiddleware),this.nodeTypeMap=new Map,this.conditionalCodeMap=new Map,this.stateSchema=e.stateSchema||null,this.nodePrompts=new Map,this.nodeOptions=new Map,this._invokeAgent=e.invokeAgent||null}setStateSchema(e){return this.stateSchema=e,this}getStateSchema(){return this.stateSchema}addNode(e,t,s={}){const o=t instanceof L?t:new L(t);return o.name=e,this.nodes.set(e,o),s.prompt&&this.nodePrompts.set(e,s.prompt),Object.keys(s).length>0&&this.nodeOptions.set(e,s),this}addConditionalNode(e,t){const s=new te({...t,name:e});return this.nodes.set(e,s),this}addEdge(e,t){return this.edges.set(e,t),this}setNodeType(e,t){return this.nodeTypeMap.set(e,t),this}addConditionalEdges(e,t,{labels:s}={}){return this.edges.set(e,{conditional:!0,routes:t,labels:s}),typeof t=="function"&&this.conditionalCodeMap.set(e,t.toString()),this}setEntryPoint(e){return this.entryPoint=e,this}use(e){return typeof e=="function"&&this.middleware.push(e),this}_composeMiddleware(e,t,s,o,n){let r=s;for(let c=e.length-1;c>=0;c--){const p=e[c],i=r;r=()=>p(t,i,o,n)}return r()}serialize(){const e=[],t={};for(const[n,r]of this.nodes){const c=this.nodeTypeMap.get(n)||n;e.push({id:n,type:c,data:{nodeType:c,label:n}});const p=r._isCustomCode||!1,i={};p&&typeof r.execute=="function"&&(i.customCode=r.execute.toString());const u=this.nodePrompts.get(n);if(u&&(i.prompt=u),typeof r.customExecute=="function"&&(i.executeCode=r.customExecute.toString()),r.outputSchema)try{if(typeof r.outputSchema._def<"u"){const P=H(r.outputSchema,{target:"openApi3"}),d=this._flattenJsonSchemaToVariables(P);i.outputSchema={jsonSchema:P,variables:d}}else i.outputSchema={schema:r.outputSchema}}catch(v){console.warn(`Failed to convert schema for ${n}:`,v.message)}const g=(this.resolvedToolsMap||{})[n];g?.toolIds&&(i.tools=g.toolIds),Object.keys(i).length>0&&(t[n]=i)}const s=[];for(const[n,r]of this.edges)if(typeof r=="string")s.push({source:n,target:r});else if(r.conditional){const c=this.conditionalCodeMap.get(n)||r.routes.toString(),p=this._inferConditionalTargets(r.routes),i=r.labels||{};for(const u of p){const g={source:n,target:u,data:{conditionalCode:c}};i[u]&&(g.label=i[u]),s.push(g)}}let o=null;if(this.stateSchema)try{o=H(this.stateSchema,{target:"openApi3"})}catch{o=this.stateSchema}return{nodes:e,edges:s,nodeConfigs:t,stateSchema:o}}_inferConditionalTargets(e){const t=e.toString(),s=new Set,o=/return\s+['"]([^'"]+)['"]/g;let n;for(;(n=o.exec(t))!==null;)s.add(n[1]);return[...s]}_flattenJsonSchemaToVariables(e,t=""){let s=e;if(e.$ref&&e.definitions){const o=e.$ref.replace("#/definitions/","");s=e.definitions[o]||e}return this._flattenSchema(s,t)}_flattenSchema(e,t=""){if(!e||typeof e!="object")return[];const s=[],o=e.properties||{},n=e.required||[];for(const[r,c]of Object.entries(o)){const p=t?`${t}.${r}`:r,i=!n.includes(r);if(s.push({path:p,type:c.type||"unknown",label:c.description||this._formatLabel(r),optional:i}),c.type==="object"&&c.properties){const u=this._flattenSchema(c,p);s.push(...u)}if(c.type==="array"&&c.items?.type==="object"&&c.items.properties){const u=this._flattenSchema(c.items,`${p}[]`);s.push(...u)}}return s}_formatLabel(e){return e.replace(/([A-Z])/g," $1").replace(/^./,t=>t.toUpperCase()).trim()}_summarizeNodeOutput(e,t){if(!t||typeof t!="object")return[];const s=[];t.success!==void 0&&s.push(`Result: ${t.success?"passed":"failed"}`);for(const[o,n]of Object.entries(t))if(!(o==="success"||o==="raw"||o==="nextNode")){if(typeof n=="string"&&n.length<=80)s.push(`${o}: ${n}`);else if(Array.isArray(n)){const r=n.length,c=n.filter(i=>i?.passed===!0).length;if(n.some(i=>i?.passed!==void 0)){const i=r-c;s.push(`${o}: ${c}/${r} passed${i?`, ${i} failed`:""}`)}else s.push(`${o}: ${r} items`)}if(s.length>=4)break}return s}async run(e,t={}){if(!this.entryPoint)throw new Error("No entry point set for graph");const s=t.cwd||process.cwd();ne({path:E(s,".env")});let o=t.config||{};if(!o||Object.keys(o).length===0)try{const l=E(s,".zibby.config.js");b(l)&&(o=(await import(l)).default||{})}catch{}process.env.EXECUTION_ID&&!o.agent?.strictMode&&(o.agent={...o.agent,strictMode:!0});let n=t.agentType;if(!n){const l=o?.agent;l?.provider?n=l.provider:l?.gemini?n="gemini":l?.claude?n="claude":l?.cursor?n="cursor":l?.codex?n="codex":n=process.env.AGENT_TYPE||"cursor"}const r=t.contextConfig||e?.config?.contextConfig||e?.config?.context||o?.context||{};if(this.stateSchema){const l=this.stateSchema.safeParse(t);if(!l.success){const m=l.error.issues.map(w=>`${w.path.join(".")}: ${w.message}`);throw console.error("\u274C Initial state validation failed:"),m.forEach(w=>console.error(` - ${w}`)),new Error(`State validation failed: ${m.join(", ")}`)}S.step("State validated against schema")}const c=pe(),p=t.sessionPath||c;p||fe();const{sessionPath:i,sessionTimestamp:u,sessionId:g}=me({cwd:s,config:o,traceFrom:"WorkflowGraph.run",initialState:{sessionPath:p,sessionTimestamp:t.sessionTimestamp}});S.step(`Session ${g}`);const v=await oe.loadContext(t.specPath||"",s,r);Object.keys(v).length>0&&S.step(`Context loaded: ${Object.keys(v).join(", ")}`);let P=t.outputPath;!P&&t.specPath&&(e?.calculateOutputPath?P=e.calculateOutputPath(t.specPath):console.warn(`\u26A0\uFE0F outputPath not resolved (specPath=${t.specPath})`));const d=new ee({...t,config:o,agentType:n,outputPath:P,sessionPath:i,sessionTimestamp:u,context:v,resolvedTools:this.resolvedToolsMap||{}}),C=new Map;try{await import("@zibby/skills")}catch{}const{getSkill:J}=await import("./skill-registry.js"),M=new Set;for(const[,l]of this.nodes)for(const m of l.config?.skills||[])M.add(m);for(const l of M){const m=J(l);if(typeof m?.middleware=="function")try{const w=await m.middleware();typeof w=="function"&&C.set(l,w)}catch{}}let a=this.entryPoint;const O=[];for(;a&&a!=="END";){const l=E(i,ae);if(b(l)){console.warn(`
5
+ \u{1F6D1} Studio stop requested \u2014 ending workflow.`);try{se(l)}catch{}if(e&&typeof e.cleanup=="function")try{await e.cleanup()}catch{}return S.step("Workflow stopped by Studio"),{success:!0,state:d.getAll(),executionLog:O,stoppedByStudio:!0}}const m=this.nodes.get(a);if(!m)throw new Error(`Node '${a}' not found in graph`);const w=JSON.stringify({sessionPath:i,sessionTimestamp:u,currentNode:a,createdAt:new Date().toISOString(),config:d.get("config")}),q=E(i,V);z(q,w,"utf-8");const D=d.get("config")?.paths?.output||x,K=E(s,D,V);W(E(s,D),{recursive:!0});try{z(K,w,"utf-8")}catch{}const Y=t.onPipelineProgress;if(typeof Y=="function")try{Y({cwd:s,sessionPath:i,sessionId:g,outputBase:d.get("config")?.paths?.output||x,currentNode:a})}catch{}const Q=(this.resolvedToolsMap||{})[a]||null;d.set("_currentNodeTools",Q);const X=d.get("nodeConfigs")||{};d.set("_currentNodeConfig",X[a]||{}),S.nodeStart(a);const R=Date.now(),T=this.nodePrompts.get(a);if(!this._invokeAgent){const _=await import("./agents/index.js");this._invokeAgent=_.invokeAgent}const j=this._invokeAgent,F={state:d,invokeAgent:async(_={},I={})=>{let h=I.prompt||"";if(T)try{h=re.compile(T,{noEscape:!0})(_)}catch(y){throw console.error(`\u274C Template rendering failed for node '${a}':`,y.message),new Error(`Template rendering failed: ${y.message}`,{cause:y})}else if(!h)throw new Error(`No prompt template configured for node '${a}' and no prompt provided in options`);const B={state:d.getAll(),images:I.images||[]},A={model:I.model||d.get("model"),workspace:d.get("workspace"),schema:I.schema,...I};return j(h,B,A)},_coreInvokeAgent:j,agent:e,nodeId:a,promptTemplate:T,getPromptTemplate:()=>T,...d.getAll()};try{const _=(m.config?.skills||[]).map($=>C.get($)).filter(Boolean),I=[...this.middleware,..._];let h;I.length>0?h=await this._composeMiddleware(I,a,async()=>m.execute(F,d),d.getAll(),d):h=await m.execute(F,d);const B=Date.now()-R;if(O.push({node:a,success:h.success,duration:B,timestamp:new Date().toISOString()}),!h.success){if(String(h.error||"").includes("Stopped from Zibby Studio")){if(S.step("Workflow stopped by Studio"),d.set("stoppedByStudio",!0),e&&typeof e.cleanup=="function")try{await e.cleanup()}catch{}return{success:!0,state:d.getAll(),executionLog:O,stoppedByStudio:!0}}d.append("errors",{node:a,error:h.error});const N=m.config?.retries||0,U=`${a}_retries`,k=d.getAll()[U]||0;if(k<N){S.stepInfo(`Retrying (attempt ${k+1}/${N})`),d.update({[U]:k+1,[`${a}_raw`]:h.raw});continue}throw S.nodeFailed(a,h.error,{duration:B}),new Error(`Node '${a}' failed after ${k} attempts: ${h.error}`)}d.update({[a]:h.output});const A=this._summarizeNodeOutput(a,h.output);S.nodeComplete(a,{duration:B,details:A});const y=this.edges.get(a);if(!y)a="END";else if(y.conditional){const $=d.getAll(),N=y.routes($);S.route(a,N),a=N}else a=y}catch(_){throw S.isInsideNode&&S.nodeFailed(a,_.message,{duration:Date.now()-R}),d.set("failed",!0),d.set("failedAt",a),_}}S.graphComplete();const Z={success:!0,state:d.getAll(),executionLog:O};return e&&typeof e.onComplete=="function"&&await e.onComplete(Z),Z}}export{Oe as WorkflowGraph,fe as clearInheritedSessionEnvForFreshRun,he as generateWorkflowSessionId,pe as readStudioPinnedSessionPathFromEnv,me as resolveWorkflowSession,le as shouldTrustInheritedSessionEnv,ue as syncProcessEnvToSession};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/core",
3
- "version": "0.1.30",
3
+ "version": "0.1.33",
4
4
  "description": "Core test automation engine with multi-agent and multi-MCP support",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/core",
3
- "version": "0.1.30",
3
+ "version": "0.1.33",
4
4
  "description": "Core test automation engine with multi-agent and multi-MCP support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@ import { SKILLS } from '@zibby/core';
16
16
 
17
17
  export const CHAT_CONFIG = {
18
18
  name: 'zibby_chat',
19
- skills: [SKILLS.CORE_TOOLS, SKILLS.SKILL_INSTALLER, SKILLS.CHAT_MEMORY],
19
+ skills: [SKILLS.CORE_TOOLS, SKILLS.SKILL_INSTALLER, SKILLS.CHAT_MEMORY, SKILLS.WORKFLOW_BUILDER],
20
20
  timeout: 0,
21
21
 
22
22
  systemPrompt: `You are Zibby, a helpful AI assistant. Capabilities come from installed skills.
@@ -0,0 +1,358 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "id": "preflight",
5
+ "type": "preflight",
6
+ "data": {
7
+ "nodeType": "preflight",
8
+ "label": "preflight"
9
+ }
10
+ },
11
+ {
12
+ "id": "execute_live",
13
+ "type": "execute_live",
14
+ "data": {
15
+ "nodeType": "execute_live",
16
+ "label": "execute_live"
17
+ }
18
+ },
19
+ {
20
+ "id": "generate_script",
21
+ "type": "generate_script",
22
+ "data": {
23
+ "nodeType": "generate_script",
24
+ "label": "generate_script"
25
+ }
26
+ }
27
+ ],
28
+ "edges": [
29
+ {
30
+ "source": "preflight",
31
+ "target": "execute_live"
32
+ },
33
+ {
34
+ "source": "generate_script",
35
+ "target": "END"
36
+ }
37
+ ],
38
+ "nodeConfigs": {
39
+ "preflight": {
40
+ "outputSchema": {
41
+ "jsonSchema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "title": {
45
+ "type": "string",
46
+ "description": "Concise test title (5-10 words, action-oriented). Prefix with ticket ID if found."
47
+ },
48
+ "assertions": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "object",
52
+ "properties": {
53
+ "description": {
54
+ "type": "string",
55
+ "description": "What to verify (e.g., \"User is redirected to dashboard\")"
56
+ },
57
+ "expected": {
58
+ "type": "string",
59
+ "description": "What the expected outcome looks like (e.g., \"URL contains /dashboard\")"
60
+ }
61
+ },
62
+ "required": [
63
+ "description",
64
+ "expected"
65
+ ],
66
+ "additionalProperties": false
67
+ },
68
+ "description": "Every expected result from the spec as a verifiable assertion"
69
+ }
70
+ },
71
+ "required": [
72
+ "title",
73
+ "assertions"
74
+ ],
75
+ "additionalProperties": false
76
+ },
77
+ "variables": [
78
+ {
79
+ "path": "title",
80
+ "type": "string",
81
+ "label": "Concise test title (5-10 words, action-oriented). Prefix with ticket ID if found.",
82
+ "optional": false
83
+ },
84
+ {
85
+ "path": "assertions",
86
+ "type": "array",
87
+ "label": "Every expected result from the spec as a verifiable assertion",
88
+ "optional": false
89
+ },
90
+ {
91
+ "path": "assertions[].description",
92
+ "type": "string",
93
+ "label": "What to verify (e.g., \"User is redirected to dashboard\")",
94
+ "optional": false
95
+ },
96
+ {
97
+ "path": "assertions[].expected",
98
+ "type": "string",
99
+ "label": "What the expected outcome looks like (e.g., \"URL contains /dashboard\")",
100
+ "optional": false
101
+ }
102
+ ]
103
+ }
104
+ },
105
+ "execute_live": {
106
+ "outputSchema": {
107
+ "jsonSchema": {
108
+ "type": "object",
109
+ "properties": {
110
+ "success": {
111
+ "type": "boolean",
112
+ "description": "Whether the test execution completed successfully"
113
+ },
114
+ "steps": {
115
+ "type": "array",
116
+ "items": {
117
+ "type": "string"
118
+ },
119
+ "description": "Array of test steps executed"
120
+ },
121
+ "finalUrl": {
122
+ "type": "string",
123
+ "description": "Final URL after test execution"
124
+ },
125
+ "actions": {
126
+ "type": "array",
127
+ "description": "Detailed array of actions performed with descriptions and reasoning"
128
+ },
129
+ "assertions": {
130
+ "type": "array",
131
+ "items": {
132
+ "type": "object",
133
+ "properties": {
134
+ "description": {
135
+ "type": "string",
136
+ "description": "What was verified"
137
+ },
138
+ "passed": {
139
+ "type": "boolean",
140
+ "description": "Whether the assertion passed"
141
+ },
142
+ "verifiedAfterAction": {
143
+ "type": "number",
144
+ "description": "Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED"
145
+ },
146
+ "evidence": {
147
+ "type": "string",
148
+ "description": "Brief evidence of what was observed"
149
+ }
150
+ },
151
+ "required": [
152
+ "description",
153
+ "passed",
154
+ "verifiedAfterAction"
155
+ ],
156
+ "additionalProperties": false
157
+ },
158
+ "description": "Array of assertions made during test"
159
+ },
160
+ "waits": {
161
+ "type": "array",
162
+ "description": "Array of waits needed for proper test execution"
163
+ },
164
+ "evidenceScreenshots": {
165
+ "type": "array",
166
+ "items": {
167
+ "type": "object",
168
+ "properties": {
169
+ "filename": {
170
+ "type": "string",
171
+ "description": "Descriptive filename pattern: {step-number}-{action-or-state}.png"
172
+ },
173
+ "description": {
174
+ "type": "string",
175
+ "description": "What the screenshot shows and why it is evidence"
176
+ },
177
+ "verdict": {
178
+ "type": "string",
179
+ "enum": [
180
+ "pass",
181
+ "fail",
182
+ "info"
183
+ ],
184
+ "description": "Test verdict: pass/fail for validation points, info for checkpoints"
185
+ }
186
+ },
187
+ "required": [
188
+ "filename",
189
+ "description",
190
+ "verdict"
191
+ ],
192
+ "additionalProperties": false
193
+ },
194
+ "description": "Array of screenshots taken at key validation points throughout the test"
195
+ },
196
+ "browserClosed": {
197
+ "type": "boolean",
198
+ "description": "Whether the browser was properly closed (should always be true)"
199
+ },
200
+ "notes": {
201
+ "type": "string",
202
+ "description": "Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute"
203
+ }
204
+ },
205
+ "required": [
206
+ "success",
207
+ "steps",
208
+ "browserClosed"
209
+ ],
210
+ "additionalProperties": false
211
+ },
212
+ "variables": [
213
+ {
214
+ "path": "success",
215
+ "type": "boolean",
216
+ "label": "Whether the test execution completed successfully",
217
+ "optional": false
218
+ },
219
+ {
220
+ "path": "steps",
221
+ "type": "array",
222
+ "label": "Array of test steps executed",
223
+ "optional": false
224
+ },
225
+ {
226
+ "path": "finalUrl",
227
+ "type": "string",
228
+ "label": "Final URL after test execution",
229
+ "optional": true
230
+ },
231
+ {
232
+ "path": "actions",
233
+ "type": "array",
234
+ "label": "Detailed array of actions performed with descriptions and reasoning",
235
+ "optional": true
236
+ },
237
+ {
238
+ "path": "assertions",
239
+ "type": "array",
240
+ "label": "Array of assertions made during test",
241
+ "optional": true
242
+ },
243
+ {
244
+ "path": "assertions[].description",
245
+ "type": "string",
246
+ "label": "What was verified",
247
+ "optional": false
248
+ },
249
+ {
250
+ "path": "assertions[].passed",
251
+ "type": "boolean",
252
+ "label": "Whether the assertion passed",
253
+ "optional": false
254
+ },
255
+ {
256
+ "path": "assertions[].verifiedAfterAction",
257
+ "type": "number",
258
+ "label": "Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED",
259
+ "optional": false
260
+ },
261
+ {
262
+ "path": "assertions[].evidence",
263
+ "type": "string",
264
+ "label": "Brief evidence of what was observed",
265
+ "optional": true
266
+ },
267
+ {
268
+ "path": "waits",
269
+ "type": "array",
270
+ "label": "Array of waits needed for proper test execution",
271
+ "optional": true
272
+ },
273
+ {
274
+ "path": "evidenceScreenshots",
275
+ "type": "array",
276
+ "label": "Array of screenshots taken at key validation points throughout the test",
277
+ "optional": true
278
+ },
279
+ {
280
+ "path": "evidenceScreenshots[].filename",
281
+ "type": "string",
282
+ "label": "Descriptive filename pattern: {step-number}-{action-or-state}.png",
283
+ "optional": false
284
+ },
285
+ {
286
+ "path": "evidenceScreenshots[].description",
287
+ "type": "string",
288
+ "label": "What the screenshot shows and why it is evidence",
289
+ "optional": false
290
+ },
291
+ {
292
+ "path": "evidenceScreenshots[].verdict",
293
+ "type": "string",
294
+ "label": "Test verdict: pass/fail for validation points, info for checkpoints",
295
+ "optional": false
296
+ },
297
+ {
298
+ "path": "browserClosed",
299
+ "type": "boolean",
300
+ "label": "Whether the browser was properly closed (should always be true)",
301
+ "optional": false
302
+ },
303
+ {
304
+ "path": "notes",
305
+ "type": "string",
306
+ "label": "Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute",
307
+ "optional": true
308
+ }
309
+ ]
310
+ }
311
+ },
312
+ "generate_script": {
313
+ "outputSchema": {
314
+ "jsonSchema": {
315
+ "type": "object",
316
+ "properties": {
317
+ "success": {
318
+ "type": "boolean"
319
+ },
320
+ "scriptPath": {
321
+ "type": "string"
322
+ },
323
+ "method": {
324
+ "type": "string"
325
+ }
326
+ },
327
+ "required": [
328
+ "success",
329
+ "scriptPath",
330
+ "method"
331
+ ],
332
+ "additionalProperties": false
333
+ },
334
+ "variables": [
335
+ {
336
+ "path": "success",
337
+ "type": "boolean",
338
+ "label": "Success",
339
+ "optional": false
340
+ },
341
+ {
342
+ "path": "scriptPath",
343
+ "type": "string",
344
+ "label": "Script Path",
345
+ "optional": false
346
+ },
347
+ {
348
+ "path": "method",
349
+ "type": "string",
350
+ "label": "Method",
351
+ "optional": false
352
+ }
353
+ ]
354
+ }
355
+ }
356
+ },
357
+ "stateSchema": null
358
+ }