@devassure/cli 1.0.8 → 1.0.10

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 CHANGED
@@ -1,4 +1,4 @@
1
- # DevAssure Agent CLI
1
+ # DevAssure AI Testing Agent CLI
2
2
 
3
3
  Command-line interface for DevAssure Testing Agent - Autonomous agent executing tests from natural language instructions.
4
4
 
@@ -401,6 +401,56 @@ tags:
401
401
  - admin
402
402
  ```
403
403
 
404
+ ## Actions
405
+
406
+ Actions group steps that can be reused across multiple tests. Add action YAML files under `.devassure/actions/`. To call an action, use its **name** as a step in a test file.
407
+
408
+ **Fields in an action file:**
409
+
410
+ | Field | Description |
411
+ |-------|-------------|
412
+ | `name` | The name of the action. Used to call the action from the test file. |
413
+ | `description` | A short description of the action. |
414
+ | `steps` | A list of steps that make up the action. |
415
+
416
+ **Example action file** (e.g. `.devassure/actions/login_as_admin.yaml`):
417
+
418
+ ```yaml
419
+ name: login_as_admin
420
+ description: Login to the app as admin using google
421
+ steps:
422
+ - Open admin portal url
423
+ - Click on Google login button
424
+ - Enter admin email and password
425
+ - If MFA is asked, enter the authenticator OTP
426
+ - If allow access is asked, click on allow access
427
+ ```
428
+
429
+ **Example 2** (e.g. `.devassure/actions/add_manager_user.yaml`):
430
+
431
+ ```yaml
432
+ name: add_manager_user
433
+ description: Add a manager user to the app
434
+ steps:
435
+ - Go to users page
436
+ - Click on Add User button
437
+ - Enter email
438
+ - Enter role as Manager
439
+ - Once team dropdown appears assign any team to the user
440
+ - Enter random user details
441
+ - Click on Add User button
442
+ ```
443
+
444
+ **Usage in test file:** reference actions by name in `steps`:
445
+
446
+ ```yaml
447
+ steps:
448
+ - login_as_admin
449
+ - add_manager_user
450
+ - Open users list page
451
+ - Verify if the manager user is added
452
+ ```
453
+
404
454
  ## FAQ
405
455
 
406
456
  **How to add new test cases?**
package/dist/index.js CHANGED
@@ -80,7 +80,7 @@ To free the port: lsof -ti:${i} | xargs kill`):p}}throw new Error("Failed to sta
80
80
  </div>
81
81
  </body>
82
82
  </html>
83
- `),I.close(),a({authCode:g.code,redirectUri:p})):(c.writeHead(400),c.end("Invalid callback"));});I.listen(e,async()=>{let v=new URL(u.authorizationUrl);v.searchParams.set("response_type","code"),v.searchParams.set("client_id",u.clientId),v.searchParams.set("redirect_uri",p),v.searchParams.set("state",t),v.searchParams.set("code_challenge",r),v.searchParams.set("code_challenge_method","S256"),v.searchParams.set("scope","offline_access");try{await this.clientPort.openExternal(v.toString());}catch(c){I.close(),d(new Error(`Failed to open browser: ${c instanceof Error?c.message:String(c)}`));}}),I.on("error",v=>{d(v);}),setTimeout(()=>{I.close(),d(new Error("Authentication timeout. Please try again."));},300*1e3);})}async exchangeCodeForTokens(e,t,s){let r=ct(),n=Buffer.from(`${r.clientId}:${r.clientSecret}`).toString("base64"),i=await fetch(r.tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${n}`},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:s,code_verifier:t})});if(!i.ok){let a=await i.text();throw new Error(`Token exchange failed: ${i.status} ${a}`)}let o=await i.json();if(!o.access_token||!o.refresh_token)throw new Error("Invalid token response");return o}generateRandomString(e){let t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",s="",r=new Uint8Array(e);we.getRandomValues(r);for(let n=0;n<e;n++)s+=t[r[n]%t.length];return s}async generateCodeChallenge(e){let s=new TextEncoder().encode(e),r=await we.subtle.digest("SHA-256",s);return btoa(String.fromCharCode(...new Uint8Array(r))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}};async function Hr(e){let t=process.env.DEVASSURE_TOKEN;if(t&&t.trim()!=="")try{return await e.jobPing(t,"api_key_env_var"),{success:!0,message:""}}catch{return {success:false,message:"auth token in Environment variable DEVASSURE_TOKEN is invalid, add valid token or remove the variable and run `devassure login` to login."}}let s=await ee.getAuthToken();if(s)try{return await e.jobPing(s,"token"),{success:!0,message:""}}catch{return await ee.clearAuthToken(),{success:false,message:"auth-token added is invalid. Please add new token using `devassure add-token` command or run `devassure login` to login from browser."}}if(await ee.getAccessToken())try{return await e.ensureAuthenticated(),{success:!0,message:""}}catch{return {success:false,message:"Not authenticated. Please login to authenticate. Run `devassure login` to login from browser."}}return {success:false,message:"Not authenticated. Please login to authenticate. Run `devassure login` to login from browser."}}async function io(e){try{return {Authorization:`Bearer ${await e.ensureAuthenticated()}`}}catch{return null}}function oo(e){return {Authorization:`Bearer ${e}`}}async function hs(e,t,s={},r,n,i,o,a){let u=no().baseUrl,p=new URL(t,u);o&&Object.entries(o).forEach(([m,w])=>{p.searchParams.set(m,w);});let I=p.toString(),v=i==="no_auth",c=v?void 0:i||await io(a);if(!v&&!c)return {exception:"unauthorized",status:401,statusText:"Unauthorized",headers:new Headers};let h={method:e.toUpperCase(),headers:{"Content-Type":"application/json",...c,...r?.headers},...r};(e==="post"||e==="put")&&s&&(h.body=JSON.stringify(s));let g;try{g=await fetch(I,h);}catch{return {exception:"connection_error",status:0,statusText:"Connection Error",headers:new Headers}}if(g.status===401&&n&&!v)try{let m=await a.refreshToken();if(m?.access_token){let w={...r,headers:{"Content-Type":"application/json",...r?.headers}};return await hs(e,t,s,w,!1,oo(m.access_token),o,a)}}catch{return await ee.clearTokens(),{exception:"unauthorized",status:401,statusText:"Unauthorized - Session expired",headers:g.headers}}let y;try{let m=g.headers.get("content-type");m&&m.includes("application/json")?y=await g.json():y=await g.text();}catch{}return {data:y,status:g.status,statusText:g.statusText,headers:g.headers}}var zr=class{constructor(){this.authService=null,this.webAppCallFn=null;}initialize(e,t){this.authService=e,this.webAppCallFn=t;}async request(e,t={}){if(!this.webAppCallFn||!this.authService)throw new Error("ApiService not initialized. Call initialize() first.");let s=t.method?.toLowerCase()||"get",r;if(t.body)if(typeof t.body=="string")try{r=JSON.parse(t.body);}catch{r=t.body;}else r=t.body;let n=await this.webAppCallFn(s,e,r,{...t,refreshTokenOn401:!t.skipAuth,authHeader:t.skipAuth?"no_auth":void 0},!t.skipAuth,t.skipAuth?"no_auth":void 0,void 0);if(n.exception)throw n.exception==="unauthorized"?new Error("Authentication required. Please login to authenticate."):n.exception==="connection_error"?new Error("Connection error. Please check your network connection."):new Error(`API request failed: ${n.exception}`);if(!n.data&&n.status!==204)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n.data}async get(e,t){return this.request(e,{...t,method:"GET"})}async post(e,t,s){return this.request(e,{...s,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,s){return this.request(e,{...s,method:"PUT",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.request(e,{...t,method:"DELETE"})}},Gr="normal",ds=null;function ao(e){ds=e,jr();}function jr(){ds&&(Gr=ds.getLogLevel());}function co(){return jr(),Gr==="debug"}var F={debug:(...e)=>{co()&&console.log("[DEBUG]",...e);}},lo=class{constructor(){this.clientPort=null,this.pollTimer=null,this.pollInterval=500,this.isPolling=false,this.isPaused=false,this.state=null,this.taskFeedItems=new Map,this.scenarioFeedItems=new Map,this.sessionTracking=null,this.completedTasks=new Set,this.completedScenarios=new Set,this.sessionErrorFeedItemCreated=false,this.sessionWaitFeedItemCreated=false,this.sessionWaitFeedItemUpdatedToSuccess=false,this.sessionNotFoundStartTime=null,this.sessionWaitTimeout=120*1e3,this.maxPollTime=3600*1e3,this.startTime=0,this.resolveCallback=null,this.rejectCallback=null,this.terminationRequested=false,this.activePrompts=new Map,this.pollCallCounter=0;}initialize(e){this.clientPort=e,ao(e);}async start(e){if(!this.clientPort)throw new Error("PollingService not initialized. Call initialize() first.");return F.debug(`PollingService.start() called for session ${e}`),await L.initialize(),this.state={sessionId:e,feedItems:new Map,progress:null,isPolling:false,isCompleted:false},this.startTime=Date.now(),this.isPolling=false,this.isPaused=false,this.terminationRequested=false,this.sessionWaitFeedItemCreated=false,this.sessionWaitFeedItemUpdatedToSuccess=false,this.sessionNotFoundStartTime=null,F.debug(`PollingService: Starting polling for session ${e} with interval ${this.pollInterval}ms`),new Promise((t,s)=>{this.resolveCallback=t,this.rejectCallback=s,this.pollTimer=setInterval(()=>{this.poll().catch(r=>{F.debug(`Error in poll() from interval: ${r instanceof Error?r.message:String(r)}`);});},this.pollInterval),this.poll().catch(r=>{F.debug(`Error in initial poll(): ${r instanceof Error?r.message:String(r)}`);});})}async poll(){if(++this.pollCallCounter,!this.clientPort||!this.state||this.isPaused){F.debug(`poll() - clientPort or state or isPaused is false client port = ${this.clientPort?"true":"false"}, state = ${this.state?"true":"false"}, isPaused = ${this.isPaused?"true":"false"}`);return}if(!this.isPolling){if(this.isPolling=true,this.terminationRequested){this.isPolling=false;return}if(!L){this.isPolling=false;return}try{if(Date.now()-this.startTime>this.maxPollTime){if(F.debug("poll() - max poll time exceeded"),await this.stop(),this.terminationRequested)return;this.rejectCallback?.(new Error("Session execution timed out"));return}let e;try{e=await L.sessionRepository.getById(this.state.sessionId);}catch(a){throw F.debug(`poll() - error getting session from database: ${a instanceof Error?a.message:String(a)}`),a}if(!e){if(!this.sessionWaitFeedItemCreated){let a=`session-wait-${this.state.sessionId}`,d={id:a,name:"Booting up DevAssure Agent",status:"running",stateIndicators:[{status:"running",indicator:"blinkingHexagon",color:"cyan"},{status:"success",indicator:"hexagon",color:"green"}]};this.state.feedItems.set(a,d),this.clientPort.onFeedItem(d),this.sessionWaitFeedItemCreated=!0;}if(this.sessionNotFoundStartTime===null&&(this.sessionNotFoundStartTime=Date.now()),Date.now()-this.sessionNotFoundStartTime>this.sessionWaitTimeout){if(await this.stop(),this.terminationRequested)return;this.rejectCallback?.(new Error("Session not found in database after waiting 2 minutes"));return}this.isPolling=!1;return}this.sessionNotFoundStartTime=null;let t=`session-wait-${this.state.sessionId}`,s=this.state.feedItems.get(t);if(s&&!this.sessionWaitFeedItemUpdatedToSuccess){this.sessionWaitFeedItemUpdatedToSuccess=!0;let a={...s,status:"success",currentMessages:[R("Agent is ready!",{icon:"blinkingHexagon",iconColor:"grey",textColor:"grey"})]};this.state.feedItems.set(t,a),this.clientPort.onFeedItem(a),await new Promise(d=>setTimeout(d,1e3));}this.sessionTracking||(this.sessionTracking=this.initializeSessionTracking(e.status),F.debug(`Session ${this.state.sessionId} tracking initialized with status ${e.status}`));let r=e.status,n=this.isSessionCompleted(r);if(F.debug(`Session ${this.state.sessionId} status check: ${r}, isCompleted: ${n}, wasActive: ${this.sessionTracking.wasActive}, lastStatus: ${this.sessionTracking.lastStatus}`),n&&!this.sessionTracking.wasActive){if(F.debug(`Session ${this.state.sessionId} is already completed (was never active), handling completion immediately`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1,this.sessionTracking.lastStatus=e.status,F.debug(`Session ${this.state.sessionId} is completed, all data read and emitted, stopping polling`),await this.stop(),this.terminationRequested)return;this.state.isCompleted=!0,this.resolveCallback?.();return}await this.handleTasks(this.state.sessionId),await this.handleScenarios(this.state.sessionId),await this.updateProgress(this.state.sessionId),this.sessionTracking.lastStatus!==e.status&&this.sessionTracking.lastStatus&&F.debug(`Session ${this.state.sessionId} status changed: ${this.sessionTracking.lastStatus} -> ${e.status}`);let o=this.sessionTracking.wasActive&&n;if(await this.handleSessionErrors(this.state.sessionId),o?(F.debug(`Session ${this.state.sessionId} transitioned from active to completed`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1):n?(F.debug(`Session ${this.state.sessionId} is completed (was not active before)`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1):(this.sessionTracking.wasActive||F.debug(`Session ${this.state.sessionId} is now active`),this.sessionTracking.wasActive=!0),this.sessionTracking.lastStatus=e.status,!n){let a={sessionId:this.state.sessionId,status:e.status,feedItems:Array.from(this.state.feedItems.values()),progress:this.state.progress||void 0,timestamp:new Date};this.clientPort.onSnapshot(a);}if(n){if(F.debug(`Session ${this.state.sessionId} is completed, all data read and emitted, stopping polling`),await this.stop(),this.terminationRequested)return;this.state.isCompleted=!0,this.resolveCallback?.();return}this.isPolling=!1;}catch(e){if(F.debug(`poll() error: ${e instanceof Error?e.message:String(e)}`),await this.stop(),this.terminationRequested)return;this.rejectCallback?.(e instanceof Error?e:new Error(String(e)));}}}async handleTasks(e){try{let t=await L.taskRepository.getBySessionId(e),s=new ye(L.taskRepository);for(let r of t){if(r.name.startsWith("execute_scenario_"))continue;let n=`task-${r.id}`,i=ye.isTaskActive(r.status),o=ye.isTaskCompleted(r.status);if(o&&this.completedTasks.has(r.id))continue;let a=this.taskFeedItems.get(r.id);a||(a=this.initializeTaskTracking(r.id),this.taskFeedItems.set(r.id,a),F.debug(`Task ${r.id} (${r.name}) tracking initialized`)),a.wasActive&&o&&F.debug(`Task ${r.id} (${r.name}) transitioned from active to completed`);let{taskStepIds:u,messageIds:p}=await this.collectTaskSubItemIds(r.id),I=await s.getPendingUserInputTaskStep(r.id),v=await L.taskRepository.getValidatingUserInputTaskStep(r.id),c=a.lastStatus!==r.status;c&&a.lastStatus&&F.debug(`Task ${r.id} (${r.name}) status changed: ${a.lastStatus} -> ${r.status}`);let h=this.detectSubItemChanges(u,a.lastTaskStepIds);h&&F.debug(`Task ${r.id} (${r.name}) task steps changed: ${u.size} current, ${a.lastTaskStepIds.size} previous`);let g=this.detectSubItemChanges(p,a.lastMessageIds);g&&F.debug(`Task ${r.id} (${r.name}) messages changed: ${p.size} current, ${a.lastMessageIds.size} previous`);let y=I?.id!==a.lastPendingUserInputStepId;y&&F.debug(`Task ${r.id} (${r.name}) pending user input step changed: ${I?.id||"none"} -> ${a.lastPendingUserInputStepId||"none"}`);let m=v?.id!==a.lastValidatingUserInputStepId;if(m&&F.debug(`Task ${r.id} (${r.name}) validating user input step changed: ${v?.id||"none"} -> ${a.lastValidatingUserInputStepId||"none"}`),i){if(a.wasActive=!0,I){let f=`task-${r.id}-step-${I.id}`;if(!this.activePrompts.has(f)&&(y||!a.lastPendingUserInputStepId)){let T=ye.getPromptTypeFromTaskStepData(I.data);if(T){this.pause(),F.debug(`Polling paused for user input on task ${r.id} (${r.name}), step ${I.id}`);let P=new AbortController;this.activePrompts.set(f,P);let x=await s.getTaskMessages(r.id),D=ye.mapTaskStatusToFeedStatus(r.status),O=ye.createFeedItem(r,x);O.onInput=!0,O.currentPrompt={message:I.label||"Please provide input:",type:T==="text"?"text":"confirm",placeholder:T==="text"?"Enter value...":void 0},this.state.feedItems.set(n,O),this.clientPort.onFeedItem(O);try{let G=await this.clientPort.userPrompt({type:T==="text"?"text":"confirm",message:I.label||"Please provide input:",placeholder:T==="text"?"Enter value...":void 0},P.signal);if(G.ok&&G.value!==void 0){let U=(typeof G.value=="boolean",String(G.value));await L.taskRepository.updateTaskStepUserInput(I.id,U),F.debug(`User input received for task ${r.id} (${r.name}), step ${I.id}, updated DB with status=validating`);}else F.debug(`User input cancelled for task ${r.id} (${r.name}), step ${I.id}`);}catch(G){F.debug(`User input prompt failed for task ${r.id} (${r.name}), step ${I.id}: ${G instanceof Error?G.message:String(G)}`);}finally{this.activePrompts.delete(f);let G=this.state.feedItems.get(n);if(G){let U={...G};delete U.currentPrompt,U.onInput=!1,this.state.feedItems.set(n,U),this.clientPort.onFeedItem(U);}this.resume(),F.debug(`Polling resumed after user input for task ${r.id} (${r.name})`);}}}}let w=await s.getTaskMessages(r.id),l=ye.mapTaskStatusToFeedStatus(r.status),C=ye.createFeedItem(r,w);I&&!this.activePrompts.has(`task-${r.id}-step-${I.id}`)&&(C.onInput=!0),(!this.state.feedItems.has(n)||c||h||g||y||m)&&(this.state.feedItems.has(n)?F.debug(`Task ${r.id} (${r.name}) feed item updated`):F.debug(`Task ${r.id} (${r.name}) feed item created`),this.state.feedItems.set(n,C),this.clientPort.onFeedItem(C)),a.lastStatus=r.status,a.lastUpdatedAt=new Date().toISOString(),a.lastPendingUserInputStepId=I?.id,a.lastValidatingUserInputStepId=v?.id,a.lastTaskStepIds=u,a.lastMessageIds=p;}else if(o&&!this.completedTasks.has(r.id)){F.debug(`Task ${r.id} (${r.name}) marked as completed`),this.completedTasks.add(r.id),a.wasActive=!1;let w=await s.getTaskMessages(r.id),l=ye.createFeedItem(r,w);this.state.feedItems.set(n,l),this.clientPort.onFeedItem(l),this.state.feedItems.delete(n);}else a.wasActive=!1;}}catch(t){F.debug(`Error handling tasks: ${t}`);}}async handleScenarios(e){try{let t=await L.scenarioRepository.getBySessionId(e),s=new at(L.scenarioRepository);for(let r of t){let n=`scenario-${r.id}`,i=at.isScenarioActive(r.status),o=at.isScenarioCompleted(r.status);if(r.status==="new"||o&&this.completedScenarios.has(r.id))continue;let a=this.scenarioFeedItems.get(r.id);a||(a=this.initializeScenarioTracking(r.id),this.scenarioFeedItems.set(r.id,a),F.debug(`Scenario ${r.id} (${r.title}) tracking initialized`));let d=a.wasActive&&o;d&&F.debug(`Scenario ${r.id} (${r.title}) transitioned from active to completed`),d&&await this.handleScenarioCompletion(r.id);let u=await this.collectScenarioSubItemIds(r.id,r.browser_session_id),p=a.lastStatus!==r.status;p&&a.lastStatus&&F.debug(`Scenario ${r.id} (${r.title}) status changed: ${a.lastStatus} -> ${r.status}`);let I=this.detectSubItemChanges(u.agentStepIds,a.lastAgentStepIds);I&&F.debug(`Scenario ${r.id} (${r.title}) agent steps changed: ${u.agentStepIds.size} current, ${a.lastAgentStepIds.size} previous`);let v=!1;for(let[y,m]of u.agentStepActionIds.entries()){let w=a.lastAgentStepActionIds.get(y)||new Set;if(this.detectSubItemChanges(m,w)){v=!0,F.debug(`Scenario ${r.id} (${r.title}) agent step actions changed for step ${y}: ${m.size} current, ${w.size} previous`);break}}let c=!1;for(let[y,m]of u.agentStepValidationIds.entries()){let w=a.lastAgentStepValidationIds.get(y)||new Set;if(this.detectSubItemChanges(m,w)){c=!0,F.debug(`Scenario ${r.id} (${r.title}) agent step validations changed for step ${y}: ${m.size} current, ${w.size} previous`);break}}let h=this.detectSubItemChanges(u.passedValidationIds,a.lastPassedValidationIds);h&&F.debug(`Scenario ${r.id} (${r.title}) passed validations changed: ${u.passedValidationIds.size} current, ${a.lastPassedValidationIds.size} previous`);let g=this.detectSubItemChanges(u.errorIds,a.lastErrorIds);if(g&&F.debug(`Scenario ${r.id} (${r.title}) errors changed: ${u.errorIds.size} current, ${a.lastErrorIds.size} previous`),i){a.wasActive=!0;let y=await s.getScenarioMessages(r),m=at.createFeedItem(r,y);(!this.state.feedItems.has(n)||p||I||v||c||h||g)&&(this.state.feedItems.has(n)?F.debug(`Scenario ${r.id} (${r.title}) feed item updated`):F.debug(`Scenario ${r.id} (${r.title}) feed item created`),this.state.feedItems.set(n,m),this.clientPort.onFeedItem(m)),a.lastStatus=r.status,a.lastUpdatedAt=new Date().toISOString(),a.lastAgentStepIds=u.agentStepIds,a.lastAgentStepActionIds=u.agentStepActionIds,a.lastAgentStepValidationIds=u.agentStepValidationIds,a.lastPassedValidationIds=u.passedValidationIds,a.lastErrorIds=u.errorIds;}else o&&!this.completedScenarios.has(r.id)?(F.debug(`Scenario ${r.id} (${r.title}) marked as completed`),this.completedScenarios.add(r.id),a.wasActive=!1,this.state.feedItems.delete(n)):a.wasActive=!1;}}catch(t){F.debug(`Error handling scenarios: ${t}`);}}async handleScenarioCompletion(e){try{let s=(await L.scenarioRepository.getBySessionId(this.state.sessionId)).find(u=>u.id===e);if(!s)return;let r=await L.analysisRepository.getLatestScenarioAnalysis(e);if(!r){F.debug(`Scenario ${e} completion: no analysis found`);return}F.debug(`Scenario ${e} (${s.title}) completion: analysis found, score=${r.score}`);let n=`scenario-${s.id}`,i=this.state.feedItems.get(n);if(!i)return;let o=[];o.push({message:`Score: ${r.score==null?"-":r.score} / 100`,styles:{icon:"\u{1F4CA}",textColor:this.getScoreColor(r.score||-1)}}),r.analysis_summary&&o.push({message:`Analysis: ${r.analysis_summary}`,styles:{icon:"\u{1F9E0}",textColor:"white"}}),o.push({message:`Execution Summary: ${r.execution_summary}`,styles:{icon:"hexagon",iconColor:"blueBright",textColor:"white"}});let a=await L.scenarioRepository.getIssuesByParent(r.id,"scenario_analysis");a.length>0&&F.debug(`Scenario ${e} (${s.title}) completion: ${a.length} issues found`);for(let u of a){let p=[];u.issue_type&&p.push(`${u.issue_type}`),u.description&&p.push(`${u.description}`),p.length>0&&o.push({message:p.join(" | "),styles:{icon:"\u{1F41E}",textColor:u.severity==="critical"||u.severity==="high"?"red":"yellow"}});}let d=await L.scenarioRepository.getPassedValidationsByScenarioId(e);d.length>0&&F.debug(`Scenario ${e} (${s.title}) completion: ${d.length} passed validations found`);for(let u of d){let p=[];u.validation_type&&p.push(`${u.validation_type}`),u.description&&p.push(`${u.description}`),p.length>0&&o.push({message:p.join(" | "),styles:{icon:"check",iconColor:"greenBright",textColor:"greenBright"}});}if(o.length>0){let u={...i,completedLogStyle:{icon:"\u{1F9EA}",textColor:"magenta",bold:!0},status:at.mapScenarioStatusToFeedStatus(s.status),currentMessages:o};this.state.feedItems.set(n,u),this.clientPort.onFeedItem(u);}}catch{}}async handleSessionErrors(e){try{let t=await L.errorRepository.getAllByParent(e,"test_session");if(t.length===0)return;F.debug(`Session ${e}: Found ${t.length} session-level errors`);let s=`session-error-${e}`;if(this.sessionErrorFeedItemCreated||this.state.feedItems.has(s))return;let r=[];for(let i of t)i.message&&r.push(this.createErrorMessage(i.message));let n={id:s,name:"Session Error",status:"failed",currentMessages:r.length>0?[r[r.length-1]]:[],allMessages:r.length>1?r.slice(0,-1):[],objectType:"Error"};this.state.feedItems.set(s,n),this.clientPort.onFeedItem(n),this.sessionErrorFeedItemCreated=!0,F.debug(`Session ${e}: Created error feed item with ${t.length} errors`);}catch(t){F.debug(`Error handling session errors: ${t instanceof Error?t.message:String(t)}`);}}async handleSessionCompletion(e){try{let t=await L.sessionRepository.getById(e);if(!t)return;await new Promise(u=>setTimeout(u,1e3)),await this.handleTasks(e);let s=[],r=await L.analysisRepository.getLatestTestSessionAnalysis(e),n=await L.scenarioRepository.getBySessionId(e);if(r){s.push({message:`Score: ${r.score==null?"-":r.score} / 100`,styles:{icon:"\u{1F4CA}",textColor:this.getScoreColor(r.score||-1)}}),s.push({message:`Scenarios run: ${n.length}`,styles:{icon:"\u{1F9EA}",textColor:"white"}}),r.analysis_summary&&s.push({message:`Analysis: ${r.analysis_summary}`,styles:{icon:"\u{1F9E0}",textColor:"white"}});let u=await L.analysisRepository.getIssuesByParent(r.id,"test_session_analysis");u.length>0&&F.debug(`Session ${e} completion: ${u.length} issues found`);for(let p of u){let I=[];p.issue_type&&I.push(`${p.issue_type}`),p.description&&I.push(`${p.description}`),I.length>0&&s.push({message:I.join(" | "),styles:{icon:"\u{1F41E}",textColor:p.severity==="critical"||p.severity==="high"?"red":"yellow"}});}}let i=t.status,o;switch(i){case "success":o="success";break;case "error":case "failing":o="failed";break;case "skipped":o="skipped";break;case "cancelled":o="cancelled";break;default:o="failed";}if(i==="error"){let u=await L.errorRepository.getAllByParent(e,"test_session");if(u.length>0){F.debug(`Session ${e} completion: ${u.length} errors found`);for(let p=0;p<u.length;p++){let I=u[p];I.message&&s.push(this.createErrorMessage(I.message));}}}let a={id:`session-${e}`,name:t.title?`Session: ${t.title}`:`Session: ${e}`,status:o,objectType:"TestSession",completedLogStyle:{icon:"\u{1F3AF}",textColor:"cyanBright",bold:!0},currentMessages:s};this.state.feedItems.set(a.id,a),this.clientPort.onFeedItem(a);let d={sessionId:e,status:t.status,feedItems:Array.from(this.state.feedItems.values()),progress:this.state.progress||void 0,timestamp:new Date};this.clientPort.onSnapshot(d),await new Promise(u=>setTimeout(u,500));}catch(t){F.debug(`Error in handleSessionCompletion: ${t instanceof Error?t.message:String(t)}`);}}async updateProgress(e){try{let t=await L.progressRepository.getTotalScenarioCount(e),s=await L.progressRepository.getCompletedScenarioCount(e);if(t>0){let r=Math.round(s/t*100);this.state.progress={total:t,current:s,percentage:r,type:"number"};}}catch{}}isSessionCompleted(e){return ["success","error","skipped","cancelled"].includes(e)}pause(){this.isPaused=true;}resume(){this.isPaused=false,!this.isPolling&&this.state&&!this.state.isCompleted&&this.poll();}async stop(){await new Promise(e=>setTimeout(e,200)),this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.isPolling=false,this.isPaused=false;}requestTermination(){this.terminationRequested=true,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null);}async stopAndResolve(){await this.stop(),this.state&&(this.state.isCompleted=true),this.resolveCallback?.();}setRate(e){this.pollInterval=e,this.pollTimer&&(this.stop().catch(()=>{}),this.pollTimer=setInterval(()=>this.poll(),this.pollInterval));}createInfoMessage(e){return R(e,{icon:"circle",iconColor:"gray",textColor:"gray"})}createErrorMessage(e){return R(e,{icon:"cross",iconColor:"red",textColor:"red"})}getScoreColor(e){return e<0?"gray":e>=90?"greenBright":e>=80||e>=70?"yellowBright":"redBright"}initializeTaskTracking(e){return {lastStatus:"",wasActive:false,lastUpdatedAt:new Date().toISOString(),lastMessageIds:new Set,lastTaskStepIds:new Set}}initializeScenarioTracking(e){return {lastStatus:"",wasActive:false,lastUpdatedAt:new Date().toISOString(),lastAgentStepIds:new Set,lastAgentStepActionIds:new Map,lastAgentStepValidationIds:new Map,lastPassedValidationIds:new Set,lastErrorIds:new Set}}initializeSessionTracking(e){return {lastStatus:e,wasActive:false,lastErrorIds:new Set}}detectSubItemChanges(e,t){if(e.size!==t.size)return true;for(let s of e)if(!t.has(s))return true;return false}async collectTaskSubItemIds(e){let t=await L.taskRepository.getStepsByTaskId(e),s=new Set(t.map(u=>u.id)),r=await L.taskRepository.getMessagesByParent(e,"task"),n=t.map(u=>L.taskRepository.getMessagesByParent(u.id,"step")),o=(await Promise.all(n)).flat(),a=[...r,...o],d=new Set(a.map(u=>u.id));return {taskStepIds:s,messageIds:d}}async collectScenarioSubItemIds(e,t){let s=new Set,r=new Map,n=new Map;if(t){let u=await L.scenarioRepository.getAgentStepsByBrowserSessionId(t);for(let p of u){s.add(p.id);let I=await L.scenarioRepository.getAgentStepActionsByStepId(p.id),v=new Set(I.map(g=>g.id));r.set(p.id,v);let c=await L.scenarioRepository.getAgentStepValidationsByStepId(p.id),h=new Set(c.map(g=>g.id));n.set(p.id,h);}}let i=await L.scenarioRepository.getPassedValidationsByScenarioId(e),o=new Set(i.map(u=>u.id)),a=await L.scenarioRepository.getErrorsByScenarioId(e),d=new Set(a.map(u=>u.id));return {agentStepIds:s,agentStepActionIds:r,agentStepValidationIds:n,passedValidationIds:o,errorIds:d}}getController(){return {pause:()=>this.pause(),resume:()=>this.resume(),stop:()=>this.stop(),setRate:e=>this.setRate(e)}}};function ys(e,t={}){return new Promise((s,r)=>{let n=Lr.request(e,t,i=>s(i));n.on("error",r),n.end();})}async function Wr(e){let t=await ys(e,{method:"HEAD"});if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location)return t.resume(),Wr(t.headers.location);if(!t.statusCode||t.statusCode<200||t.statusCode>=300)throw t.resume(),new Error(`HEAD failed: HTTP ${t.statusCode}`);let s=Number(t.headers["content-length"]??0)||0,r=String(t.headers["accept-ranges"]??"").toLowerCase().includes("bytes");return t.resume(),{total:s,acceptRanges:r}}async function uo(e,t,s,r,n){let i=await ys(e,{method:"GET",headers:{Range:`bytes=${t}-${s}`}});if(i.statusCode!==206)throw i.resume(),new Error(`Range not supported or ignored (HTTP ${i.statusCode}).`);i.on("data",a=>n?.(a.length));let o=k.createWriteStream(r,{flags:"w"});await pipeline(i,o);}async function po(e,t){let s=k.createWriteStream(t,{flags:"w"});for(let r of e){let n=k.createReadStream(r);await pipeline(n,s,{end:false});}await new Promise((r,n)=>{s.end(i=>i?n(i):r());});}async function fo(e,t,s){let r=Math.max(1,s?.parts),n=s?.minPartSizeMB*1024*1024,i=s?.emitEveryMs??250,{total:o,acceptRanges:a}=await Wr(e);if(!o)throw new Error("Unknown content-length; cannot parallelize safely.");if(!a||o<n||r===1){let c=await ys(e,{method:"GET"});if(!c.statusCode||c.statusCode<200||c.statusCode>=300)throw c.resume(),new Error(`Download failed: HTTP ${c.statusCode}`);let h=0,g=0;c.on("data",m=>{h+=m.length;let w=Date.now();s?.onProgress&&w-g>=i&&(g=w,s.onProgress({total:o,current:h,percentage:Math.floor(h/o*100)}));});let y=k.createWriteStream(t,{highWaterMark:1024*1024});y.setMaxListeners(50),await pipeline(c,y),s?.onProgress?.({total:o,current:o,percentage:100});return}let d=Math.ceil(o/r),u=[];for(let c=0;c<r;c++){let h=c*d;if(h>=o)break;let g=Math.min(o-1,(c+1)*d-1);u.push({start:h,end:g,partPath:`${t}.part${c}`});}let p=0,I=0,v=c=>{if(p+=c,!s?.onProgress)return;let h=Date.now();h-I>=i&&(I=h,s.onProgress({total:o,current:p,percentage:Math.floor(p/o*100)}));};try{await Promise.all(u.map(c=>uo(e,c.start,c.end,c.partPath,v))),await po(u.map(c=>c.partPath),t),s?.onProgress?.({total:o,current:o,percentage:100});}finally{await Promise.allSettled(u.map(c=>$.unlink(c.partPath)));}}var Sr=promisify(k.mkdir),go=promisify(k.writeFile),mo=promisify(k.unlink),Er="1.0.15",ho="1.0.15",yo="https://devassures3bucket.s3.us-east-1.amazonaws.com",Ir=/devassure-agent,\s*version\s+([\d.]+)/i;function wo(){return process.platform==="win32"?"windows":process.platform==="darwin"?process.arch==="arm64"?"macos":"macos_intel":"linux"}function So(){return process.platform==="win32"?"devassure-agent.exe":"devassure-agent"}function Vr(){return B.join(ft(),"last-downloaded-version")}function Cr(){try{let e=Xe();return k.existsSync(e)&&k.statSync(e).isFile()}catch{return false}}function Yr(){let e=Pe(),t=So(),s=B.join(e,t),r=B.join(e,".devassure-agent-internal");try{k.existsSync(s)&&k.unlinkSync(s);}catch{}try{k.existsSync(r)&&(k.statSync(r).isDirectory()?k.rmSync(r,{recursive:!0}):k.unlinkSync(r));}catch{}}function Eo(){let e=Vr();if(!k.existsSync(e))return null;try{return k.readFileSync(e,"utf8").trim()||null}catch{return null}}function qr(){return new Promise((e,t)=>{let s=Xe(),r=spawn(s,["version"],{shell:false,stdio:["ignore","pipe","pipe"]}),n="",i="";r.stdout&&r.stdout.on("data",o=>{n+=o.toString();}),r.stderr&&r.stderr.on("data",o=>{i+=o.toString();}),r.on("error",t),r.on("close",o=>{if(o!==0){t(new Error(`devassure-agent version exited ${o}: ${i||n}`));return}let a=n.match(Ir)||i.match(Ir);a&&a[1]?e(a[1].trim()):t(new Error(`Could not parse version from: ${n||i}`));});})}function pt(e,t){e?.onFeedItem(t);}function vr(e,t){if(!e)return;let s=t.progress!==void 0&&t.progress!==null?{...t.progress,type:"progressBar",unit:"MB"}:void 0;e.onSnapshot({sessionId:"",status:"setup",feedItems:t.feedItems,progress:s,timestamp:new Date});}async function Io(e,t,s,r){let i={id:"setup-download",name:"Downloading devassure-agent",status:"running"};pt(s,i);let o=r?.progressEmitIntervalMs??300;try{await fo(e,t,{parts:8,minPartSizeMB:10,emitEveryMs:o,onProgress:({total:a,current:d,percentage:u})=>{vr(s,{feedItems:[{...i}],progress:{total:+(a/(1024*1024)).toFixed(2),current:+(d/(1024*1024)).toFixed(2),percentage:u}});}}),await new Promise(a=>setTimeout(a,100)),vr(s,{feedItems:[{...i,status:"success",currentMessages:[R("Download complete.")]}],progress:void 0});}catch(a){try{k.unlinkSync(t);}catch{}throw a}}async function Co(e,t,s){let n={id:"setup-extract",name:"Extracting\u2026",status:"running",currentMessages:[R("Extracting...")]};pt(s,n),new _r.default(e).extractAllTo(t,true),await mo(e).catch(()=>{}),Tr(Xe()),Tr(_i()),await new Promise(o=>setTimeout(o,100)),pt(s,{...n,status:"success",currentMessages:[R("Extracted.")]});}function Tr(e){try{k.chmodSync(e,493);}catch{}}async function cs(e,t){let s=Pe(),r=ft(),n=wo(),i=ho,o=`${yo}/${n}/devassure-agent/${i}/DevAssure-Agent.zip`;await Sr(s,{recursive:true}),await Sr(r,{recursive:true}),Yr();let a=B.join(r,`DevAssure-Agent-${i}.zip`);await Io(o,a,e,t),await Co(a,s,e);let d={id:"setup-version-check",name:"Setting up DevAssure agent",status:"running"};pt(e,d);let u=await qr();await new Promise(p=>setTimeout(p,100)),pt(e,{...d,status:"success"}),await go(Vr(),u,"utf8"),await new Promise(p=>setTimeout(p,100));}async function ws(e,t){let s=t?.force===true,r=t?.progressEmitIntervalMs,n=Ot().environment==="dev";if(n){if(!Cr())throw new Error(`Dev mode: devassure-agent not found. Add devassure-agent and .devassure-agent-internal to ${Pe()}`);return}let i=r!==void 0?{progressEmitIntervalMs:r}:void 0;if(s){if(Yr(),n)throw new Error(`Dev mode: agent cannot be downloaded. Add devassure-agent and .devassure-agent-internal to ${Pe()}`);await cs(e,i);return}if(!Cr()){await cs(e,i);return}let o=Eo();if(!(o&&St.valid(o)&&St.gte(o,Er))){try{let a=await qr();if(St.valid(a)&&St.gte(a,Er))return}catch{}await cs(e,i);}}var Kr=class{constructor(){this.clientPort=null,this.pollingService=null,this.agentProcessBySessionId=new Map,this.activePrompts=new Map;}initialize(e){this.clientPort=e,this.pollingService=new lo,this.pollingService.initialize(e);}async close(){await L.close(),await this.pollingService?.stop();}async executeSession(e,t,s,r,n,i,o,a){if(!this.clientPort||!this.pollingService)throw new Error("SessionService not initialized. Call initialize() first.");await L.initialize(),await ws(this.clientPort??void 0);let d=Xe(),u=["start",`--test-session-id=${e}`,`--project-path=${t}`];r?.trim()?u.push(`--test-case-file=${r}`):(n?.trim()?u.push(`--test-cases-csv=${n}`):s?.trim()&&u.push(`--test-cases-dir=${s}`),i&&u.push(`--filter=${i}`)),o&&u.push("--archive",o),a?.trim()&&u.push(`--environment=${a.trim()}`);let p=this.clientPort?.getLogLevel()==="debug",I=spawn(d,u,{shell:false,stdio:["ignore",p?"pipe":"ignore",p?"pipe":"ignore"]});this.agentProcessBySessionId.set(e,I);let v=async()=>{await this.pollingService?.stop(),I.kill(),this.agentProcessBySessionId.delete(e);};p&&I.stdout&&(I.stdout.on("data",c=>{console.log(">>> [AGENT]: ",c.toString());}),I.stderr&&I.stderr.on("data",c=>{console.log(">>> [AGENT]: [ERROR] ",c.toString());}));try{this.handleUserInputPrompts(e).catch(()=>{}),await this.pollingService.start(e);}finally{await v();}}async sessionExecutionTermination(e){let t=`session-termination-${e}`;try{if(await L.initialize(),await L.sessionRepository.updateStatus(e,"cancelled"),this.clientPort){let i={id:t,name:"Terminating execution...",status:"failing",stateIndicators:[{status:"failing",indicator:"blinkingCircle",color:"red"}],currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}if(await new Promise(i=>setTimeout(i,2e3)),this.pollingService?.requestTermination(),await this.clientPort?.markActiveFeedItemsCancelled?.([t]),await new Promise(i=>setTimeout(i,6e3)),this.clientPort){let i={id:t,name:"Terminated execution.",status:"failing",stateIndicators:[{status:"failing",indicator:"blinkingCircle",color:"red"}],currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}let s=this.agentProcessBySessionId.get(e);if(s&&(s.kill(),this.agentProcessBySessionId.delete(e)),await new Promise(i=>setTimeout(i,200)),this.clientPort){let i={id:t,name:"Execution terminated!",status:"error",currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}let r=await L.sessionRepository.getById(e);r&&r.status!=="cancelled"&&await L.sessionRepository.updateStatus(e,"cancelled"),(await L.errorRepository.getBySessionId(e)).length===0&&await L.errorRepository.createForSession(e,"Execution cancelled by user!"),await this.pollingService?.stopAndResolve();}catch(s){console.error(`Error during session termination: ${s instanceof Error?s.message:String(s)}`);}}async handleUserInputPrompts(e){if(!this.clientPort||!this.pollingService)return;let t=new ye(L.taskRepository),s=true,r=setInterval(async()=>{if(!s){clearInterval(r);return}try{let n=await L.taskRepository.getBySessionId(e);for(let i of n){if(!ye.isTaskActive(i.status))continue;let a=await t.getPendingUserInputTaskStep(i.id);if(a){let d=`task-${i.id}-step-${a.id}`;if(this.activePrompts.has(d))continue;let u=ye.getPromptTypeFromTaskStepData(a.data);if(!u)continue;let p=new AbortController;this.activePrompts.set(d,p);try{let I=await this.clientPort.userPrompt({type:u==="text"?"text":"confirm",message:a.label||"Please provide input:",placeholder:u==="text"?"Enter value...":void 0},p.signal);if(I.ok&&I.value!==void 0){let v=(typeof I.value=="boolean",String(I.value));await L.taskRepository.updateTaskStepUserInput(a.id,v);}}catch{}finally{this.activePrompts.delete(d);}}}}catch{}},500);return new Promise(n=>{let i=setInterval(()=>{(!this.pollingService||!this.pollingService.state||this.pollingService.state.isCompleted)&&(s=false,clearInterval(r),clearInterval(i),n());},100);})}},kt=class{async getBrowserSessionCount(){return await L.browserSessionRepository.getCount()}async getTestSessionCount(){return await L.sessionRepository.getCount()}async getScenarioCount(){return await L.scenarioRepository.getCount()}async getStorageSize(){let e=Te(),t=B.join(e,"data","devassure.db"),s=B.join(e,"reports"),r=0;try{r+=await $.stat(t).then(n=>n.size);}catch{}try{r+=await this.calculateDirectorySize(s);}catch{}return r}async calculateDirectorySize(e){let t=0;try{let s=await $.readdir(e,{withFileTypes:!0});for(let r of s){let n=B.join(e,r.name);if(r.isDirectory())t+=await this.calculateDirectorySize(n);else if(r.isFile())try{let i=await $.stat(n);t+=i.size;}catch{}}}catch{}return t}formatStorageSize(e){if(e===0)return "0 B";let t=1024,s=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(e)/Math.log(t));return `${(e/Math.pow(t,r)).toFixed(2)} ${s[r]??"B"}`}},Zr=class{async getAllSessions(){return await L.sessionRepository.getAllOrderedByCreatedAt()}async getSessionsToDelete(e,t){let s=await this.getAllSessions();if(e!==void 0){let r=new Date;r.setDate(r.getDate()-e);let n=r.toISOString();return s.filter(i=>i.created_at?i.created_at<n:true)}return t!==void 0?t>=s.length?[]:s.slice(t):s}async getCleanupStats(e){let s=(await L.browserSessionRepository.getBySessionIds(e)).length,r=await this.calculateStorageSize(e);return {sessionsToDelete:e.length,browserSessionsToDelete:s,storageToFree:r}}async cleanupSessionsByIds(e){if(e.length===0)return;let t=await L.sessionRepository.getExistingSessionIds(e);await this.deleteSessions(t);}async calculateStorageSize(e){let t=Te(),s=0;for(let r of e){let n=B.join(t,"data",r),i=B.join(t,"reports",r);try{s+=await this.getDirectorySize(n);}catch{}try{s+=await this.getDirectorySize(i);}catch{}}return s}async getDirectorySize(e){let t=0;try{let s=await $.readdir(e,{withFileTypes:!0});for(let r of s){let n=B.join(e,r.name);if(r.isDirectory())t+=await this.getDirectorySize(n);else if(r.isFile())try{let i=await $.stat(n);t+=i.size;}catch{}}}catch{}return t}async deleteAllData(){await q(async()=>{await W.run("BEGIN TRANSACTION");try{await W.run("DELETE FROM AgentStepAction"),await W.run("DELETE FROM AgentStepValidation"),await W.run("DELETE FROM AgentStep"),await W.run("DELETE FROM PassedValidation"),await W.run("DELETE FROM TaskStep"),await W.run("DELETE FROM Task"),await W.run("DELETE FROM Error"),await W.run("DELETE FROM Message"),await W.run("DELETE FROM Issue"),await W.run("DELETE FROM BrowserSession"),await W.run("DELETE FROM ScenarioAnalysis"),await W.run("DELETE FROM Scenario"),await W.run("DELETE FROM TestSessionAnalysis"),await W.run("DELETE FROM ReportsRender"),await W.run("DELETE FROM SessionData"),await W.run("DELETE FROM TestSession"),await W.run("COMMIT");}catch(r){throw await W.run("ROLLBACK"),r}});let e=Te(),t=B.join(e,"data"),s=B.join(e,"reports");try{let r=await $.readdir(t,{withFileTypes:!0});for(let n of r)n.isDirectory()&&await $.rm(B.join(t,n.name),{recursive:!0,force:!0});}catch{}try{let r=await $.readdir(s,{withFileTypes:!0});for(let n of r)n.isDirectory()&&await $.rm(B.join(s,n.name),{recursive:!0,force:!0});}catch{}}async deleteSessions(e,t){if(e.length===0&&!t)return;if(t){await this.deleteAllData();return}let s=await L.scenarioRepository.getScenarioIdsBySessionIds(e),r=await L.browserSessionRepository.getBrowserSessionIdsBySessionIds(e),n=[];for(let d of e){let u=await L.taskRepository.getBySessionId(d);n.push(...u.map(p=>p.id));}let i=[];for(let d of n){let u=await L.taskRepository.getStepsByTaskId(d);i.push(...u.map(p=>p.id));}let o=[];for(let d of s){let u=await L.analysisRepository.getLatestScenarioAnalysis(d);u&&o.push(u.id);}let a=[];for(let d of e){let u=await L.analysisRepository.getLatestTestSessionAnalysis(d);u&&a.push(u.id);}await q(async()=>{await W.run("BEGIN TRANSACTION");try{let d=L.cleanupRepository;await d.deleteAgentStepActionsByBrowserSessionIds(r),await d.deleteAgentStepValidationsByBrowserSessionIds(r),await d.deleteAgentStepsByBrowserSessionIds(r),await d.deletePassedValidationsByScenarioIds(s),await d.deleteTaskStepsBySessionIds(e),await d.deleteTasksBySessionIds(e),await d.deleteErrorsBySessionIds(e),await d.deleteMessagesBySessionIds(e,s,r,n,i),await d.deleteIssuesByAnalysisIds(o,a),await d.deleteBrowserSessionsBySessionIds(e),await d.deleteScenarioAnalysesByScenarioIds(s),await d.deleteScenariosBySessionIds(e),await d.deleteTestSessionAnalysesBySessionIds(e),await d.deleteReportsRendersBySessionIds(e),await d.deleteSessionDataBySessionIds(e),await d.deleteTestSessionsByIds(e),await W.run("COMMIT");}catch(d){throw await W.run("ROLLBACK"),d}}),await this.deleteSessionFolders(e);}async deleteSessionFolders(e){let t=Te();for(let s of e){let r=B.join(t,"data",s),n=B.join(t,"reports",s);try{await $.rm(r,{recursive:!0,force:!0});}catch{}try{await $.rm(n,{recursive:!0,force:!0});}catch{}}}};function vo(){let e=new Date,t=r=>r.toString().padStart(2,"0"),s=e.getMilliseconds().toString().padStart(3,"0").padEnd(6,"0");return `${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:${t(e.getSeconds())}.${s}`}var Bt=class{constructor(){this.clientPort=null;}initialize(e){this.clientPort=e;}async archiveReport(e,t){let s=Xe(),r=["archive-results","--target-path",e,"--test-session-id",t],n=this.clientPort?.getLogLevel()==="debug",i=spawn(s,r,{shell:false,stdio:n?["ignore","pipe","pipe"]:"inherit"});return n&&i.stdout&&i.stdout.on("data",o=>{console.log(">>> [AGENT]: ",o.toString());}),n&&i.stderr&&i.stderr.on("data",o=>{console.log(">>> [AGENT]: [ERROR] ",o.toString());}),new Promise(o=>{i.on("exit",(a,d)=>{o(a??(d?1:0));});})}async openReport(e,t,s,r){await L.initialize();let n=e;if(!r&&t){let I=await L.sessionRepository.getLastSessionId();if(!I)throw new Error("No test sessions found");n=I;}let i=vo(),o=Xe(),a=["open-report"];r?a.push("--archive",r):n&&a.push(`--test-session-id=${n}`);let d=spawn(o,a,{shell:false});this.clientPort?.getLogLevel()==="debug"&&(d.stdout.on("data",I=>{console.log(">>> [AGENT]: ",I.toString());}),d.stderr.on("data",I=>{console.log(">>> [AGENT]: [ERROR] ",I.toString());}));let u=()=>{d.killed||d.kill();},p=()=>{d.killed||d.kill();};process.on("SIGINT",p),process.on("SIGTERM",p);try{let I=await this.pollForReportsRender(i,n);if(!I)throw new Error("Failed to start report server - no ReportsRender entry found");if(!I.host||I.port===null)throw new Error("Report server started but host/port not available");let v=`http://${I.host}:${I.port}`;if(s){let c="report-server",h={id:c,name:`Running report server at ${v}`,status:"running",currentMessages:[R(`Report server running at ${v}`)]};await s.addFeedItem(h),await new Promise(async g=>{let y=!1,m=async()=>{if(!y){y=!0,u();try{await s.updateFeedItem(c,{status:"success",currentMessages:[R("Report server closed")],currentPrompt:void 0});}catch(l){F.debug(">>> [REPORT SERVICE]: Feed item already removed",{error:l});}g();}},w={message:"Stop reporting server.",type:"userKill",callbackFunction:async l=>{F.debug(">>> [REPORT SERVICE]: User kill callback",{proceed:l}),l||await m();}};await s.updateFeedItem(c,{currentPrompt:w}),d.on("exit",async()=>{if(!y){y=!0;try{await s.updateFeedItem(c,{status:"success",currentMessages:[R("Report server closed")],currentPrompt:void 0});}catch(l){F.debug(">>> [REPORT SERVICE]: Feed item already removed on exit",{error:l});}g();}});});}return {url:v,process:d}}catch(I){throw u(),I}finally{process.removeListener("SIGINT",p),process.removeListener("SIGTERM",p);}}async pollForReportsRender(e,t,s=60,r=500){for(let n=0;n<s;n++)try{let i;if(t)i=await L.reportsRenderRepository.getLatestByTestSessionIdAfter(t,e);else {let o=await L.reportsRenderRepository.getByCreatedAtAfter(e);i=o.length>0?o[0]:void 0;}if(i&&i.host&&i.port!==null)return {host:i.host,port:i.port};await new Promise(o=>setTimeout(o,r));}catch{await new Promise(o=>setTimeout(o,r));}return null}},Xr="00:00:00";function To(e){if(e<0||!Number.isFinite(e))return Xr;let t=Math.floor(e/1e3),s=Math.floor(t/3600),r=Math.floor(t%3600/60),n=t%60,i=o=>o.toString().padStart(2,"0");return `${i(s)}:${i(r)}:${i(n)}`}function br(e){if(e==null||e==="")return null;let t=Date.parse(e);return Number.isNaN(t)?null:t}var Jr=class{async getSessionSummary(e){let t=await L.sessionRepository.getById(e);if(!t)throw new Error("Session not found");let r=(await L.scenarioRepository.getBySessionId(e)).length,n=await L.analysisRepository.getLatestTestSessionAnalysis(e),i=null,o=0;n&&(i=n.score??null,o=(await L.analysisRepository.getIssuesByParent(n.id,"test_session_analysis")).length);let a=await L.scenarioRepository.getPassedValidationCountByTestSessionId(e),d=await L.analysisRepository.getFailedValidationCountByTestSessionId(e),u=null,p=br(t.created_at),I=br(t.end_time);p!=null&&I!=null&&(u=I-p);let v=u!=null?To(u):Xr;return {sessionId:e,title:t.title??null,environment:t.test_environment??null,scenarios:r,score:i,passedValidations:a,failedValidations:d,failureGroups:o,durationMs:u,durationString:v}}},bo=".devassure";function tt(e){let t=process.cwd();return B.join(t,bo)}function $t(e){let t=tt();k.existsSync(t)||k.mkdirSync(t,{recursive:true});}function gt(e,t,s,r){$t();let n=B.join(tt(),e),i="";s&&(i=s+`
83
+ `),I.close(),a({authCode:g.code,redirectUri:p})):(c.writeHead(400),c.end("Invalid callback"));});I.listen(e,async()=>{let v=new URL(u.authorizationUrl);v.searchParams.set("response_type","code"),v.searchParams.set("client_id",u.clientId),v.searchParams.set("redirect_uri",p),v.searchParams.set("state",t),v.searchParams.set("code_challenge",r),v.searchParams.set("code_challenge_method","S256"),v.searchParams.set("scope","offline_access");try{await this.clientPort.openExternal(v.toString());}catch(c){I.close(),d(new Error(`Failed to open browser: ${c instanceof Error?c.message:String(c)}`));}}),I.on("error",v=>{d(v);}),setTimeout(()=>{I.close(),d(new Error("Authentication timeout. Please try again."));},300*1e3);})}async exchangeCodeForTokens(e,t,s){let r=ct(),n=Buffer.from(`${r.clientId}:${r.clientSecret}`).toString("base64"),i=await fetch(r.tokenUrl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${n}`},body:new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:s,code_verifier:t})});if(!i.ok){let a=await i.text();throw new Error(`Token exchange failed: ${i.status} ${a}`)}let o=await i.json();if(!o.access_token||!o.refresh_token)throw new Error("Invalid token response");return o}generateRandomString(e){let t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",s="",r=new Uint8Array(e);we.getRandomValues(r);for(let n=0;n<e;n++)s+=t[r[n]%t.length];return s}async generateCodeChallenge(e){let s=new TextEncoder().encode(e),r=await we.subtle.digest("SHA-256",s);return btoa(String.fromCharCode(...new Uint8Array(r))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}};async function Hr(e){let t=process.env.DEVASSURE_TOKEN;if(t&&t.trim()!=="")try{return await e.jobPing(t,"api_key_env_var"),{success:!0,message:""}}catch{return {success:false,message:"auth token in Environment variable DEVASSURE_TOKEN is invalid, add valid token or remove the variable and run `devassure login` to login."}}let s=await ee.getAuthToken();if(s)try{return await e.jobPing(s,"token"),{success:!0,message:""}}catch{return await ee.clearAuthToken(),{success:false,message:"auth-token added is invalid. Please add new token using `devassure add-token` command or run `devassure login` to login from browser."}}if(await ee.getAccessToken())try{return await e.ensureAuthenticated(),{success:!0,message:""}}catch{return {success:false,message:"Not authenticated. Please login to authenticate. Run `devassure login` to login from browser."}}return {success:false,message:"Not authenticated. Please login to authenticate. Run `devassure login` to login from browser."}}async function io(e){try{return {Authorization:`Bearer ${await e.ensureAuthenticated()}`}}catch{return null}}function oo(e){return {Authorization:`Bearer ${e}`}}async function hs(e,t,s={},r,n,i,o,a){let u=no().baseUrl,p=new URL(t,u);o&&Object.entries(o).forEach(([m,w])=>{p.searchParams.set(m,w);});let I=p.toString(),v=i==="no_auth",c=v?void 0:i||await io(a);if(!v&&!c)return {exception:"unauthorized",status:401,statusText:"Unauthorized",headers:new Headers};let h={method:e.toUpperCase(),headers:{"Content-Type":"application/json",...c,...r?.headers},...r};(e==="post"||e==="put")&&s&&(h.body=JSON.stringify(s));let g;try{g=await fetch(I,h);}catch{return {exception:"connection_error",status:0,statusText:"Connection Error",headers:new Headers}}if(g.status===401&&n&&!v)try{let m=await a.refreshToken();if(m?.access_token){let w={...r,headers:{"Content-Type":"application/json",...r?.headers}};return await hs(e,t,s,w,!1,oo(m.access_token),o,a)}}catch{return await ee.clearTokens(),{exception:"unauthorized",status:401,statusText:"Unauthorized - Session expired",headers:g.headers}}let y;try{let m=g.headers.get("content-type");m&&m.includes("application/json")?y=await g.json():y=await g.text();}catch{}return {data:y,status:g.status,statusText:g.statusText,headers:g.headers}}var zr=class{constructor(){this.authService=null,this.webAppCallFn=null;}initialize(e,t){this.authService=e,this.webAppCallFn=t;}async request(e,t={}){if(!this.webAppCallFn||!this.authService)throw new Error("ApiService not initialized. Call initialize() first.");let s=t.method?.toLowerCase()||"get",r;if(t.body)if(typeof t.body=="string")try{r=JSON.parse(t.body);}catch{r=t.body;}else r=t.body;let n=await this.webAppCallFn(s,e,r,{...t,refreshTokenOn401:!t.skipAuth,authHeader:t.skipAuth?"no_auth":void 0},!t.skipAuth,t.skipAuth?"no_auth":void 0,void 0);if(n.exception)throw n.exception==="unauthorized"?new Error("Authentication required. Please login to authenticate."):n.exception==="connection_error"?new Error("Connection error. Please check your network connection."):new Error(`API request failed: ${n.exception}`);if(!n.data&&n.status!==204)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n.data}async get(e,t){return this.request(e,{...t,method:"GET"})}async post(e,t,s){return this.request(e,{...s,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,s){return this.request(e,{...s,method:"PUT",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.request(e,{...t,method:"DELETE"})}},Gr="normal",ds=null;function ao(e){ds=e,jr();}function jr(){ds&&(Gr=ds.getLogLevel());}function co(){return jr(),Gr==="debug"}var F={debug:(...e)=>{co()&&console.log("[DEBUG]",...e);}},lo=class{constructor(){this.clientPort=null,this.pollTimer=null,this.pollInterval=500,this.isPolling=false,this.isPaused=false,this.state=null,this.taskFeedItems=new Map,this.scenarioFeedItems=new Map,this.sessionTracking=null,this.completedTasks=new Set,this.completedScenarios=new Set,this.sessionErrorFeedItemCreated=false,this.sessionWaitFeedItemCreated=false,this.sessionWaitFeedItemUpdatedToSuccess=false,this.sessionNotFoundStartTime=null,this.sessionWaitTimeout=120*1e3,this.maxPollTime=3600*1e3,this.startTime=0,this.resolveCallback=null,this.rejectCallback=null,this.terminationRequested=false,this.activePrompts=new Map,this.pollCallCounter=0;}initialize(e){this.clientPort=e,ao(e);}async start(e){if(!this.clientPort)throw new Error("PollingService not initialized. Call initialize() first.");return F.debug(`PollingService.start() called for session ${e}`),await L.initialize(),this.state={sessionId:e,feedItems:new Map,progress:null,isPolling:false,isCompleted:false},this.startTime=Date.now(),this.isPolling=false,this.isPaused=false,this.terminationRequested=false,this.sessionWaitFeedItemCreated=false,this.sessionWaitFeedItemUpdatedToSuccess=false,this.sessionNotFoundStartTime=null,F.debug(`PollingService: Starting polling for session ${e} with interval ${this.pollInterval}ms`),new Promise((t,s)=>{this.resolveCallback=t,this.rejectCallback=s,this.pollTimer=setInterval(()=>{this.poll().catch(r=>{F.debug(`Error in poll() from interval: ${r instanceof Error?r.message:String(r)}`);});},this.pollInterval),this.poll().catch(r=>{F.debug(`Error in initial poll(): ${r instanceof Error?r.message:String(r)}`);});})}async poll(){if(++this.pollCallCounter,!this.clientPort||!this.state||this.isPaused){F.debug(`poll() - clientPort or state or isPaused is false client port = ${this.clientPort?"true":"false"}, state = ${this.state?"true":"false"}, isPaused = ${this.isPaused?"true":"false"}`);return}if(!this.isPolling){if(this.isPolling=true,this.terminationRequested){this.isPolling=false;return}if(!L){this.isPolling=false;return}try{if(Date.now()-this.startTime>this.maxPollTime){if(F.debug("poll() - max poll time exceeded"),await this.stop(),this.terminationRequested)return;this.rejectCallback?.(new Error("Session execution timed out"));return}let e;try{e=await L.sessionRepository.getById(this.state.sessionId);}catch(a){throw F.debug(`poll() - error getting session from database: ${a instanceof Error?a.message:String(a)}`),a}if(!e){if(!this.sessionWaitFeedItemCreated){let a=`session-wait-${this.state.sessionId}`,d={id:a,name:"Booting up DevAssure Agent",status:"running",stateIndicators:[{status:"running",indicator:"blinkingHexagon",color:"cyan"},{status:"success",indicator:"hexagon",color:"green"}]};this.state.feedItems.set(a,d),this.clientPort.onFeedItem(d),this.sessionWaitFeedItemCreated=!0;}if(this.sessionNotFoundStartTime===null&&(this.sessionNotFoundStartTime=Date.now()),Date.now()-this.sessionNotFoundStartTime>this.sessionWaitTimeout){if(await this.stop(),this.terminationRequested)return;this.rejectCallback?.(new Error("Session not found in database after waiting 2 minutes"));return}this.isPolling=!1;return}this.sessionNotFoundStartTime=null;let t=`session-wait-${this.state.sessionId}`,s=this.state.feedItems.get(t);if(s&&!this.sessionWaitFeedItemUpdatedToSuccess){this.sessionWaitFeedItemUpdatedToSuccess=!0;let a={...s,status:"success",currentMessages:[R("Agent is ready!",{icon:"blinkingHexagon",iconColor:"grey",textColor:"grey"})]};this.state.feedItems.set(t,a),this.clientPort.onFeedItem(a),await new Promise(d=>setTimeout(d,1e3));}this.sessionTracking||(this.sessionTracking=this.initializeSessionTracking(e.status),F.debug(`Session ${this.state.sessionId} tracking initialized with status ${e.status}`));let r=e.status,n=this.isSessionCompleted(r);if(F.debug(`Session ${this.state.sessionId} status check: ${r}, isCompleted: ${n}, wasActive: ${this.sessionTracking.wasActive}, lastStatus: ${this.sessionTracking.lastStatus}`),n&&!this.sessionTracking.wasActive){if(F.debug(`Session ${this.state.sessionId} is already completed (was never active), handling completion immediately`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1,this.sessionTracking.lastStatus=e.status,F.debug(`Session ${this.state.sessionId} is completed, all data read and emitted, stopping polling`),await this.stop(),this.terminationRequested)return;this.state.isCompleted=!0,this.resolveCallback?.();return}await this.handleTasks(this.state.sessionId),await this.handleScenarios(this.state.sessionId),await this.updateProgress(this.state.sessionId),this.sessionTracking.lastStatus!==e.status&&this.sessionTracking.lastStatus&&F.debug(`Session ${this.state.sessionId} status changed: ${this.sessionTracking.lastStatus} -> ${e.status}`);let o=this.sessionTracking.wasActive&&n;if(await this.handleSessionErrors(this.state.sessionId),o?(F.debug(`Session ${this.state.sessionId} transitioned from active to completed`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1):n?(F.debug(`Session ${this.state.sessionId} is completed (was not active before)`),await this.handleSessionCompletion(this.state.sessionId),this.sessionTracking.wasActive=!1):(this.sessionTracking.wasActive||F.debug(`Session ${this.state.sessionId} is now active`),this.sessionTracking.wasActive=!0),this.sessionTracking.lastStatus=e.status,!n){let a={sessionId:this.state.sessionId,status:e.status,feedItems:Array.from(this.state.feedItems.values()),progress:this.state.progress||void 0,timestamp:new Date};this.clientPort.onSnapshot(a);}if(n){if(F.debug(`Session ${this.state.sessionId} is completed, all data read and emitted, stopping polling`),await this.stop(),this.terminationRequested)return;this.state.isCompleted=!0,this.resolveCallback?.();return}this.isPolling=!1;}catch(e){if(F.debug(`poll() error: ${e instanceof Error?e.message:String(e)}`),await this.stop(),this.terminationRequested)return;this.rejectCallback?.(e instanceof Error?e:new Error(String(e)));}}}async handleTasks(e){try{let t=await L.taskRepository.getBySessionId(e),s=new ye(L.taskRepository);for(let r of t){if(r.name.startsWith("execute_scenario_"))continue;let n=`task-${r.id}`,i=ye.isTaskActive(r.status),o=ye.isTaskCompleted(r.status);if(o&&this.completedTasks.has(r.id))continue;let a=this.taskFeedItems.get(r.id);a||(a=this.initializeTaskTracking(r.id),this.taskFeedItems.set(r.id,a),F.debug(`Task ${r.id} (${r.name}) tracking initialized`)),a.wasActive&&o&&F.debug(`Task ${r.id} (${r.name}) transitioned from active to completed`);let{taskStepIds:u,messageIds:p}=await this.collectTaskSubItemIds(r.id),I=await s.getPendingUserInputTaskStep(r.id),v=await L.taskRepository.getValidatingUserInputTaskStep(r.id),c=a.lastStatus!==r.status;c&&a.lastStatus&&F.debug(`Task ${r.id} (${r.name}) status changed: ${a.lastStatus} -> ${r.status}`);let h=this.detectSubItemChanges(u,a.lastTaskStepIds);h&&F.debug(`Task ${r.id} (${r.name}) task steps changed: ${u.size} current, ${a.lastTaskStepIds.size} previous`);let g=this.detectSubItemChanges(p,a.lastMessageIds);g&&F.debug(`Task ${r.id} (${r.name}) messages changed: ${p.size} current, ${a.lastMessageIds.size} previous`);let y=I?.id!==a.lastPendingUserInputStepId;y&&F.debug(`Task ${r.id} (${r.name}) pending user input step changed: ${I?.id||"none"} -> ${a.lastPendingUserInputStepId||"none"}`);let m=v?.id!==a.lastValidatingUserInputStepId;if(m&&F.debug(`Task ${r.id} (${r.name}) validating user input step changed: ${v?.id||"none"} -> ${a.lastValidatingUserInputStepId||"none"}`),i){if(a.wasActive=!0,I){let f=`task-${r.id}-step-${I.id}`;if(!this.activePrompts.has(f)&&(y||!a.lastPendingUserInputStepId)){let T=ye.getPromptTypeFromTaskStepData(I.data);if(T){this.pause(),F.debug(`Polling paused for user input on task ${r.id} (${r.name}), step ${I.id}`);let P=new AbortController;this.activePrompts.set(f,P);let x=await s.getTaskMessages(r.id),D=ye.mapTaskStatusToFeedStatus(r.status),O=ye.createFeedItem(r,x);O.onInput=!0,O.currentPrompt={message:I.label||"Please provide input:",type:T==="text"?"text":"confirm",placeholder:T==="text"?"Enter value...":void 0},this.state.feedItems.set(n,O),this.clientPort.onFeedItem(O);try{let G=await this.clientPort.userPrompt({type:T==="text"?"text":"confirm",message:I.label||"Please provide input:",placeholder:T==="text"?"Enter value...":void 0},P.signal);if(G.ok&&G.value!==void 0){let U=(typeof G.value=="boolean",String(G.value));await L.taskRepository.updateTaskStepUserInput(I.id,U),F.debug(`User input received for task ${r.id} (${r.name}), step ${I.id}, updated DB with status=validating`);}else F.debug(`User input cancelled for task ${r.id} (${r.name}), step ${I.id}`);}catch(G){F.debug(`User input prompt failed for task ${r.id} (${r.name}), step ${I.id}: ${G instanceof Error?G.message:String(G)}`);}finally{this.activePrompts.delete(f);let G=this.state.feedItems.get(n);if(G){let U={...G};delete U.currentPrompt,U.onInput=!1,this.state.feedItems.set(n,U),this.clientPort.onFeedItem(U);}this.resume(),F.debug(`Polling resumed after user input for task ${r.id} (${r.name})`);}}}}let w=await s.getTaskMessages(r.id),l=ye.mapTaskStatusToFeedStatus(r.status),C=ye.createFeedItem(r,w);I&&!this.activePrompts.has(`task-${r.id}-step-${I.id}`)&&(C.onInput=!0),(!this.state.feedItems.has(n)||c||h||g||y||m)&&(this.state.feedItems.has(n)?F.debug(`Task ${r.id} (${r.name}) feed item updated`):F.debug(`Task ${r.id} (${r.name}) feed item created`),this.state.feedItems.set(n,C),this.clientPort.onFeedItem(C)),a.lastStatus=r.status,a.lastUpdatedAt=new Date().toISOString(),a.lastPendingUserInputStepId=I?.id,a.lastValidatingUserInputStepId=v?.id,a.lastTaskStepIds=u,a.lastMessageIds=p;}else if(o&&!this.completedTasks.has(r.id)){F.debug(`Task ${r.id} (${r.name}) marked as completed`),this.completedTasks.add(r.id),a.wasActive=!1;let w=await s.getTaskMessages(r.id),l=ye.createFeedItem(r,w);this.state.feedItems.set(n,l),this.clientPort.onFeedItem(l),this.state.feedItems.delete(n);}else a.wasActive=!1;}}catch(t){F.debug(`Error handling tasks: ${t}`);}}async handleScenarios(e){try{let t=await L.scenarioRepository.getBySessionId(e),s=new at(L.scenarioRepository);for(let r of t){let n=`scenario-${r.id}`,i=at.isScenarioActive(r.status),o=at.isScenarioCompleted(r.status);if(r.status==="new"||o&&this.completedScenarios.has(r.id))continue;let a=this.scenarioFeedItems.get(r.id);a||(a=this.initializeScenarioTracking(r.id),this.scenarioFeedItems.set(r.id,a),F.debug(`Scenario ${r.id} (${r.title}) tracking initialized`));let d=a.wasActive&&o;d&&F.debug(`Scenario ${r.id} (${r.title}) transitioned from active to completed`),d&&await this.handleScenarioCompletion(r.id);let u=await this.collectScenarioSubItemIds(r.id,r.browser_session_id),p=a.lastStatus!==r.status;p&&a.lastStatus&&F.debug(`Scenario ${r.id} (${r.title}) status changed: ${a.lastStatus} -> ${r.status}`);let I=this.detectSubItemChanges(u.agentStepIds,a.lastAgentStepIds);I&&F.debug(`Scenario ${r.id} (${r.title}) agent steps changed: ${u.agentStepIds.size} current, ${a.lastAgentStepIds.size} previous`);let v=!1;for(let[y,m]of u.agentStepActionIds.entries()){let w=a.lastAgentStepActionIds.get(y)||new Set;if(this.detectSubItemChanges(m,w)){v=!0,F.debug(`Scenario ${r.id} (${r.title}) agent step actions changed for step ${y}: ${m.size} current, ${w.size} previous`);break}}let c=!1;for(let[y,m]of u.agentStepValidationIds.entries()){let w=a.lastAgentStepValidationIds.get(y)||new Set;if(this.detectSubItemChanges(m,w)){c=!0,F.debug(`Scenario ${r.id} (${r.title}) agent step validations changed for step ${y}: ${m.size} current, ${w.size} previous`);break}}let h=this.detectSubItemChanges(u.passedValidationIds,a.lastPassedValidationIds);h&&F.debug(`Scenario ${r.id} (${r.title}) passed validations changed: ${u.passedValidationIds.size} current, ${a.lastPassedValidationIds.size} previous`);let g=this.detectSubItemChanges(u.errorIds,a.lastErrorIds);if(g&&F.debug(`Scenario ${r.id} (${r.title}) errors changed: ${u.errorIds.size} current, ${a.lastErrorIds.size} previous`),i){a.wasActive=!0;let y=await s.getScenarioMessages(r),m=at.createFeedItem(r,y);(!this.state.feedItems.has(n)||p||I||v||c||h||g)&&(this.state.feedItems.has(n)?F.debug(`Scenario ${r.id} (${r.title}) feed item updated`):F.debug(`Scenario ${r.id} (${r.title}) feed item created`),this.state.feedItems.set(n,m),this.clientPort.onFeedItem(m)),a.lastStatus=r.status,a.lastUpdatedAt=new Date().toISOString(),a.lastAgentStepIds=u.agentStepIds,a.lastAgentStepActionIds=u.agentStepActionIds,a.lastAgentStepValidationIds=u.agentStepValidationIds,a.lastPassedValidationIds=u.passedValidationIds,a.lastErrorIds=u.errorIds;}else o&&!this.completedScenarios.has(r.id)?(F.debug(`Scenario ${r.id} (${r.title}) marked as completed`),this.completedScenarios.add(r.id),a.wasActive=!1,this.state.feedItems.delete(n)):a.wasActive=!1;}}catch(t){F.debug(`Error handling scenarios: ${t}`);}}async handleScenarioCompletion(e){try{let s=(await L.scenarioRepository.getBySessionId(this.state.sessionId)).find(u=>u.id===e);if(!s)return;let r=await L.analysisRepository.getLatestScenarioAnalysis(e);if(!r){F.debug(`Scenario ${e} completion: no analysis found`);return}F.debug(`Scenario ${e} (${s.title}) completion: analysis found, score=${r.score}`);let n=`scenario-${s.id}`,i=this.state.feedItems.get(n);if(!i)return;let o=[];o.push({message:`Score: ${r.score==null?"-":r.score} / 100`,styles:{icon:"\u{1F4CA}",textColor:this.getScoreColor(r.score||-1)}}),r.analysis_summary&&o.push({message:`Analysis: ${r.analysis_summary}`,styles:{icon:"\u{1F9E0}",textColor:"white"}}),o.push({message:`Execution Summary: ${r.execution_summary}`,styles:{icon:"hexagon",iconColor:"blueBright",textColor:"white"}});let a=await L.scenarioRepository.getIssuesByParent(r.id,"scenario_analysis");a.length>0&&F.debug(`Scenario ${e} (${s.title}) completion: ${a.length} issues found`);for(let u of a){let p=[];u.issue_type&&p.push(`${u.issue_type}`),u.description&&p.push(`${u.description}`),p.length>0&&o.push({message:p.join(" | "),styles:{icon:"\u{1F41E}",textColor:u.severity==="critical"||u.severity==="high"?"red":"yellow"}});}let d=await L.scenarioRepository.getPassedValidationsByScenarioId(e);d.length>0&&F.debug(`Scenario ${e} (${s.title}) completion: ${d.length} passed validations found`);for(let u of d){let p=[];u.validation_type&&p.push(`${u.validation_type}`),u.description&&p.push(`${u.description}`),p.length>0&&o.push({message:p.join(" | "),styles:{icon:"check",iconColor:"greenBright",textColor:"greenBright"}});}if(o.length>0){let u={...i,completedLogStyle:{icon:"\u{1F9EA}",textColor:"magenta",bold:!0},status:at.mapScenarioStatusToFeedStatus(s.status),currentMessages:o};this.state.feedItems.set(n,u),this.clientPort.onFeedItem(u);}}catch{}}async handleSessionErrors(e){try{let t=await L.errorRepository.getAllByParent(e,"test_session");if(t.length===0)return;F.debug(`Session ${e}: Found ${t.length} session-level errors`);let s=`session-error-${e}`;if(this.sessionErrorFeedItemCreated||this.state.feedItems.has(s))return;let r=[];for(let i of t)i.message&&r.push(this.createErrorMessage(i.message));let n={id:s,name:"Session Error",status:"failed",currentMessages:r.length>0?[r[r.length-1]]:[],allMessages:r.length>1?r.slice(0,-1):[],objectType:"Error"};this.state.feedItems.set(s,n),this.clientPort.onFeedItem(n),this.sessionErrorFeedItemCreated=!0,F.debug(`Session ${e}: Created error feed item with ${t.length} errors`);}catch(t){F.debug(`Error handling session errors: ${t instanceof Error?t.message:String(t)}`);}}async handleSessionCompletion(e){try{let t=await L.sessionRepository.getById(e);if(!t)return;await new Promise(u=>setTimeout(u,1e3)),await this.handleTasks(e);let s=[],r=await L.analysisRepository.getLatestTestSessionAnalysis(e),n=await L.scenarioRepository.getBySessionId(e);if(r){s.push({message:`Score: ${r.score==null?"-":r.score} / 100`,styles:{icon:"\u{1F4CA}",textColor:this.getScoreColor(r.score||-1)}}),s.push({message:`Scenarios run: ${n.length}`,styles:{icon:"\u{1F9EA}",textColor:"white"}}),r.analysis_summary&&s.push({message:`Analysis: ${r.analysis_summary}`,styles:{icon:"\u{1F9E0}",textColor:"white"}});let u=await L.analysisRepository.getIssuesByParent(r.id,"test_session_analysis");u.length>0&&F.debug(`Session ${e} completion: ${u.length} issues found`);for(let p of u){let I=[];p.issue_type&&I.push(`${p.issue_type}`),p.description&&I.push(`${p.description}`),I.length>0&&s.push({message:I.join(" | "),styles:{icon:"\u{1F41E}",textColor:p.severity==="critical"||p.severity==="high"?"red":"yellow"}});}}let i=t.status,o;switch(i){case "success":o="success";break;case "error":case "failing":o="failed";break;case "skipped":o="skipped";break;case "cancelled":o="cancelled";break;default:o="failed";}if(i==="error"){let u=await L.errorRepository.getAllByParent(e,"test_session");if(u.length>0){F.debug(`Session ${e} completion: ${u.length} errors found`);for(let p=0;p<u.length;p++){let I=u[p];I.message&&s.push(this.createErrorMessage(I.message));}}}let a={id:`session-${e}`,name:t.title?`Session: ${t.title}`:`Session: ${e}`,status:o,objectType:"TestSession",completedLogStyle:{icon:"\u{1F3AF}",textColor:"cyanBright",bold:!0},currentMessages:s};this.state.feedItems.set(a.id,a),this.clientPort.onFeedItem(a);let d={sessionId:e,status:t.status,feedItems:Array.from(this.state.feedItems.values()),progress:this.state.progress||void 0,timestamp:new Date};this.clientPort.onSnapshot(d),await new Promise(u=>setTimeout(u,500));}catch(t){F.debug(`Error in handleSessionCompletion: ${t instanceof Error?t.message:String(t)}`);}}async updateProgress(e){try{let t=await L.progressRepository.getTotalScenarioCount(e),s=await L.progressRepository.getCompletedScenarioCount(e);if(t>0){let r=Math.round(s/t*100);this.state.progress={total:t,current:s,percentage:r,type:"number"};}}catch{}}isSessionCompleted(e){return ["success","error","skipped","cancelled"].includes(e)}pause(){this.isPaused=true;}resume(){this.isPaused=false,!this.isPolling&&this.state&&!this.state.isCompleted&&this.poll();}async stop(){await new Promise(e=>setTimeout(e,200)),this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.isPolling=false,this.isPaused=false;}requestTermination(){this.terminationRequested=true,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null);}async stopAndResolve(){await this.stop(),this.state&&(this.state.isCompleted=true),this.resolveCallback?.();}setRate(e){this.pollInterval=e,this.pollTimer&&(this.stop().catch(()=>{}),this.pollTimer=setInterval(()=>this.poll(),this.pollInterval));}createInfoMessage(e){return R(e,{icon:"circle",iconColor:"gray",textColor:"gray"})}createErrorMessage(e){return R(e,{icon:"cross",iconColor:"red",textColor:"red"})}getScoreColor(e){return e<0?"gray":e>=90?"greenBright":e>=80||e>=70?"yellowBright":"redBright"}initializeTaskTracking(e){return {lastStatus:"",wasActive:false,lastUpdatedAt:new Date().toISOString(),lastMessageIds:new Set,lastTaskStepIds:new Set}}initializeScenarioTracking(e){return {lastStatus:"",wasActive:false,lastUpdatedAt:new Date().toISOString(),lastAgentStepIds:new Set,lastAgentStepActionIds:new Map,lastAgentStepValidationIds:new Map,lastPassedValidationIds:new Set,lastErrorIds:new Set}}initializeSessionTracking(e){return {lastStatus:e,wasActive:false,lastErrorIds:new Set}}detectSubItemChanges(e,t){if(e.size!==t.size)return true;for(let s of e)if(!t.has(s))return true;return false}async collectTaskSubItemIds(e){let t=await L.taskRepository.getStepsByTaskId(e),s=new Set(t.map(u=>u.id)),r=await L.taskRepository.getMessagesByParent(e,"task"),n=t.map(u=>L.taskRepository.getMessagesByParent(u.id,"step")),o=(await Promise.all(n)).flat(),a=[...r,...o],d=new Set(a.map(u=>u.id));return {taskStepIds:s,messageIds:d}}async collectScenarioSubItemIds(e,t){let s=new Set,r=new Map,n=new Map;if(t){let u=await L.scenarioRepository.getAgentStepsByBrowserSessionId(t);for(let p of u){s.add(p.id);let I=await L.scenarioRepository.getAgentStepActionsByStepId(p.id),v=new Set(I.map(g=>g.id));r.set(p.id,v);let c=await L.scenarioRepository.getAgentStepValidationsByStepId(p.id),h=new Set(c.map(g=>g.id));n.set(p.id,h);}}let i=await L.scenarioRepository.getPassedValidationsByScenarioId(e),o=new Set(i.map(u=>u.id)),a=await L.scenarioRepository.getErrorsByScenarioId(e),d=new Set(a.map(u=>u.id));return {agentStepIds:s,agentStepActionIds:r,agentStepValidationIds:n,passedValidationIds:o,errorIds:d}}getController(){return {pause:()=>this.pause(),resume:()=>this.resume(),stop:()=>this.stop(),setRate:e=>this.setRate(e)}}};function ys(e,t={}){return new Promise((s,r)=>{let n=Lr.request(e,t,i=>s(i));n.on("error",r),n.end();})}async function Wr(e){let t=await ys(e,{method:"HEAD"});if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location)return t.resume(),Wr(t.headers.location);if(!t.statusCode||t.statusCode<200||t.statusCode>=300)throw t.resume(),new Error(`HEAD failed: HTTP ${t.statusCode}`);let s=Number(t.headers["content-length"]??0)||0,r=String(t.headers["accept-ranges"]??"").toLowerCase().includes("bytes");return t.resume(),{total:s,acceptRanges:r}}async function uo(e,t,s,r,n){let i=await ys(e,{method:"GET",headers:{Range:`bytes=${t}-${s}`}});if(i.statusCode!==206)throw i.resume(),new Error(`Range not supported or ignored (HTTP ${i.statusCode}).`);i.on("data",a=>n?.(a.length));let o=k.createWriteStream(r,{flags:"w"});await pipeline(i,o);}async function po(e,t){let s=k.createWriteStream(t,{flags:"w"});for(let r of e){let n=k.createReadStream(r);await pipeline(n,s,{end:false});}await new Promise((r,n)=>{s.end(i=>i?n(i):r());});}async function fo(e,t,s){let r=Math.max(1,s?.parts),n=s?.minPartSizeMB*1024*1024,i=s?.emitEveryMs??250,{total:o,acceptRanges:a}=await Wr(e);if(!o)throw new Error("Unknown content-length; cannot parallelize safely.");if(!a||o<n||r===1){let c=await ys(e,{method:"GET"});if(!c.statusCode||c.statusCode<200||c.statusCode>=300)throw c.resume(),new Error(`Download failed: HTTP ${c.statusCode}`);let h=0,g=0;c.on("data",m=>{h+=m.length;let w=Date.now();s?.onProgress&&w-g>=i&&(g=w,s.onProgress({total:o,current:h,percentage:Math.floor(h/o*100)}));});let y=k.createWriteStream(t,{highWaterMark:1024*1024});y.setMaxListeners(50),await pipeline(c,y),s?.onProgress?.({total:o,current:o,percentage:100});return}let d=Math.ceil(o/r),u=[];for(let c=0;c<r;c++){let h=c*d;if(h>=o)break;let g=Math.min(o-1,(c+1)*d-1);u.push({start:h,end:g,partPath:`${t}.part${c}`});}let p=0,I=0,v=c=>{if(p+=c,!s?.onProgress)return;let h=Date.now();h-I>=i&&(I=h,s.onProgress({total:o,current:p,percentage:Math.floor(p/o*100)}));};try{await Promise.all(u.map(c=>uo(e,c.start,c.end,c.partPath,v))),await po(u.map(c=>c.partPath),t),s?.onProgress?.({total:o,current:o,percentage:100});}finally{await Promise.allSettled(u.map(c=>$.unlink(c.partPath)));}}var Sr=promisify(k.mkdir),go=promisify(k.writeFile),mo=promisify(k.unlink),Er="1.0.17",ho="1.0.17",yo="https://devassures3bucket.s3.us-east-1.amazonaws.com",Ir=/devassure-agent,\s*version\s+([\d.]+)/i;function wo(){return process.platform==="win32"?"windows":process.platform==="darwin"?process.arch==="arm64"?"macos":"macos_intel":"linux"}function So(){return process.platform==="win32"?"devassure-agent.exe":"devassure-agent"}function Vr(){return B.join(ft(),"last-downloaded-version")}function Cr(){try{let e=Xe();return k.existsSync(e)&&k.statSync(e).isFile()}catch{return false}}function Yr(){let e=Pe(),t=So(),s=B.join(e,t),r=B.join(e,".devassure-agent-internal");try{k.existsSync(s)&&k.unlinkSync(s);}catch{}try{k.existsSync(r)&&(k.statSync(r).isDirectory()?k.rmSync(r,{recursive:!0}):k.unlinkSync(r));}catch{}}function Eo(){let e=Vr();if(!k.existsSync(e))return null;try{return k.readFileSync(e,"utf8").trim()||null}catch{return null}}function qr(){return new Promise((e,t)=>{let s=Xe(),r=spawn(s,["version"],{shell:false,stdio:["ignore","pipe","pipe"]}),n="",i="";r.stdout&&r.stdout.on("data",o=>{n+=o.toString();}),r.stderr&&r.stderr.on("data",o=>{i+=o.toString();}),r.on("error",t),r.on("close",o=>{if(o!==0){t(new Error(`devassure-agent version exited ${o}: ${i||n}`));return}let a=n.match(Ir)||i.match(Ir);a&&a[1]?e(a[1].trim()):t(new Error(`Could not parse version from: ${n||i}`));});})}function pt(e,t){e?.onFeedItem(t);}function vr(e,t){if(!e)return;let s=t.progress!==void 0&&t.progress!==null?{...t.progress,type:"progressBar",unit:"MB"}:void 0;e.onSnapshot({sessionId:"",status:"setup",feedItems:t.feedItems,progress:s,timestamp:new Date});}async function Io(e,t,s,r){let i={id:"setup-download",name:"Downloading devassure-agent",status:"running"};pt(s,i);let o=r?.progressEmitIntervalMs??300;try{await fo(e,t,{parts:8,minPartSizeMB:10,emitEveryMs:o,onProgress:({total:a,current:d,percentage:u})=>{vr(s,{feedItems:[{...i}],progress:{total:+(a/(1024*1024)).toFixed(2),current:+(d/(1024*1024)).toFixed(2),percentage:u}});}}),await new Promise(a=>setTimeout(a,100)),vr(s,{feedItems:[{...i,status:"success",currentMessages:[R("Download complete.")]}],progress:void 0});}catch(a){try{k.unlinkSync(t);}catch{}throw a}}async function Co(e,t,s){let n={id:"setup-extract",name:"Extracting\u2026",status:"running",currentMessages:[R("Extracting...")]};pt(s,n),new _r.default(e).extractAllTo(t,true),await mo(e).catch(()=>{}),Tr(Xe()),Tr(_i()),await new Promise(o=>setTimeout(o,100)),pt(s,{...n,status:"success",currentMessages:[R("Extracted.")]});}function Tr(e){try{k.chmodSync(e,493);}catch{}}async function cs(e,t){let s=Pe(),r=ft(),n=wo(),i=ho,o=`${yo}/${n}/devassure-agent/${i}/DevAssure-Agent.zip`;await Sr(s,{recursive:true}),await Sr(r,{recursive:true}),Yr();let a=B.join(r,`DevAssure-Agent-${i}.zip`);await Io(o,a,e,t),await Co(a,s,e);let d={id:"setup-version-check",name:"Setting up DevAssure agent",status:"running"};pt(e,d);let u=await qr();await new Promise(p=>setTimeout(p,100)),pt(e,{...d,status:"success"}),await go(Vr(),u,"utf8"),await new Promise(p=>setTimeout(p,100));}async function ws(e,t){let s=t?.force===true,r=t?.progressEmitIntervalMs,n=Ot().environment==="dev";if(n){if(!Cr())throw new Error(`Dev mode: devassure-agent not found. Add devassure-agent and .devassure-agent-internal to ${Pe()}`);return}let i=r!==void 0?{progressEmitIntervalMs:r}:void 0;if(s){if(Yr(),n)throw new Error(`Dev mode: agent cannot be downloaded. Add devassure-agent and .devassure-agent-internal to ${Pe()}`);await cs(e,i);return}if(!Cr()){await cs(e,i);return}let o=Eo();if(!(o&&St.valid(o)&&St.gte(o,Er))){try{let a=await qr();if(St.valid(a)&&St.gte(a,Er))return}catch{}await cs(e,i);}}var Kr=class{constructor(){this.clientPort=null,this.pollingService=null,this.agentProcessBySessionId=new Map,this.activePrompts=new Map;}initialize(e){this.clientPort=e,this.pollingService=new lo,this.pollingService.initialize(e);}async close(){await L.close(),await this.pollingService?.stop();}async executeSession(e,t,s,r,n,i,o,a){if(!this.clientPort||!this.pollingService)throw new Error("SessionService not initialized. Call initialize() first.");await L.initialize(),await ws(this.clientPort??void 0);let d=Xe(),u=["start",`--test-session-id=${e}`,`--project-path=${t}`];r?.trim()?u.push(`--test-case-file=${r}`):(n?.trim()?u.push(`--test-cases-csv=${n}`):s?.trim()&&u.push(`--test-cases-dir=${s}`),i&&u.push(`--filter=${i}`)),o&&u.push("--archive",o),a?.trim()&&u.push(`--environment=${a.trim()}`);let p=this.clientPort?.getLogLevel()==="debug",I=spawn(d,u,{shell:false,stdio:["ignore",p?"pipe":"ignore",p?"pipe":"ignore"]});this.agentProcessBySessionId.set(e,I);let v=async()=>{await this.pollingService?.stop(),I.kill(),this.agentProcessBySessionId.delete(e);};p&&I.stdout&&(I.stdout.on("data",c=>{console.log(">>> [AGENT]: ",c.toString());}),I.stderr&&I.stderr.on("data",c=>{console.log(">>> [AGENT]: [ERROR] ",c.toString());}));try{this.handleUserInputPrompts(e).catch(()=>{}),await this.pollingService.start(e);}finally{await v();}}async sessionExecutionTermination(e){let t=`session-termination-${e}`;try{if(await L.initialize(),await L.sessionRepository.updateStatus(e,"cancelled"),this.clientPort){let i={id:t,name:"Terminating execution...",status:"failing",stateIndicators:[{status:"failing",indicator:"blinkingCircle",color:"red"}],currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}if(await new Promise(i=>setTimeout(i,2e3)),this.pollingService?.requestTermination(),await this.clientPort?.markActiveFeedItemsCancelled?.([t]),await new Promise(i=>setTimeout(i,6e3)),this.clientPort){let i={id:t,name:"Terminated execution.",status:"failing",stateIndicators:[{status:"failing",indicator:"blinkingCircle",color:"red"}],currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}let s=this.agentProcessBySessionId.get(e);if(s&&(s.kill(),this.agentProcessBySessionId.delete(e)),await new Promise(i=>setTimeout(i,200)),this.clientPort){let i={id:t,name:"Execution terminated!",status:"error",currentMessages:[],noBorder:!0};this.clientPort.onFeedItem(i);}let r=await L.sessionRepository.getById(e);r&&r.status!=="cancelled"&&await L.sessionRepository.updateStatus(e,"cancelled"),(await L.errorRepository.getBySessionId(e)).length===0&&await L.errorRepository.createForSession(e,"Execution cancelled by user!"),await this.pollingService?.stopAndResolve();}catch(s){console.error(`Error during session termination: ${s instanceof Error?s.message:String(s)}`);}}async handleUserInputPrompts(e){if(!this.clientPort||!this.pollingService)return;let t=new ye(L.taskRepository),s=true,r=setInterval(async()=>{if(!s){clearInterval(r);return}try{let n=await L.taskRepository.getBySessionId(e);for(let i of n){if(!ye.isTaskActive(i.status))continue;let a=await t.getPendingUserInputTaskStep(i.id);if(a){let d=`task-${i.id}-step-${a.id}`;if(this.activePrompts.has(d))continue;let u=ye.getPromptTypeFromTaskStepData(a.data);if(!u)continue;let p=new AbortController;this.activePrompts.set(d,p);try{let I=await this.clientPort.userPrompt({type:u==="text"?"text":"confirm",message:a.label||"Please provide input:",placeholder:u==="text"?"Enter value...":void 0},p.signal);if(I.ok&&I.value!==void 0){let v=(typeof I.value=="boolean",String(I.value));await L.taskRepository.updateTaskStepUserInput(a.id,v);}}catch{}finally{this.activePrompts.delete(d);}}}}catch{}},500);return new Promise(n=>{let i=setInterval(()=>{(!this.pollingService||!this.pollingService.state||this.pollingService.state.isCompleted)&&(s=false,clearInterval(r),clearInterval(i),n());},100);})}},kt=class{async getBrowserSessionCount(){return await L.browserSessionRepository.getCount()}async getTestSessionCount(){return await L.sessionRepository.getCount()}async getScenarioCount(){return await L.scenarioRepository.getCount()}async getStorageSize(){let e=Te(),t=B.join(e,"data","devassure.db"),s=B.join(e,"reports"),r=0;try{r+=await $.stat(t).then(n=>n.size);}catch{}try{r+=await this.calculateDirectorySize(s);}catch{}return r}async calculateDirectorySize(e){let t=0;try{let s=await $.readdir(e,{withFileTypes:!0});for(let r of s){let n=B.join(e,r.name);if(r.isDirectory())t+=await this.calculateDirectorySize(n);else if(r.isFile())try{let i=await $.stat(n);t+=i.size;}catch{}}}catch{}return t}formatStorageSize(e){if(e===0)return "0 B";let t=1024,s=["B","KB","MB","GB","TB"],r=Math.floor(Math.log(e)/Math.log(t));return `${(e/Math.pow(t,r)).toFixed(2)} ${s[r]??"B"}`}},Zr=class{async getAllSessions(){return await L.sessionRepository.getAllOrderedByCreatedAt()}async getSessionsToDelete(e,t){let s=await this.getAllSessions();if(e!==void 0){let r=new Date;r.setDate(r.getDate()-e);let n=r.toISOString();return s.filter(i=>i.created_at?i.created_at<n:true)}return t!==void 0?t>=s.length?[]:s.slice(t):s}async getCleanupStats(e){let s=(await L.browserSessionRepository.getBySessionIds(e)).length,r=await this.calculateStorageSize(e);return {sessionsToDelete:e.length,browserSessionsToDelete:s,storageToFree:r}}async cleanupSessionsByIds(e){if(e.length===0)return;let t=await L.sessionRepository.getExistingSessionIds(e);await this.deleteSessions(t);}async calculateStorageSize(e){let t=Te(),s=0;for(let r of e){let n=B.join(t,"data",r),i=B.join(t,"reports",r);try{s+=await this.getDirectorySize(n);}catch{}try{s+=await this.getDirectorySize(i);}catch{}}return s}async getDirectorySize(e){let t=0;try{let s=await $.readdir(e,{withFileTypes:!0});for(let r of s){let n=B.join(e,r.name);if(r.isDirectory())t+=await this.getDirectorySize(n);else if(r.isFile())try{let i=await $.stat(n);t+=i.size;}catch{}}}catch{}return t}async deleteAllData(){await q(async()=>{await W.run("BEGIN TRANSACTION");try{await W.run("DELETE FROM AgentStepAction"),await W.run("DELETE FROM AgentStepValidation"),await W.run("DELETE FROM AgentStep"),await W.run("DELETE FROM PassedValidation"),await W.run("DELETE FROM TaskStep"),await W.run("DELETE FROM Task"),await W.run("DELETE FROM Error"),await W.run("DELETE FROM Message"),await W.run("DELETE FROM Issue"),await W.run("DELETE FROM BrowserSession"),await W.run("DELETE FROM ScenarioAnalysis"),await W.run("DELETE FROM Scenario"),await W.run("DELETE FROM TestSessionAnalysis"),await W.run("DELETE FROM ReportsRender"),await W.run("DELETE FROM SessionData"),await W.run("DELETE FROM TestSession"),await W.run("COMMIT");}catch(r){throw await W.run("ROLLBACK"),r}});let e=Te(),t=B.join(e,"data"),s=B.join(e,"reports");try{let r=await $.readdir(t,{withFileTypes:!0});for(let n of r)n.isDirectory()&&await $.rm(B.join(t,n.name),{recursive:!0,force:!0});}catch{}try{let r=await $.readdir(s,{withFileTypes:!0});for(let n of r)n.isDirectory()&&await $.rm(B.join(s,n.name),{recursive:!0,force:!0});}catch{}}async deleteSessions(e,t){if(e.length===0&&!t)return;if(t){await this.deleteAllData();return}let s=await L.scenarioRepository.getScenarioIdsBySessionIds(e),r=await L.browserSessionRepository.getBrowserSessionIdsBySessionIds(e),n=[];for(let d of e){let u=await L.taskRepository.getBySessionId(d);n.push(...u.map(p=>p.id));}let i=[];for(let d of n){let u=await L.taskRepository.getStepsByTaskId(d);i.push(...u.map(p=>p.id));}let o=[];for(let d of s){let u=await L.analysisRepository.getLatestScenarioAnalysis(d);u&&o.push(u.id);}let a=[];for(let d of e){let u=await L.analysisRepository.getLatestTestSessionAnalysis(d);u&&a.push(u.id);}await q(async()=>{await W.run("BEGIN TRANSACTION");try{let d=L.cleanupRepository;await d.deleteAgentStepActionsByBrowserSessionIds(r),await d.deleteAgentStepValidationsByBrowserSessionIds(r),await d.deleteAgentStepsByBrowserSessionIds(r),await d.deletePassedValidationsByScenarioIds(s),await d.deleteTaskStepsBySessionIds(e),await d.deleteTasksBySessionIds(e),await d.deleteErrorsBySessionIds(e),await d.deleteMessagesBySessionIds(e,s,r,n,i),await d.deleteIssuesByAnalysisIds(o,a),await d.deleteBrowserSessionsBySessionIds(e),await d.deleteScenarioAnalysesByScenarioIds(s),await d.deleteScenariosBySessionIds(e),await d.deleteTestSessionAnalysesBySessionIds(e),await d.deleteReportsRendersBySessionIds(e),await d.deleteSessionDataBySessionIds(e),await d.deleteTestSessionsByIds(e),await W.run("COMMIT");}catch(d){throw await W.run("ROLLBACK"),d}}),await this.deleteSessionFolders(e);}async deleteSessionFolders(e){let t=Te();for(let s of e){let r=B.join(t,"data",s),n=B.join(t,"reports",s);try{await $.rm(r,{recursive:!0,force:!0});}catch{}try{await $.rm(n,{recursive:!0,force:!0});}catch{}}}};function vo(){let e=new Date,t=r=>r.toString().padStart(2,"0"),s=e.getMilliseconds().toString().padStart(3,"0").padEnd(6,"0");return `${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:${t(e.getSeconds())}.${s}`}var Bt=class{constructor(){this.clientPort=null;}initialize(e){this.clientPort=e;}async archiveReport(e,t){let s=Xe(),r=["archive-results","--target-path",e,"--test-session-id",t],n=this.clientPort?.getLogLevel()==="debug",i=spawn(s,r,{shell:false,stdio:n?["ignore","pipe","pipe"]:"inherit"});return n&&i.stdout&&i.stdout.on("data",o=>{console.log(">>> [AGENT]: ",o.toString());}),n&&i.stderr&&i.stderr.on("data",o=>{console.log(">>> [AGENT]: [ERROR] ",o.toString());}),new Promise(o=>{i.on("exit",(a,d)=>{o(a??(d?1:0));});})}async openReport(e,t,s,r){await L.initialize();let n=e;if(!r&&t){let I=await L.sessionRepository.getLastSessionId();if(!I)throw new Error("No test sessions found");n=I;}let i=vo(),o=Xe(),a=["open-report"];r?a.push("--archive",r):n&&a.push(`--test-session-id=${n}`);let d=spawn(o,a,{shell:false});this.clientPort?.getLogLevel()==="debug"&&(d.stdout.on("data",I=>{console.log(">>> [AGENT]: ",I.toString());}),d.stderr.on("data",I=>{console.log(">>> [AGENT]: [ERROR] ",I.toString());}));let u=()=>{d.killed||d.kill();},p=()=>{d.killed||d.kill();};process.on("SIGINT",p),process.on("SIGTERM",p);try{let I=await this.pollForReportsRender(i,n);if(!I)throw new Error("Failed to start report server - no ReportsRender entry found");if(!I.host||I.port===null)throw new Error("Report server started but host/port not available");let v=`http://${I.host}:${I.port}`;if(s){let c="report-server",h={id:c,name:`Running report server at ${v}`,status:"running",currentMessages:[R(`Report server running at ${v}`)]};await s.addFeedItem(h),await new Promise(async g=>{let y=!1,m=async()=>{if(!y){y=!0,u();try{await s.updateFeedItem(c,{status:"success",currentMessages:[R("Report server closed")],currentPrompt:void 0});}catch(l){F.debug(">>> [REPORT SERVICE]: Feed item already removed",{error:l});}g();}},w={message:"Stop reporting server.",type:"userKill",callbackFunction:async l=>{F.debug(">>> [REPORT SERVICE]: User kill callback",{proceed:l}),l||await m();}};await s.updateFeedItem(c,{currentPrompt:w}),d.on("exit",async()=>{if(!y){y=!0;try{await s.updateFeedItem(c,{status:"success",currentMessages:[R("Report server closed")],currentPrompt:void 0});}catch(l){F.debug(">>> [REPORT SERVICE]: Feed item already removed on exit",{error:l});}g();}});});}return {url:v,process:d}}catch(I){throw u(),I}finally{process.removeListener("SIGINT",p),process.removeListener("SIGTERM",p);}}async pollForReportsRender(e,t,s=60,r=500){for(let n=0;n<s;n++)try{let i;if(t)i=await L.reportsRenderRepository.getLatestByTestSessionIdAfter(t,e);else {let o=await L.reportsRenderRepository.getByCreatedAtAfter(e);i=o.length>0?o[0]:void 0;}if(i&&i.host&&i.port!==null)return {host:i.host,port:i.port};await new Promise(o=>setTimeout(o,r));}catch{await new Promise(o=>setTimeout(o,r));}return null}},Xr="00:00:00";function To(e){if(e<0||!Number.isFinite(e))return Xr;let t=Math.floor(e/1e3),s=Math.floor(t/3600),r=Math.floor(t%3600/60),n=t%60,i=o=>o.toString().padStart(2,"0");return `${i(s)}:${i(r)}:${i(n)}`}function br(e){if(e==null||e==="")return null;let t=Date.parse(e);return Number.isNaN(t)?null:t}var Jr=class{async getSessionSummary(e){let t=await L.sessionRepository.getById(e);if(!t)throw new Error("Session not found");let r=(await L.scenarioRepository.getBySessionId(e)).length,n=await L.analysisRepository.getLatestTestSessionAnalysis(e),i=null,o=0;n&&(i=n.score??null,o=(await L.analysisRepository.getIssuesByParent(n.id,"test_session_analysis")).length);let a=await L.scenarioRepository.getPassedValidationCountByTestSessionId(e),d=await L.analysisRepository.getFailedValidationCountByTestSessionId(e),u=null,p=br(t.created_at),I=br(t.end_time);p!=null&&I!=null&&(u=I-p);let v=u!=null?To(u):Xr;return {sessionId:e,title:t.title??null,environment:t.test_environment??null,scenarios:r,score:i,passedValidations:a,failedValidations:d,failureGroups:o,durationMs:u,durationString:v}}},bo=".devassure";function tt(e){let t=process.cwd();return B.join(t,bo)}function $t(e){let t=tt();k.existsSync(t)||k.mkdirSync(t,{recursive:true});}function gt(e,t,s,r){$t();let n=B.join(tt(),e),i="";s&&(i=s+`
84
84
  `),i+=gs.dump(t,{indent:2,lineWidth:-1}),k.writeFileSync(n,i,"utf8");}function Qr(e,t){gt("app.yaml",e,`# App Configuration
85
85
  # This file contains the description of the web application
86
86
  # Documentation: https://www.npmjs.com/package/@devassure/cli
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devassure/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "DevAssure CLI application",
6
6
  "main": "dist/index.js",