shiplightai 0.1.63 → 0.1.64

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.
@@ -50,6 +50,19 @@ interface ServerRoutesOptions {
50
50
  * `ws://host/ws/debugger/:sessionId/cdp-browser`.
51
51
  */
52
52
  liveviewUrlBuilder?: (sessionId: string, req: Request) => string;
53
+ /**
54
+ * Fallback router for idle sessions (no sandbox process). When the SPA
55
+ * makes file-related API calls (`/api/test-flow`, `/api/files`, etc.)
56
+ * through the session proxy but the sandbox isn't running, the proxy
57
+ * delegates to this router instead of forwarding to the inner process.
58
+ */
59
+ fallbackRouter?: Router;
60
+ /**
61
+ * Absolute path to the artifacts directory (screenshots, videos).
62
+ * When set, `/api/report-assets/*` requests inside a session are served
63
+ * from this directory instead of being proxied to the inner process.
64
+ */
65
+ artifactsDir?: string;
53
66
  }
54
67
  declare function createDebuggerHttpRoutes(opts: ServerRoutesOptions): Router;
55
68
  /**
@@ -1,3 +1,3 @@
1
- import{Router as y}from"express";import b from"express";import*as g from"fs";import*as d from"path";function w(o){return o?d.isAbsolute(o)?d.normalize(o):d.resolve(o):null}function x(o,t){let u=t.headers.host??"127.0.0.1";return`${(t.headers["x-forwarded-proto"]??"ws")==="https"?"wss":"ws"}://${u}/ws/debugger/${o}/cdp-browser`}function v(o){let t=d.basename(o);return t.endsWith(".test.yaml")||t.endsWith(".test.yml")}function P(o){let{manager:t,staticDir:u,resolveYamlPath:p=w,liveviewUrlBuilder:c=x}=o,a=y();a.post("/api/debugger/sessions",b.json(),async(e,s)=>{let r=e.body?.yamlPath;if(typeof r!="string"||!r){s.status(400).json({error:"yamlPath is required"});return}let i=p(r);if(!i){s.status(403).json({error:"Path outside allowed roots"});return}if(!v(i)){s.status(400).json({error:"Not a Shiplight test file (expected *.test.yaml or *.test.yml)"});return}if(!g.existsSync(i)){s.status(404).json({error:"File not found"});return}let l=t.listSessions().find(n=>n.yamlPath===i&&n.status!=="ended");try{let n=await t.openSession(i);s.status(l?200:201).json({sessionId:n.sessionId,yamlPath:n.yamlPath,startedAt:n.startedAt,status:n.status})}catch(n){s.status(500).json({error:n.message})}}),a.get("/api/debugger/sessions",(e,s)=>{s.setHeader("Cache-Control","no-store"),s.json({sessions:t.listSessions().map(r=>({sessionId:r.sessionId,yamlPath:r.yamlPath,startedAt:r.startedAt,status:r.status}))})}),a.delete("/api/debugger/sessions/:sessionId",async(e,s)=>{let r=e.params.sessionId,i=!!t.getSession(r);await t.closeSession(r),s.json({deleted:!0,alreadyGone:!i})}),g.existsSync(u)?a.use("/debugger/static",b.static(u)):console.error(`[debugger] WARNING: debugger static dir missing at ${u} \u2014 iframe routes will 404`),a.get("/debugger/:sessionId/",(e,s)=>{let r=e.params.sessionId;if(!t.getSession(r)){s.status(404).send("Debugger session not found");return}let l=d.join(u,"index.html");if(!g.existsSync(l)){s.status(500).send(`Debugger SPA bundle missing at ${l}`);return}let f=g.readFileSync(l,"utf-8").replace(/(src|href)="\/assets\//g,'$1="/debugger/static/assets/').replace(/(src|href)="\/index\.html/g,'$1="/debugger/static/index.html');s.type("html").send(f)});let m=(e,s,r)=>{let i=e.params.sessionId,l=e.originalUrl,n=`/debugger/${i}`;if(l.startsWith(n)){let h=l.slice(n.length)||"/";e.url=h,Object.defineProperty(e,"originalUrl",{value:h,configurable:!0})}t.httpProxyFor(i,{liveviewUrlBuilder:()=>c(i,e)})(e,s,r)};return a.use("/debugger/:sessionId",m),a}function R(o,t,u,p){let a=(t.url??"").match(/^\/ws\/debugger\/([^/]+)(.*)$/);if(!a){u.write(`HTTP/1.1 404 Not Found\r
1
+ import{Router as R}from"express";import y from"express";import*as c from"fs";import*as l from"path";function P(i){return i?l.isAbsolute(i)?l.normalize(i):l.resolve(i):null}function S(i,t){let u=t.headers.host??"127.0.0.1";return`${(t.headers["x-forwarded-proto"]??"ws")==="https"?"wss":"ws"}://${u}/ws/debugger/${i}/cdp-browser`}function j(i){let t=l.basename(i);return t.endsWith(".test.yaml")||t.endsWith(".test.yml")}function I(i){let{manager:t,staticDir:u,resolveYamlPath:f=P,liveviewUrlBuilder:b=S,fallbackRouter:g,artifactsDir:m}=i,n=R(),w=m?y.static(m):null;n.post("/api/debugger/sessions",y.json(),async(e,s)=>{let r=e.body?.yamlPath;if(typeof r!="string"||!r){s.status(400).json({error:"yamlPath is required"});return}let o=f(r);if(!o){s.status(403).json({error:"Path outside allowed roots"});return}if(!j(o)){s.status(400).json({error:"Not a Shiplight test file (expected *.test.yaml or *.test.yml)"});return}if(!c.existsSync(o)){s.status(404).json({error:"File not found"});return}let d=t.listSessions().find(a=>a.yamlPath===o&&a.status!=="ended");try{let a=t.openSession(o);s.status(d?200:201).json({sessionId:a.sessionId,yamlPath:a.yamlPath,startedAt:a.startedAt,status:a.status})}catch(a){s.status(500).json({error:a.message})}}),n.get("/api/debugger/sessions",(e,s)=>{s.setHeader("Cache-Control","no-store"),s.json({sessions:t.listSessions().map(r=>({sessionId:r.sessionId,yamlPath:r.yamlPath,startedAt:r.startedAt,status:r.status}))})}),n.delete("/api/debugger/sessions/:sessionId",async(e,s)=>{let r=e.params.sessionId,o=!!t.getSession(r);await t.closeSession(r),s.json({deleted:!0,alreadyGone:!o})}),c.existsSync(u)?n.use("/debugger/static",y.static(u)):console.error(`[debugger] WARNING: debugger static dir missing at ${u} \u2014 iframe routes will 404`),n.get("/debugger/:sessionId/",(e,s)=>{let r=e.params.sessionId;if(!t.getSession(r)){s.status(404).send("Debugger session not found");return}let d=l.join(u,"index.html");if(!c.existsSync(d)){s.status(500).send(`Debugger SPA bundle missing at ${d}`);return}let h=c.readFileSync(d,"utf-8").replace(/(src|href)="\/assets\//g,'$1="/debugger/static/assets/').replace(/(src|href)="\/index\.html/g,'$1="/debugger/static/index.html');s.type("html").send(h)});let x=async(e,s,r)=>{let o=e.params.sessionId;if(o==="static")return r();let d=t.getSession(o);if(!d){s.status(404).json({status:"error",message:"Session not found"});return}let a=e.originalUrl,h=`/debugger/${o}`;if(a.startsWith(h)){let p=a.slice(h.length)||"/";e.url=p,Object.defineProperty(e,"originalUrl",{value:p,configurable:!0})}if(w&&e.path.startsWith("/api/report-assets/")){e.url=e.url.replace(/^\/api\/report-assets/,""),w(e,s,r);return}if(d.status==="idle"||d.status==="starting"){let p=`${e.method} ${e.path}`;if(p==="POST /api/int-runner/create-session")try{await t.startSandbox(o)}catch(v){s.status(500).json({status:"error",message:v.message});return}else if(p==="POST /api/int-runner/liveview-url"){s.json({liveviewUrl:"",browserWsUrl:""});return}else if(e.path.startsWith("/api/int-runner/")){s.status(503).json({status:"error",message:"Sandbox not started"});return}else if(g){g(e,s,r);return}else{s.status(503).json({status:"error",message:"Sandbox not started"});return}}t.httpProxyFor(o,{liveviewUrlBuilder:()=>b(o,e)})(e,s,r)};return n.use("/debugger/:sessionId",x),n}function D(i,t,u,f){let g=(t.url??"").match(/^\/ws\/debugger\/([^/]+)(.*)$/);if(!g){u.write(`HTTP/1.1 404 Not Found\r
2
2
  \r
3
- `),u.destroy();return}let m=a[1],e=a[2]||"";e.startsWith("/cdp-browser/page/")?e=e.slice(12):(e==="/cdp-browser"||e==="/cdp-browser/")&&(e=""),o.wsUpgradeFor(m)(t,u,p,e||void 0)}export{P as createDebuggerHttpRoutes,R as handleDebuggerUpgrade};
3
+ `),u.destroy();return}let m=g[1],n=g[2]||"";n.startsWith("/cdp-browser/page/")?n=n.slice(12):(n==="/cdp-browser"||n==="/cdp-browser/")&&(n=""),i.wsUpgradeFor(m)(t,u,f,n||void 0)}export{I as createDebuggerHttpRoutes,D as handleDebuggerUpgrade};
package/dist/index.js CHANGED
@@ -4364,7 +4364,7 @@ ${p.join(`
4364
4364
  `)}function xc(e,t){let i=[],a=t?.version||"unknown";i.push(`// @generated by shiplightai v${a}`),i.push(...bo()),i.push(""),t?.use&&Object.keys(t.use).length>0&&(i.push(`test.use(${JSON.stringify(t.use,null,2)});`),i.push(""));let n=new Set,o={imports:n,actionEntityStore:t?.actionEntityStore},r=t?.testName||"Test Suite",s=Ln(t?.tags);i.push(`test.describe.serial('${s}${_e(r)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(i.push(...bi("beforeAll",e.beforeAll,o,1)),i.push("")),e.beforeEach&&e.beforeEach.length>0&&(i.push(...bi("beforeEach",e.beforeEach,o,1)),i.push(""));for(let c=0;c<e.tests.length;c++){let d=e.tests[c],h=d.timeout||d.skip!==void 0||d.fail!==void 0||d.only||d.slow?{timeout:d.timeout,skip:d.skip,fail:d.fail,only:d.only,slow:d.slow}:void 0;if(d.parameters&&d.parameters.length>0)for(let p of d.parameters){let g=wo(d.testFlow,p.values);i.push(...vi(g,`${_e(d.name)} [${_e(p.name)}]`,o,1,h)),i.push("")}else i.push(...vi(d.testFlow,_e(d.name),o,1,h)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&i.push("")}return e.afterEach&&e.afterEach.length>0&&(i.push(...bi("afterEach",e.afterEach,o,1)),i.push("")),e.afterAll&&e.afterAll.length>0&&i.push(...bi("afterAll",e.afterAll,o,1)),i.push("});"),yo(i,n),i.join(`
4365
4365
  `)}function Ln(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}var vc=["testContext","request"];function xi(e){let t=new Set;function i(a){for(let n of a)switch(n.type){case ue.ACTION:{let r=n.action_entity?.action_data?.kwargs;if(r?.args&&Array.isArray(r.args))for(let s of r.args)typeof s=="string"&&vc.includes(s)&&t.add(s);break}case ue.STEP:i(n.statements);break;case ue.IF_ELSE:{let o=n;i(o.then),o.else&&i(o.else);break}case ue.WHILE_LOOP:i(n.body);break}}return i(e),t}function _c(e){let t=xi(e.statements??[]);if(e.teardown)for(let i of xi(e.teardown))t.add(i);return t}function Rn(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function vi(e,t,i,a=0,n){let o=" ".repeat(a),r=[],s=_c(e),l=Rn(s),c=n?.only?"test.only":"test";r.push(`${o}${c}('${t}', async (${l}) => {`),n?.skip===!0?r.push(`${o} test.skip();`):typeof n?.skip=="string"&&r.push(`${o} test.skip(true, '${_e(n.skip)}');`),n?.fail===!0?r.push(`${o} test.fail();`):typeof n?.fail=="string"&&r.push(`${o} test.fail(true, '${_e(n.fail)}');`),n?.slow&&r.push(`${o} test.slow();`),n?.timeout&&r.push(`${o} test.setTimeout(${n.timeout});`);let d=e.teardown&&e.teardown.length>0,h=a+1;if(d){if(r.push(`${o} try {`),e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let g=pe(e.statements,h+1,i);r.push(...g)}r.push(`${o} } finally {`),r.push(`${o} // Teardown`);let p=pe(e.teardown,h+1,i,"teardown");r.push(...p),r.push(`${o} }`)}else if(e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let p=pe(e.statements,h,i);r.push(...p)}return r.push(`${o}});`),r}function mo(e,t,i){let a=[],n=fo(t),o=xi(n),r=Rn(o);return a.push(`test.${e}(async (${r}) => {`),a.push(...pe(n,1,i,e)),a.push("});"),a}function bi(e,t,i,a){let n=" ".repeat(a),o=[],r=fo(t);if(e==="beforeAll"||e==="afterAll"){let l={...i,noAgent:!0};o.push(`${n}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${n} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...pe(r,a+1,l,e)),o.push(`${n} await page.close();`),o.push(`${n}});`)}else{let l=xi(r),c=Rn(l);o.push(`${n}test.${e}(async (${c}) => {`),o.push(...pe(r,a+1,i,e)),o.push(`${n}});`)}return o}function fo(e){let i=oc({goal:"_hook",statements:e});return me(i).statements??[]}function wo(e,t){let i=Mn(e);for(let[a,n]of Object.entries(t))i=i.split(`<<${a}>>`).join(String(n));return me(i)}function bo(){return["import { test, expect } from 'shiplightai/fixture';"]}function yo(e,t){if(t.size>0){let i=0;for(let n=0;n<e.length;n++)e[n].startsWith("import ")&&(i=n+1);let a=Array.from(t);e.splice(i,0,...a)}}var go=5;function vo(e,t,i){let a={expandingPaths:new Set([Nn(t)]),depth:0,referencedPaths:new Set,basePath:i},n={...e};Array.isArray(n.statements)&&(n.statements=Ne(n.statements,t,a)),Array.isArray(n.teardown)&&(n.teardown=Ne(n.teardown,t,a));for(let o of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(n[o])&&(n[o]=Ne(n[o],t,a));return{doc:n,referencedTemplatePaths:Array.from(a.referencedPaths)}}function Ne(e,t,i){let a=[];for(let n of e)if(Mc(n)){let o=Ic(n,t,i);a.push(o)}else a.push(Pc(n,t,i));return a}function Mc(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function Ic(e,t,i){if(i.depth>=go)throw new Error(`Template expansion exceeded maximum depth of ${go}. Check for deeply nested or circular template references.`);let a=Nn(Ac(t),e.template),n=!Tc(a)&&i.basePath?Nn(i.basePath,e.template):a;if(i.expandingPaths.has(n))throw new Error(`Circular template reference detected: ${n} is already being expanded. Stack: ${Array.from(i.expandingPaths).join(" \u2192 ")} \u2192 ${n}`);i.referencedPaths.add(n);let o;try{o=Ec(n,"utf-8")}catch(f){throw new Error(`Failed to read template file: ${n} (referenced from ${t}): ${f.message}`)}let r=po(o);if(!r||typeof r!="object")throw new Error(`Invalid template file: ${n} \u2014 expected a YAML object`);let s=r.params||[],l=e.params||{};for(let f of s)if(!(f in l))throw new Error(`Template ${e.template} requires param "${f}" but it was not provided. Required params: [${s.join(", ")}]`);let c=r.statements;if(!Array.isArray(c))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(l).length>0){let y=$c(c);for(let[b,m]of Object.entries(l))y=y.split(`<<${b}>>`).join(String(m));c=po(y)}let d={expandingPaths:new Set([...i.expandingPaths,n]),depth:i.depth+1,referencedPaths:i.referencedPaths},h=Ne(c,n,d),g={STEP:r.name||e.template.replace(/\.yaml$/,"").split("/").pop()||e.template,template_path:e.template,statements:h};return Object.keys(l).length>0&&(g.template_params=l),g}function Pc(e,t,i){if(typeof e!="object"||e===null)return e;let a={...e};return Array.isArray(a.statements)&&(a.statements=Ne(a.statements,t,i)),Array.isArray(a.THEN)&&(a.THEN=Ne(a.THEN,t,i)),Array.isArray(a.ELSE)&&(a.ELSE=Ne(a.ELSE,t,i)),Array.isArray(a.DO)&&(a.DO=Ne(a.DO,t,i)),a}var Fn=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}};function Hn(e,t,i){let a=Sc(e),n=a?.name,o=a?.tags,r=a?.use;if(a&&(a.name!==void 0||a.tags!==void 0||a.use!==void 0)&&(delete a.name,delete a.tags,delete a.use),a?.suite){if(a.goal||a.statements)throw new Fn('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return Oc(a,n,o,r,t,i)}return Cc(a,n,o,r,t,i)}function Cc(e,t,i,a,n,o){let r=e?.beforeEach,s=e?.afterEach,l=_o(e?.parameters),c=e?.timeout,d=e?.skip,h=e?.fail,p=e?.only,g=e?.slow,f=e?.settings,y=a;if(f){let _={};f.auto_dismiss_modal!==void 0&&(_.autoDismissModal=!!f.auto_dismiss_modal),f.browser_timezone!==void 0&&f.browser_timezone!==null&&(_.timezoneId=String(f.browser_timezone)),f.browser_language!==void 0&&f.browser_language!==null&&(_.locale=String(f.browser_language)),f.extra_http_headers!==void 0&&f.extra_http_headers!==null&&typeof f.extra_http_headers=="object"&&(_.extraHTTPHeaders=f.extra_http_headers),Object.keys(_).length>0&&(y={...y,..._})}if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow,delete e.settings),e?.url)throw new Fn(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let b=[];if(n&&e&&typeof e=="object"){let _=vo(e,n,o);e=_.doc,b=_.referencedTemplatePaths}let m=xo(e),x=me(m);return n&&(Xe(x.statements??[],n,"main"),x.teardown&&Xe(x.teardown,n,"teardown")),{testFlow:x,name:t,tags:i,use:y,beforeEach:r,afterEach:s,parameters:l,timeout:c,skip:d,fail:h,only:p,slow:g,referencedTemplatePaths:b}}function Oc(e,t,i,a,n,o){let r=e.suite;if(!Array.isArray(r.tests)||r.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let s=r.beforeAll,l=r.afterAll,c=r.beforeEach,d=r.afterEach,h=[],p=r.tests.map(y=>{if(!y.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(y.statements)||y.statements.length===0)throw new Error(`Suite test "${y.name}" must have a non-empty "statements" array.`);let b={goal:y.name,statements:y.statements};y.teardown&&(b.teardown=y.teardown);let m=[],x=b;if(n&&typeof b=="object"){let $=vo(b,n,o);x=$.doc,m=$.referencedTemplatePaths,h.push(...m)}let _=xo(x),E=me(_),A=_o(y.parameters);return{testFlow:E,name:y.name,tags:Array.isArray(y.tags)?y.tags:void 0,parameters:A,timeout:y.timeout,skip:y.skip,fail:y.fail,only:y.only,slow:y.slow}}),g=r.base_url,f=g?{...a,baseURL:g}:a;return{suite:{beforeAll:s,afterAll:l,beforeEach:c,afterEach:d,tests:p},name:t,tags:i,use:f,referencedTemplatePaths:h}}function _o(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,i)=>{if(!t.name)throw new Error(`Parameter set at index ${i} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function Xe(e,t,i){for(let a=0;a<e.length;a++){let n=e[a],o=`${i}.${a}`,r=n.description||"";if(n.uid=Lc(t,o,r),n.type===ue.STEP)Xe(n.statements,t,o);else if(n.type===ue.IF_ELSE){let s=n;Xe(s.then,t,`${o}.then`),s.else&&Xe(s.else,t,`${o}.else`)}else n.type===ue.WHILE_LOOP&&Xe(n.body,t,`${o}.body`)}}function Lc(e,t,i){let a=kc("sha256").update(`${e}:${t}:${i}`).digest("hex");return`${a.slice(0,8)}-${a.slice(8,12)}-${a.slice(12,16)}-${a.slice(16,20)}-${a.slice(20,32)}`}function ko(e,t,i){let a=/\btemplate:\s/.test(e),n=/^suite:/m.test(e),o=a||n?null:Pn(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let r,s,l=[];try{let c=i?.parsed??Hn(e,t);l=c.referencedTemplatePaths;let d={version:i?.version,actionEntityStore:i?.actionEntityStore},h=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?r=xc(c.suite,{...d,testName:c.name,tags:c.tags,use:c.use}):r=yc(c.testFlow,{...d,testName:c.name,tags:c.tags,use:h,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let p=r.split(`
4366
4366
  `).filter(g=>!g.startsWith("import ")).join(`
4367
- `);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),Dc(Rc(s),{recursive:!0}),Nc(s,r)}catch(c){let d=c instanceof Fn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var Wn="0.1.63";function So(e){try{return Fc(e).mtimeMs}catch{return 0}}var Uc=`// @generated by shiplightai v${Wn}`;function Bc(e,t){if(!To(e)||Eo(e,"utf-8").split(`
4367
+ `);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),Dc(Rc(s),{recursive:!0}),Nc(s,r)}catch(c){let d=c instanceof Fn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var Wn="0.1.64";function So(e){try{return Fc(e).mtimeMs}catch{return 0}}var Uc=`// @generated by shiplightai v${Wn}`;function Bc(e,t){if(!To(e)||Eo(e,"utf-8").split(`
4368
4368
  `,1)[0]!==Uc)return!1;let a=So(e);for(let n of t)if(So(n)>a)return!1;return!0}function Gc(e){let t=process.argv.slice(2),i=[],a=Un(e);for(let n of t){if(n.startsWith("-"))continue;let o=n.endsWith(".yaml.spec.ts")?n.replace(/\.yaml\.spec\.ts$/,".test.yaml"):n;if(!o.endsWith(".test.yaml"))continue;let r=Un(e,o);To(r)&&i.push(r.startsWith(a)?r.slice(a.length+1):o)}return i.length>0?i:null}function Ao(e){let t=Gc(e.cwd),i=t??Wc("**/*.test.yaml",{cwd:e.cwd,ignore:["**/node_modules/**"]}),a=[];for(let n of i){let o=Un(e.cwd,n),r=o.replace(/\.test\.yaml$/,".yaml.spec.ts"),s=Eo(o,"utf-8");try{let l=Hn(s,o,e.projectRoot??e.cwd),c=Hc(e.cwd,o),d=e.actionEntityStores?.get(c)??e.actionEntityStores?.get("*");if(!(d&&Object.keys(d.entries).length>0)&&Bc(r,[o,...l.referencedTemplatePaths]))continue;let p=ko(s,o,{version:Wn,actionEntityStore:d,parsed:l});if(!p.valid)throw new Error(p.errors.join("; "))}catch(l){console.error(`[shiplight] Failed to transpile ${n}:`,l),a.push({file:n,error:l})}}if(a.length>0){let n=`[shiplight] Transpilation failed for ${a.length} file(s):
4369
4369
  `+a.map(o=>` - ${o.file}`).join(`
4370
4370
  `);if(t)throw new Error(n);console.warn(n+" (skipped)")}}import*as ae from"fs";import*as ft from"path";import{globSync as ed}from"glob";Si();function Lo(e){let t=ki().SHIPLIGHT_API_TOKEN;return process.env.CI&&t?new zn:new Kn(e)}var td=".shiplight/action-cache";function Oo(e){return e.replace(/\//g,"__")+".json"}function id(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var Kn=class{constructor(t){this.cwd=t;this.cacheDir=ft.join(t,td)}isCloud=!1;cacheDir;async lookup(t){let i=new Map;if(t.length===0||!ae.existsSync(this.cacheDir))return i;for(let a of t){let n=ft.join(this.cacheDir,Oo(a));try{if(ae.existsSync(n)){let o=ae.readFileSync(n,"utf-8");i.set(a,JSON.parse(o))}}catch{}}return i}async update(t){if(t.size===0)return 0;ae.mkdirSync(this.cacheDir,{recursive:!0});let i=0;for(let[a,n]of t)try{let o=ft.join(this.cacheDir,Oo(a)),r=Cn();if(ae.existsSync(o))try{r=JSON.parse(ae.readFileSync(o,"utf-8"))}catch{}let s={...r,entries:{...r.entries,...n.entries}};ae.writeFileSync(o,JSON.stringify(s,null,2)),i++}catch{}return i}loadAll(){if(!ae.existsSync(this.cacheDir))return;let t=ed("*.json",{cwd:this.cacheDir});if(t.length===0)return;let i=new Map,a=0;for(let n of t)try{let o=ae.readFileSync(ft.join(this.cacheDir,n),"utf-8"),r=JSON.parse(o),s=id(n);i.set(s,r),a+=Object.keys(r.entries??{}).length}catch{}if(i.size!==0)return console.log(`[shiplight] Cache: loaded ${a} cached action entit${a===1?"y":"ies"} for ${i.size} test file${i.size!==1?"s":""}`),i}},zn=class{isCloud=!0;async lookup(t){let{lookupActionStores:i}=await Promise.resolve().then(()=>(jn(),Gn));return i(t)}async update(t){let{updateActionStores:i}=await Promise.resolve().then(()=>(jn(),Gn));return i(t)}loadAll(){}};Si();function od(e={}){e.dotenv!==!1&&sd(e.scanDir||process.cwd());let t=e.scanDir||process.cwd(),a=Lo(t).loadAll();return Ao({cwd:t,projectRoot:process.cwd(),actionEntityStores:a}),{reporter:[["list"],["shiplightai/reporter",{outputFolder:"shiplight-report",open:"never"}]]}}function rd(e,...t){return ad(e,...t)}function sd(e){Mo(e);for(let t of Bn(e))nd.config({path:t})}R();R();Lt();Qe();import{z as tl}from"zod";var Th=tl.object({instruction:tl.string().describe('The instruction of the operation to perform. Can only include one operation. Do not inlcude element indexes just describe the element, e.g. "select the text "Hello, world!" in "Hello, world!""')});function il(e){e.register({name:"perform_accurate_operation",description:"Perform an operation that requires accurate interaction like dragging or interacting with a specific area of an element. Only use this action when neccecary.",schema:Th,usesElementIndex:!1,async execute(t,i){let{instruction:a}=t,n={page:i.page,agentServices:i.agentServices,domService:i.domService},o=await Ze(a,n,{});if(o.status==="error"||!o.actionEntity)return{success:!1,actionEntity:{action_description:a,action_data:{action_name:"perform_accurate_operation",kwargs:{instruction:a}}},error:o.error||"Failed to generate action"};let{actionEntity:r}=o,s=await Ct(r,n);return{success:s.success,actionEntity:r,message:s.success?`Successfully executed action: ${r.action_data?.action_name}`:void 0,error:s.error}}})}Te();R();import{generateText as Ah}from"ai";import{convert as $h}from"html-to-text";async function Mh(e,t,i){let{apiKey:a,domain:n}=e;if(!a||!n)throw new Error("Mailgun configuration missing. Please provide apiKey and domain");let o=[];try{let r=`https://api.mailgun.net/v3/${n}/events`,s={event:"accepted",limit:"10",ascending:"yes",recipient:t};if(i.from_email&&(s.from=i.from_email),i.since)s.begin=Math.floor(i.since/1e3).toString();else{let d=new Date(Date.now()-6e5);s.begin=Math.floor(d.getTime()/1e3).toString()}u.info(`Mailgun params: ${JSON.stringify(s)}`);let l=await fetch(r+"?"+new URLSearchParams(s),{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`}});if(!l.ok){let d=await l.text();throw new Error(`Mailgun events API error: ${d}`)}let c=(await l.json()).items||[];for(let d of c.slice(0,10)){if(d.event!=="accepted")continue;let h=(d.storage||{}).url;if(u.info(`message_url: ${h}`),!h){let f=(d.message||{}).headers||{},y=f.subject||"",b=f.from||"",m=f.to||"";if(i.from_email&&!b.toLowerCase().includes(i.from_email.toLowerCase())||i.to_email&&!m.toLowerCase().includes(i.to_email.toLowerCase())||i.subject&&!y.toLowerCase().includes(i.subject.toLowerCase()))continue;o.push({subject:y,from:b,to:m,date:new Date(d.timestamp*1e3).toUTCString(),body:"Message body not available (Mailgun storage disabled)",message_id:f["message-id"]||""});continue}let p=h.split("/"),g=p[p.length-1];if(u.info(`Storage key: ${g}`),g){let f=`https://api.mailgun.net/v3/domains/${n}/messages/${g}`;try{let y=await fetch(f,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`,Accept:"application/json"}});if(y.ok){let b=await y.json(),m=b.Subject||"",x=b.From||"",_=b.To||"",E=b.Date||"",A=b["Message-Id"]||"";u.info(`subject: ${m}`),u.info(`from_addr: ${x}`),u.info(`to_addr: ${_}`),u.info(`date: ${E}`),u.info(`message_id: ${A}`);let $=b["body-html"]||b["body-plain"]||"";if($&&$.includes("<")&&($=$h($)),u.info(`Body: ${$.substring(0,200)}...`),i.subject&&!m.toLowerCase().includes(i.subject.toLowerCase())||i.body_contains&&!$.toLowerCase().includes(i.body_contains.toLowerCase()))continue;o.push({subject:m,from:x,to:_,date:E,body:$,message_id:A});continue}else u.warn(`Messages API returned ${y.status}`)}catch(y){u.warn(`Failed to parse JSON response: ${y}`)}}try{let f=await fetch(h,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${a}`).toString("base64")}`}});if(!f.ok){u.warn(`Could not fetch stored message: ${f.status}`);continue}let y=await f.text();u.info(`Fallback: Raw email length: ${y.length}`);let b=y.split(`
package/dist/reporter.js CHANGED
@@ -873,7 +873,7 @@ import*as k from"fs";import*as v from"path";import{z as s}from"zod";var Q=s.enum
873
873
  });
874
874
  </script>
875
875
  </body>
876
- </html>`}var Te="0.1.63",Ae=Te!=="dev"?Te:void 0;var yn=3600*1e3,bn=10080*60*1e3;var pt={before:0,main:1,teardown:2,after:3};function $e(e){let t=e.split(".")[0];return pt[t]??1}function Ee(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function ht(e){return[...e].sort(([t],[r])=>{let i=$e(t),n=$e(r);if(i!==n)return i-n;let a=Ee(t),o=Ee(r);for(let c=0;c<Math.max(a.length,o.length);c++){let l=a[c]??-1,p=o[c]??-1;if(l!==p)return l-p}return 0})}function ft(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function mt(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function q(e,t="main",r,i,n){r===void 0&&(r=!ft(e).has("test.step")),n||(n=new Map);let a=[];for(let o of e){if(o.category==="fixture"||o.category==="test.attach")continue;if(o.category==="hook"){let l=mt(o.title,t);a.push(...q(o.steps,l,r,i,n));continue}if(o.category==="test.step"||r&&(o.category==="expect"||o.category==="pw:api")){let l=n.get(t)??0;n.set(t,l+1);let p=`${t}.${l}`,y={stepId:p,description:o.title,status:o.error?"failure":o.duration===-1?"skipped":"success",duration:o.duration>=0?o.duration:void 0};o.error&&(y.error=o.error.message??o.error.stack),i&&o.location&&i.set(p,o.location),a.push(y),o.steps.length>0&&a.push(...q(o.steps,p,r,i,n))}}return a}var Z=class{outputFolder;openMode;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure"}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let p of this.collected){let y=p.test.titlePath().join(" > "),b=r.get(y);b||(b=[],r.set(y,b)),b.push(p)}let i=[];for(let[,p]of r.entries()){let y=p[0].test.location.file,b=[],d,h,f;for(let g=0;g<p.length;g++){let{test:T,result:M}=p[g],A=await this.buildReportTest(T,M,y);d=A,h||(h=A.startTime),f=A.endTime,b.push({attemptNumber:g+1,status:M.status,duration:M.duration,steps:A.steps,error:A.error,videoPath:A.videoPath,tracePath:A.tracePath})}let u=b[b.length-1],{test:S}=p[p.length-1],_={title:S.title,baseTitle:d?.baseTitle,file:v.relative(process.cwd(),y),status:u.status,duration:u.duration,steps:u.steps,error:u.error,videoPath:u.videoPath,tracePath:u.tracePath,actionStepsMap:d?.actionStepsMap,tags:d?.tags,baseUrl:d?.baseUrl,skip:d?.skip,slow:d?.slow,timeout:d?.timeout,parameterSetName:d?.parameterSetName,startTime:h,endTime:f,suiteName:d?.suiteName},w=b.some(g=>g.status==="failed"||g.status==="timedOut");b.length>1&&w&&u.status==="passed"&&(_.flaky=!0,_.retries=b.length-1,_.attempts=b),i.push(_)}let n={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString(),shiplightVersion:Ae},a=v.isAbsolute(this.outputFolder)?this.outputFolder:v.join(process.cwd(),this.outputFolder);k.mkdirSync(a,{recursive:!0});let o=v.join(a,"screenshots");for(let p=0;p<n.tests.length;p++){let y=n.tests[p],b=y.attempts&&y.attempts.length>0,d=[{obj:y,prefix:b?`test-${p}-attempt-0`:`test-${p}`,screenshotSubDir:`test-${p}`}];if(y.attempts)for(let h=0;h<y.attempts.length;h++)d.push({obj:y.attempts[h],prefix:`test-${p}-attempt-${h+1}`,screenshotSubDir:`test-${p}/attempt-${h}`});for(let{obj:h,prefix:f,screenshotSubDir:u}of d){let S=v.join(o,u),_=!1;for(let w of h.steps)if(w.screenshot&&v.isAbsolute(w.screenshot))try{_||(k.mkdirSync(S,{recursive:!0}),_=!0);let g=`${w.stepId.replace(/\./g,"-")}.png`;k.copyFileSync(w.screenshot,v.join(S,g)),w.screenshot=`screenshots/${u}/${g}`}catch(g){console.warn(`[reporter] Failed to copy screenshot for ${w.stepId}:`,g)}if(h.videoPath&&v.isAbsolute(h.videoPath)){let w=v.extname(h.videoPath)||".webm",g=`${f}-video${w}`;try{k.copyFileSync(h.videoPath,v.join(a,g)),h.videoPath=g}catch{h.videoPath=void 0}}if(h.tracePath&&v.isAbsolute(h.tracePath)){let w=v.extname(h.tracePath)||".zip",g=`${f}-trace${w}`;try{k.copyFileSync(h.tracePath,v.join(a,g)),h.tracePath=g}catch{h.tracePath=void 0}}}}let c=v.join(a,"report-data.json");k.writeFileSync(c,JSON.stringify(n,null,2),"utf-8");let l=v.join(a,"index.html");if(k.writeFileSync(l,ke(n),"utf-8"),console.log(`
876
+ </html>`}var Te="0.1.64",Ae=Te!=="dev"?Te:void 0;var yn=3600*1e3,bn=10080*60*1e3;var pt={before:0,main:1,teardown:2,after:3};function $e(e){let t=e.split(".")[0];return pt[t]??1}function Ee(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function ht(e){return[...e].sort(([t],[r])=>{let i=$e(t),n=$e(r);if(i!==n)return i-n;let a=Ee(t),o=Ee(r);for(let c=0;c<Math.max(a.length,o.length);c++){let l=a[c]??-1,p=o[c]??-1;if(l!==p)return l-p}return 0})}function ft(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function mt(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function q(e,t="main",r,i,n){r===void 0&&(r=!ft(e).has("test.step")),n||(n=new Map);let a=[];for(let o of e){if(o.category==="fixture"||o.category==="test.attach")continue;if(o.category==="hook"){let l=mt(o.title,t);a.push(...q(o.steps,l,r,i,n));continue}if(o.category==="test.step"||r&&(o.category==="expect"||o.category==="pw:api")){let l=n.get(t)??0;n.set(t,l+1);let p=`${t}.${l}`,y={stepId:p,description:o.title,status:o.error?"failure":o.duration===-1?"skipped":"success",duration:o.duration>=0?o.duration:void 0};o.error&&(y.error=o.error.message??o.error.stack),i&&o.location&&i.set(p,o.location),a.push(y),o.steps.length>0&&a.push(...q(o.steps,p,r,i,n))}}return a}var Z=class{outputFolder;openMode;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure"}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let p of this.collected){let y=p.test.titlePath().join(" > "),b=r.get(y);b||(b=[],r.set(y,b)),b.push(p)}let i=[];for(let[,p]of r.entries()){let y=p[0].test.location.file,b=[],d,h,f;for(let g=0;g<p.length;g++){let{test:T,result:M}=p[g],A=await this.buildReportTest(T,M,y);d=A,h||(h=A.startTime),f=A.endTime,b.push({attemptNumber:g+1,status:M.status,duration:M.duration,steps:A.steps,error:A.error,videoPath:A.videoPath,tracePath:A.tracePath})}let u=b[b.length-1],{test:S}=p[p.length-1],_={title:S.title,baseTitle:d?.baseTitle,file:v.relative(process.cwd(),y),status:u.status,duration:u.duration,steps:u.steps,error:u.error,videoPath:u.videoPath,tracePath:u.tracePath,actionStepsMap:d?.actionStepsMap,tags:d?.tags,baseUrl:d?.baseUrl,skip:d?.skip,slow:d?.slow,timeout:d?.timeout,parameterSetName:d?.parameterSetName,startTime:h,endTime:f,suiteName:d?.suiteName},w=b.some(g=>g.status==="failed"||g.status==="timedOut");b.length>1&&w&&u.status==="passed"&&(_.flaky=!0,_.retries=b.length-1,_.attempts=b),i.push(_)}let n={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString(),shiplightVersion:Ae},a=v.isAbsolute(this.outputFolder)?this.outputFolder:v.join(process.cwd(),this.outputFolder);k.mkdirSync(a,{recursive:!0});let o=v.join(a,"screenshots");for(let p=0;p<n.tests.length;p++){let y=n.tests[p],b=y.attempts&&y.attempts.length>0,d=[{obj:y,prefix:b?`test-${p}-attempt-0`:`test-${p}`,screenshotSubDir:`test-${p}`}];if(y.attempts)for(let h=0;h<y.attempts.length;h++)d.push({obj:y.attempts[h],prefix:`test-${p}-attempt-${h+1}`,screenshotSubDir:`test-${p}/attempt-${h}`});for(let{obj:h,prefix:f,screenshotSubDir:u}of d){let S=v.join(o,u),_=!1;for(let w of h.steps)if(w.screenshot&&v.isAbsolute(w.screenshot))try{_||(k.mkdirSync(S,{recursive:!0}),_=!0);let g=`${w.stepId.replace(/\./g,"-")}.png`;k.copyFileSync(w.screenshot,v.join(S,g)),w.screenshot=`screenshots/${u}/${g}`}catch(g){console.warn(`[reporter] Failed to copy screenshot for ${w.stepId}:`,g)}if(h.videoPath&&v.isAbsolute(h.videoPath)){let w=v.extname(h.videoPath)||".webm",g=`${f}-video${w}`;try{k.copyFileSync(h.videoPath,v.join(a,g)),h.videoPath=g}catch{h.videoPath=void 0}}if(h.tracePath&&v.isAbsolute(h.tracePath)){let w=v.extname(h.tracePath)||".zip",g=`${f}-trace${w}`;try{k.copyFileSync(h.tracePath,v.join(a,g)),h.tracePath=g}catch{h.tracePath=void 0}}}}let c=v.join(a,"report-data.json");k.writeFileSync(c,JSON.stringify(n,null,2),"utf-8");let l=v.join(a,"index.html");if(k.writeFileSync(l,ke(n),"utf-8"),console.log(`
877
877
  Shiplight report written to: ${l}`),this.openMode==="always"||this.openMode==="on-failure"&&t.status!=="passed")try{let p=(await import("open")).default;await p(l)}catch{}}printsToStdio(){return!1}async buildReportTest(t,r,i){let n={title:t.title,file:v.relative(process.cwd(),i),status:r.status,duration:r.duration,steps:[],startTime:new Date(r.startTime).toISOString(),endTime:new Date(r.startTime.getTime()+r.duration).toISOString()};r.errors.length>0&&(n.error=r.errors.map(d=>d.message||d.stack||String(d)).join(`
878
878
 
879
879
  `)),r.stdout.length>0&&(n.stdout=r.stdout.map(d=>typeof d=="string"?d:d.toString()).join("")),r.stderr.length>0&&(n.stderr=r.stderr.map(d=>typeof d=="string"?d:d.toString()).join(""));for(let d of r.attachments)d.name==="video"&&d.path&&(n.videoPath=d.path),d.name==="trace"&&d.path&&(n.tracePath=d.path);let a=r.attachments.find(d=>d.name==="shiplight-results"),o=null;if(a)try{if(a.body)o=JSON.parse(a.body.toString("utf-8"));else if(a.path){let d=k.readFileSync(a.path,"utf-8");o=JSON.parse(d)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),l={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),y=p?p[1]:t.title,b=p?p[2]:void 0;if(b&&(n.parameterSetName=b),k.existsSync(c))try{let d=k.readFileSync(c,"utf-8"),h=_e(d,c);if(h.suite){let f=h.suite.tests.find(u=>u.name===y);f&&(l=K(f.testFlow),f.tags?.length&&(n.tags=f.tags),f.skip!==void 0&&(n.skip=f.skip),f.slow&&(n.slow=f.slow),f.timeout!==void 0&&(n.timeout=f.timeout),n.baseTitle=f.name||f.testFlow?.goal),n.suiteName=h.name,h.tags?.length&&(n.suiteTags=h.tags),h.use?.baseURL&&(n.baseUrl=h.use.baseURL)}else h.testFlow&&(l=K(h.testFlow),n.baseTitle=h.name||h.testFlow?.goal,h.tags?.length&&(n.tags=h.tags),h.use?.baseURL&&(n.baseUrl=h.use.baseURL))}catch{}if(o||Object.keys(l).length>0){let d=new Set([...Object.keys(l),...Object.keys(o||{})]),h=Array.from(d).map(u=>[u,null]),f=ht(h);for(let[u]of f){let S=l[u],_=o?.[u],w=S?.description;if(!w||w==="Action"||w==="Draft"){let T=S?.action_entity;w=T?.action_description||T?.action_data?.kwargs?.description||_?.description||u}let g={stepId:u,description:w,status:_?.status||"pending",duration:_?.duration};if(_?.message){let T=typeof _.message=="string"?_.message:JSON.stringify(_.message,null,2);_.status==="failure"?g.error=T:g.message=T}if(_?.screenshot){let T=_.screenshot,M=a?.path?v.dirname(a.path):"",A=v.isAbsolute(T)?T:v.join(M,T);k.existsSync(A)&&(g.screenshot=A)}_?.code&&(g.code=_.code),n.steps.push(g)}}if(o===null&&Object.keys(l).length===0&&!i.endsWith(".yaml.spec.ts")){let d=new Map;if(n.steps=q(r.steps,"main",void 0,d),n.steps.length>0){let h=new Map;n.actionStepsMap=Object.fromEntries(n.steps.map(f=>{let u=d.get(f.stepId),S;if(u?.file){if(!h.has(u.file))try{h.set(u.file,k.readFileSync(u.file,"utf-8").split(`
@@ -7060,6 +7060,8 @@ function useSessions() {
7060
7060
  const [activeSessionId, setActiveSessionId] = reactExports.useState(null);
7061
7061
  const mountedRef = reactExports.useRef(true);
7062
7062
  const failureCountRef = reactExports.useRef(0);
7063
+ const sessionsRef = reactExports.useRef(sessions);
7064
+ sessionsRef.current = sessions;
7063
7065
  const fetchSessions = reactExports.useCallback(async () => {
7064
7066
  try {
7065
7067
  const res = await fetch("/api/debugger/sessions", { cache: "no-store" });
@@ -7068,7 +7070,10 @@ function useSessions() {
7068
7070
  if (!mountedRef.current) return true;
7069
7071
  setSessions(data.sessions);
7070
7072
  setActiveSessionId((prev) => {
7071
- if (prev && data.sessions.some((s) => s.sessionId === prev)) return prev;
7073
+ if (prev) {
7074
+ const stillExists = data.sessions.some((s) => s.sessionId === prev);
7075
+ if (stillExists) return prev;
7076
+ }
7072
7077
  const first = data.sessions.find((s) => s.status !== "ended");
7073
7078
  return first ? first.sessionId : null;
7074
7079
  });
@@ -7099,28 +7104,28 @@ function useSessions() {
7099
7104
  };
7100
7105
  }, [fetchSessions]);
7101
7106
  const openSession = reactExports.useCallback(
7102
- async (yamlPath) => {
7103
- try {
7104
- const res = await fetch("/api/debugger/sessions", {
7105
- method: "POST",
7106
- headers: { "Content-Type": "application/json" },
7107
- body: JSON.stringify({ yamlPath })
7108
- });
7109
- if (!res.ok) {
7110
- console.error("[shell] openSession failed:", res.status, await res.text());
7111
- return null;
7112
- }
7113
- const session = await res.json();
7114
- setActiveSessionId(session.sessionId);
7115
- setSessions((prev) => {
7116
- const without = prev.filter((s) => s.sessionId !== session.sessionId);
7117
- return [...without, session];
7118
- });
7119
- return session;
7120
- } catch (err) {
7121
- console.error("[shell] openSession threw:", err);
7122
- return null;
7107
+ (yamlPath) => {
7108
+ const existing = sessionsRef.current.find(
7109
+ (s) => s.yamlPath === yamlPath && s.status !== "ended"
7110
+ );
7111
+ if (existing) {
7112
+ setActiveSessionId(existing.sessionId);
7113
+ return;
7123
7114
  }
7115
+ (async () => {
7116
+ try {
7117
+ const res = await fetch("/api/debugger/sessions", {
7118
+ method: "POST",
7119
+ headers: { "Content-Type": "application/json" },
7120
+ body: JSON.stringify({ yamlPath })
7121
+ });
7122
+ if (!res.ok) return;
7123
+ const session = await res.json();
7124
+ setSessions((prev) => [...prev, session]);
7125
+ setActiveSessionId(session.sessionId);
7126
+ } catch {
7127
+ }
7128
+ })();
7124
7129
  },
7125
7130
  []
7126
7131
  );
@@ -7157,6 +7162,7 @@ const LONG_PRESS_MS = 500;
7157
7162
  const MOVE_CANCEL_PX = 10;
7158
7163
  function usePointerGestures(args) {
7159
7164
  const { onFocus, onOpen, onContextMenu, isFocused } = args;
7165
+ const longPressedRef = reactExports.useRef(false);
7160
7166
  const downRef = reactExports.useRef(null);
7161
7167
  const clearLongPress = reactExports.useCallback(() => {
7162
7168
  const d = downRef.current;
@@ -7167,22 +7173,18 @@ function usePointerGestures(args) {
7167
7173
  }, []);
7168
7174
  const onPointerDown = reactExports.useCallback(
7169
7175
  (e) => {
7170
- const x = e.clientX;
7171
- const y = e.clientY;
7172
- const pointerType = e.pointerType;
7176
+ longPressedRef.current = false;
7173
7177
  downRef.current = {
7174
- x,
7175
- y,
7176
- pointerType,
7177
- longPressTimer: null,
7178
- longPressed: false
7178
+ x: e.clientX,
7179
+ y: e.clientY,
7180
+ longPressTimer: null
7179
7181
  };
7180
- if (pointerType === "touch") {
7182
+ if (e.pointerType === "touch") {
7183
+ const x = e.clientX;
7184
+ const y = e.clientY;
7181
7185
  downRef.current.longPressTimer = window.setTimeout(() => {
7182
- const d = downRef.current;
7183
- if (!d) return;
7184
- d.longPressed = true;
7185
- onContextMenu(d.x, d.y);
7186
+ longPressedRef.current = true;
7187
+ onContextMenu(x, y);
7186
7188
  }, LONG_PRESS_MS);
7187
7189
  }
7188
7190
  },
@@ -7200,47 +7202,25 @@ function usePointerGestures(args) {
7200
7202
  },
7201
7203
  [clearLongPress]
7202
7204
  );
7203
- const justOpenedRef = reactExports.useRef(null);
7204
- const onPointerUp = reactExports.useCallback(
7205
- (e) => {
7206
- const d = downRef.current;
7207
- clearLongPress();
7208
- downRef.current = null;
7209
- if (d == null ? void 0 : d.longPressed) {
7210
- e.preventDefault();
7211
- e.stopPropagation();
7212
- return;
7213
- }
7214
- if (isFocused) {
7215
- if (justOpenedRef.current !== null) {
7216
- window.clearTimeout(justOpenedRef.current);
7217
- }
7218
- justOpenedRef.current = window.setTimeout(() => {
7219
- justOpenedRef.current = null;
7220
- }, 300);
7221
- onOpen();
7222
- } else {
7223
- onFocus();
7224
- }
7225
- },
7226
- [clearLongPress, isFocused, onFocus, onOpen]
7227
- );
7205
+ const onPointerUp = reactExports.useCallback(() => {
7206
+ clearLongPress();
7207
+ downRef.current = null;
7208
+ }, [clearLongPress]);
7228
7209
  const onPointerCancel = reactExports.useCallback(() => {
7229
7210
  clearLongPress();
7230
7211
  downRef.current = null;
7231
7212
  }, [clearLongPress]);
7232
- const onDoubleClick = reactExports.useCallback(
7213
+ const handleClick = reactExports.useCallback(
7233
7214
  (e) => {
7234
- e.preventDefault();
7235
- e.stopPropagation();
7236
- if (justOpenedRef.current !== null) {
7237
- window.clearTimeout(justOpenedRef.current);
7238
- justOpenedRef.current = null;
7215
+ if (longPressedRef.current) {
7216
+ longPressedRef.current = false;
7239
7217
  return;
7240
7218
  }
7219
+ e.preventDefault();
7220
+ if (!isFocused) onFocus();
7241
7221
  onOpen();
7242
7222
  },
7243
- [onOpen]
7223
+ [isFocused, onFocus, onOpen]
7244
7224
  );
7245
7225
  const onContextMenuHandler = reactExports.useCallback(
7246
7226
  (e) => {
@@ -7251,11 +7231,11 @@ function usePointerGestures(args) {
7251
7231
  [onContextMenu]
7252
7232
  );
7253
7233
  return {
7234
+ onClick: handleClick,
7254
7235
  onPointerDown,
7255
7236
  onPointerMove,
7256
7237
  onPointerUp,
7257
7238
  onPointerCancel,
7258
- onDoubleClick,
7259
7239
  onContextMenu: onContextMenuHandler
7260
7240
  };
7261
7241
  }
@@ -7284,8 +7264,6 @@ const colors = {
7284
7264
  // error h2
7285
7265
  // Accent
7286
7266
  accent: "#7c5cfc",
7287
- // active-tab top border, session-indicator dot, spinner top
7288
- scrollbarHover: "#4d5057",
7289
7267
  // scrollbar thumb on hover
7290
7268
  spinnerTrack: "#333333"
7291
7269
  // spinner background ring (both large + thin variants)
@@ -7820,57 +7798,43 @@ function SessionFrame({ session, isActive }) {
7820
7798
  );
7821
7799
  }
7822
7800
  function DebuggerSessionsHost({ sessions, activeSessionId }) {
7823
- const ready = sessions.filter((s) => s.status === "running");
7824
- const anyActive = ready.some((s) => s.sessionId === activeSessionId);
7825
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
7826
- "div",
7827
- {
7828
- style: {
7829
- position: "absolute",
7830
- inset: 0,
7831
- pointerEvents: anyActive ? "auto" : "none"
7801
+ const live = sessions.filter((s) => s.status !== "ended");
7802
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "absolute", inset: 0 }, children: [
7803
+ live.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(
7804
+ SessionFrame,
7805
+ {
7806
+ session: s,
7807
+ isActive: s.sessionId === activeSessionId
7832
7808
  },
7833
- children: [
7834
- ready.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(
7835
- SessionFrame,
7836
- {
7837
- session: s,
7838
- isActive: s.sessionId === activeSessionId
7839
- },
7840
- s.sessionId
7841
- )),
7842
- !anyActive && /* @__PURE__ */ jsxRuntimeExports.jsx(
7843
- "div",
7844
- {
7845
- style: {
7846
- position: "absolute",
7847
- inset: 0,
7848
- display: "flex",
7849
- flexDirection: "column",
7850
- alignItems: "center",
7851
- justifyContent: "center",
7852
- color: colors.textDim,
7853
- fontSize: 13,
7854
- textAlign: "center",
7855
- padding: 24,
7856
- pointerEvents: "none"
7857
- },
7858
- children: sessions.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
7859
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 15, color: colors.textMuted, marginBottom: 8 }, children: "No debugger sessions open" }),
7860
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
7861
- "Double-click a ",
7862
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { style: inlineCode, children: ".test.yaml" }),
7863
- " file in the tree to open one."
7864
- ] })
7865
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
7866
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: spinner }),
7867
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 12 }, children: "Starting session…" })
7868
- ] })
7869
- }
7870
- )
7871
- ]
7872
- }
7873
- );
7809
+ s.sessionId
7810
+ )),
7811
+ sessions.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
7812
+ "div",
7813
+ {
7814
+ style: {
7815
+ position: "absolute",
7816
+ inset: 0,
7817
+ display: "flex",
7818
+ flexDirection: "column",
7819
+ alignItems: "center",
7820
+ justifyContent: "center",
7821
+ color: colors.textDim,
7822
+ fontSize: 13,
7823
+ textAlign: "center",
7824
+ padding: 24,
7825
+ pointerEvents: "none"
7826
+ },
7827
+ children: [
7828
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 15, color: colors.textMuted, marginBottom: 8 }, children: "No debugger sessions open" }),
7829
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
7830
+ "Double-click a ",
7831
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { style: inlineCode, children: ".test.yaml" }),
7832
+ " file in the tree to open one."
7833
+ ] })
7834
+ ]
7835
+ }
7836
+ )
7837
+ ] });
7874
7838
  }
7875
7839
  const inlineCode = {
7876
7840
  background: colors.bgHover,
@@ -7879,14 +7843,6 @@ const inlineCode = {
7879
7843
  fontSize: 12,
7880
7844
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
7881
7845
  };
7882
- const spinner = {
7883
- width: 24,
7884
- height: 24,
7885
- border: `3px solid ${colors.spinnerTrack}`,
7886
- borderTopColor: colors.accent,
7887
- borderRadius: "50%",
7888
- animation: "shp-spin 0.8s linear infinite"
7889
- };
7890
7846
  function readBootstrap() {
7891
7847
  if (typeof window === "undefined") return { initialDir: "/", initialFile: null };
7892
7848
  const params = new URLSearchParams(window.location.search);
@@ -7923,7 +7879,7 @@ function DebuggerShellApp() {
7923
7879
  if (autoOpenedRef.current) return;
7924
7880
  if (!bootstrap.initialFile) return;
7925
7881
  autoOpenedRef.current = true;
7926
- void openSession(bootstrap.initialFile);
7882
+ openSession(bootstrap.initialFile);
7927
7883
  }, [bootstrap.initialFile, openSession]);
7928
7884
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
7929
7885
  /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: globalCss }),
@@ -7959,7 +7915,7 @@ function DebuggerShellApp() {
7959
7915
  onFocus: setFocusedPath,
7960
7916
  onOpen: (path) => {
7961
7917
  setFocusedPath(path);
7962
- void sessionsState.openSession(path);
7918
+ sessionsState.openSession(path);
7963
7919
  }
7964
7920
  }
7965
7921
  ),
@@ -8014,14 +7970,11 @@ function DebuggerShellApp() {
8014
7970
  const globalCss = `
8015
7971
  html, body, #root { height: 100%; margin: 0; padding: 0; }
8016
7972
  * { box-sizing: border-box; }
8017
- ::-webkit-scrollbar { width: 10px; height: 10px; }
8018
- ::-webkit-scrollbar-track { background: transparent; }
8019
- ::-webkit-scrollbar-thumb { background: ${colors.bgInputHint}; border-radius: 5px; }
8020
- ::-webkit-scrollbar-thumb:hover { background: ${colors.scrollbarHover}; }
7973
+ * { scrollbar-width: thin; scrollbar-color: ${colors.bgInputHint} transparent; }
8021
7974
  @keyframes shp-spin { to { transform: rotate(360deg); } }
8022
7975
  `;
8023
7976
  const container = document.getElementById("root");
8024
7977
  if (container) {
8025
7978
  clientExports.createRoot(container).render(/* @__PURE__ */ jsxRuntimeExports.jsx(DebuggerShellApp, {}));
8026
7979
  }
8027
- //# sourceMappingURL=index-CKKo90Kh.js.map
7980
+ //# sourceMappingURL=index-BQYw0-8-.js.map
@@ -13,7 +13,7 @@
13
13
  html, body { margin: 0; height: 100%; background: #1a1b1e; color: #c1c2c5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; }
14
14
  #root { height: 100%; }
15
15
  </style>
16
- <script type="module" crossorigin src="/assets/index-CKKo90Kh.js"></script>
16
+ <script type="module" crossorigin src="/assets/index-BQYw0-8-.js"></script>
17
17
  </head>
18
18
  <body>
19
19
  <div id="root"></div>