gm-skill 2.0.1914 → 2.0.1916
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/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +163 -44
- package/gm.json +1 -1
- package/package.json +1 -1
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1916",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -790,29 +790,40 @@ function findCachedBunRunnerVersion() {
|
|
|
790
790
|
const _patchedPlaywriterDistDirs = new Set();
|
|
791
791
|
function patchPlaywriterTimeoutBug(binJs) {
|
|
792
792
|
try {
|
|
793
|
-
const
|
|
794
|
-
if (_patchedPlaywriterDistDirs.has(
|
|
795
|
-
_patchedPlaywriterDistDirs.add(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
793
|
+
const pkgDir = path.dirname(binJs);
|
|
794
|
+
if (_patchedPlaywriterDistDirs.has(pkgDir)) return;
|
|
795
|
+
_patchedPlaywriterDistDirs.add(pkgDir);
|
|
796
|
+
// executor.js/cli.js live under <pkg-dir>/dist/ in the real published package layout, not
|
|
797
|
+
// directly alongside bin.js -- the prior version of this function joined path.dirname(binJs)
|
|
798
|
+
// straight onto 'executor.js'/'cli.js', which never matched any real file (fs.existsSync was
|
|
799
|
+
// always false), so the timeout-coercion patch silently never applied and every managed
|
|
800
|
+
// browser session with an explicit timeout= prefix threw
|
|
801
|
+
// `TypeError [ERR_INVALID_ARG_TYPE]: The "options.timeout" property must be of type number`.
|
|
802
|
+
// Check both the dist/ subfolder (the real layout) and the flat pkg-dir (defensive fallback
|
|
803
|
+
// for a differently-laid-out future version) so this keeps working either way.
|
|
804
|
+
const candidateDirs = [path.join(pkgDir, 'dist'), pkgDir];
|
|
805
|
+
for (const distDir of candidateDirs) {
|
|
806
|
+
const executorPath = path.join(distDir, 'executor.js');
|
|
807
|
+
if (fs.existsSync(executorPath)) {
|
|
808
|
+
const src = fs.readFileSync(executorPath, 'utf-8');
|
|
809
|
+
if (/async execute\(code, timeout = 10000\) \{(?!\s*timeout = Number)/.test(src)) {
|
|
810
|
+
const patched = src.replace(
|
|
811
|
+
/(async execute\(code, timeout = 10000\) \{)/,
|
|
812
|
+
'$1\n timeout = Number(timeout) || 10000;'
|
|
813
|
+
);
|
|
814
|
+
fs.writeFileSync(executorPath, patched);
|
|
815
|
+
}
|
|
805
816
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
817
|
+
const cliPath = path.join(distDir, 'cli.js');
|
|
818
|
+
if (fs.existsSync(cliPath)) {
|
|
819
|
+
const src = fs.readFileSync(cliPath, 'utf-8');
|
|
820
|
+
if (src.includes('timeout: options.timeout || 10000') && !src.includes('timeout: Number(options.timeout)')) {
|
|
821
|
+
const patched = src.replace(
|
|
822
|
+
'timeout: options.timeout || 10000',
|
|
823
|
+
'timeout: Number(options.timeout) || 10000'
|
|
824
|
+
);
|
|
825
|
+
fs.writeFileSync(cliPath, patched);
|
|
826
|
+
}
|
|
816
827
|
}
|
|
817
828
|
}
|
|
818
829
|
} catch (_) {}
|
|
@@ -3116,19 +3127,75 @@ function makeHostFunctions(instanceRef) {
|
|
|
3116
3127
|
const __topNM = modeOpts.match(/topN=(\d+)/);
|
|
3117
3128
|
const sampleIntervalUs = __intervalM && parseInt(__intervalM[1], 10) > 0 ? parseInt(__intervalM[1], 10) : 100;
|
|
3118
3129
|
const profileTopNBrowser = __topNM && parseInt(__topNM[1], 10) > 0 ? parseInt(__topNM[1], 10) : 20;
|
|
3130
|
+
// Real, pre-existing bug fixed here: this block previously called `page.evaluateOnNewDocument`,
|
|
3131
|
+
// which is a PUPPETEER method name -- playwriter's `page` object is a real Playwright Page,
|
|
3132
|
+
// whose equivalent is `page.addInitScript`. evaluateOnNewDocument does not exist on a
|
|
3133
|
+
// Playwright page, so both calls threw `TypeError: page.evaluateOnNewDocument is not a
|
|
3134
|
+
// function` on every single dispatch, silently swallowed by the enclosing try/catch -- meaning
|
|
3135
|
+
// window.__gmErrors (window.onerror/onunhandledrejection capture) was NEVER actually installed,
|
|
3136
|
+
// for as long as this code has existed, independent of the new GL-instrumentation block added
|
|
3137
|
+
// alongside it (which inherited the same wrong method name by copying the existing pattern).
|
|
3138
|
+
// Verified live: window.HTMLCanvasElement.prototype.getContext.toString() did not contain the
|
|
3139
|
+
// patch marker before this fix, confirming the pre-navigation init script never ran.
|
|
3119
3140
|
const debugSetup = `const __logs=[],__errs=[],__net=[];\n`
|
|
3120
3141
|
+ `try{page.on('console',m=>{try{__logs.push({type:m.type(),text:m.text()});}catch(_){}});`
|
|
3121
3142
|
+ `page.on('pageerror',e=>{try{__errs.push({type:'pageerror',msg:String(e&&e.message||e)});}catch(_){}});`
|
|
3122
3143
|
+ `page.on('error',e=>{try{__errs.push({type:'uncaught',msg:String(e&&e.message||e),stack:String(e&&e.stack||'')});}catch(_){}});`
|
|
3123
3144
|
+ `page.on('requestfinished',r=>{try{const t=r.timing();let __st=0,__sz=0;try{__st=(r.response()&&r.response().status())||0;}catch(_){}__net.push({url:String(r.url()).slice(0,120),method:r.method(),status:__st,dur_ms:Math.round(t.responseEnd),ttfb_ms:Math.round(t.responseStart)});}catch(_){}});`
|
|
3124
3145
|
+ `page.on('requestfailed',r=>{try{const err=r.failure();__errs.push({type:'fetch',msg:String(err&&err.errorText||'request failed'),url:String(r.url()).slice(0,120)});}catch(_){}});`
|
|
3125
|
-
+ `page.
|
|
3146
|
+
+ `await page.addInitScript(()=>{window.__gmErrors=[];window.onerror=(msg,src,line,col,err)=>{try{window.__gmErrors.push({type:'error',msg:String(msg),src:String(src).slice(0,80),line,col,stack:String(err&&err.stack||'')});}catch(_){};return false;};window.onunhandledrejection=(e)=>{try{window.__gmErrors.push({type:'unhandledRejection',msg:String(e.reason&&e.reason.message||e.reason),stack:String(e.reason&&e.reason.stack||'')});}catch(_){}};});`
|
|
3147
|
+
// Auto-instrument every canvas's WebGL/WebGL2 context: patch getContext once (idempotent,
|
|
3148
|
+
// pre-navigation via page.addInitScript so it is in place before the page's own first
|
|
3149
|
+
// getContext call) to wrap every draw call (drawArrays/drawElements and their -Instanced
|
|
3150
|
+
// variants) with a post-call gl.getError() drain. This is exactly the manual
|
|
3151
|
+
// gl.*=function(){...gl.getError()...} monkeypatch pattern used ad hoc to root-cause GPU
|
|
3152
|
+
// rendering bugs (stale VAO/buffer bindings, sampler-unit collisions, etc) -- making it a
|
|
3153
|
+
// standing, zero-setup capability means every browser-verb dispatch against a WebGL page
|
|
3154
|
+
// gets GL error visibility for free, without hand-rolling the instrumentation each time.
|
|
3155
|
+
// window.__gmGlErrors accumulates {fn,mode,count,offset,type,error,errorName,ctxLabel} for
|
|
3156
|
+
// up to 40 distinct GL errors per page load (capped to bound memory on a runaway-error page);
|
|
3157
|
+
// window.__gmGlDrawCalls is a running total per draw-fn name for volume context.
|
|
3158
|
+
+ `await page.addInitScript(()=>{`
|
|
3159
|
+
+ `window.__gmGlErrors=[];window.__gmGlDrawCalls={};`
|
|
3160
|
+
+ `const __glErrName=(gl,code)=>{for(const k of ['NO_ERROR','INVALID_ENUM','INVALID_VALUE','INVALID_OPERATION','INVALID_FRAMEBUFFER_OPERATION','OUT_OF_MEMORY','CONTEXT_LOST_WEBGL']){try{if(gl[k]===code)return k;}catch(_){}}return 'UNKNOWN_'+code;};`
|
|
3161
|
+
+ `const __wrapDraw=(gl,ctxLabel)=>{`
|
|
3162
|
+
+ `['drawArrays','drawElements','drawArraysInstanced','drawElementsInstanced'].forEach(fn=>{`
|
|
3163
|
+
+ `if(typeof gl[fn]!=='function'||gl[fn].__gmWrapped)return;`
|
|
3164
|
+
+ `const __orig=gl[fn].bind(gl);`
|
|
3165
|
+
+ `const __wrapped=function(...args){`
|
|
3166
|
+
+ `const __res=__orig(...args);`
|
|
3167
|
+
+ `window.__gmGlDrawCalls[fn]=(window.__gmGlDrawCalls[fn]||0)+1;`
|
|
3168
|
+
+ `const __err=gl.getError();`
|
|
3169
|
+
+ `if(__err!==gl.NO_ERROR&&window.__gmGlErrors.length<40){`
|
|
3170
|
+
+ `let __bufSize=-1;try{__bufSize=gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER,gl.BUFFER_SIZE);}catch(_){}`
|
|
3171
|
+
+ `window.__gmGlErrors.push({fn,ctxLabel,mode:args[0],count:args[1],offset:fn.indexOf('Elements')>=0?args[3]:undefined,type:fn.indexOf('Elements')>=0?args[2]:undefined,instanceCount:fn.indexOf('Instanced')>=0?args[args.length-1]:undefined,error:__err,errorName:__glErrName(gl,__err),elementArrayBufferSize:__bufSize,drawCallIndex:window.__gmGlDrawCalls[fn]});`
|
|
3172
|
+
+ `}`
|
|
3173
|
+
+ `return __res;`
|
|
3174
|
+
+ `};`
|
|
3175
|
+
+ `__wrapped.__gmWrapped=true;gl[fn]=__wrapped;`
|
|
3176
|
+
+ `});`
|
|
3177
|
+
+ `};`
|
|
3178
|
+
+ `const __origGetContext=HTMLCanvasElement.prototype.getContext;`
|
|
3179
|
+
+ `HTMLCanvasElement.prototype.getContext=function(type,...rest){`
|
|
3180
|
+
+ `const ctx=__origGetContext.call(this,type,...rest);`
|
|
3181
|
+
+ `if(ctx&&(type==='webgl'||type==='webgl2'||type==='experimental-webgl')&&typeof ctx.getError==='function'){try{__wrapDraw(ctx,type);}catch(_){}}`
|
|
3182
|
+
+ `return ctx;`
|
|
3183
|
+
+ `};`
|
|
3184
|
+
+ `});`
|
|
3126
3185
|
+ `}catch(_){}\n`;
|
|
3127
3186
|
const perfRead = `let __perf=null;try{__perf=await page.evaluate(async()=>{const n=performance.getEntriesByType('navigation')[0];const paints={};for(const p of performance.getEntriesByType('paint')){paints[p.name]=Math.round(p.startTime);}let lcp=0;try{const le=performance.getEntriesByType('largest-contentful-paint');if(le.length)lcp=Math.round(le[le.length-1].startTime);}catch(_){}let cls=0;try{for(const ls of performance.getEntriesByType('layout-shift')){if(!ls.hadRecentInput)cls+=ls.value;}}catch(_){}let longtasks=0;try{longtasks=performance.getEntriesByType('longtask').length;}catch(_){}let heapU=0,heapT=0;try{if(performance.memory){heapU=Math.round(performance.memory.usedJSHeapSize/10485.76)/100;heapT=Math.round(performance.memory.totalJSHeapSize/10485.76)/100;}}catch(_){}const fps=await new Promise(res=>{let f=0;const s=performance.now();function tick(){f++;if(performance.now()-s>=500)return res(Math.round(f/((performance.now()-s)/1000)));requestAnimationFrame(tick);}requestAnimationFrame(tick);});return{load_ms:n?Math.round(n.loadEventEnd||0):0,dcl_ms:n?Math.round(n.domContentLoadedEventEnd||0):0,resources:performance.getEntriesByType('resource').length,now:Math.round(performance.now()),first_paint_ms:paints['first-paint']||0,first_contentful_paint_ms:paints['first-contentful-paint']||0,largest_contentful_paint_ms:lcp,cumulative_layout_shift:Math.round(cls*1000)/1000,longtasks,fps,heap_used_mb:heapU,heap_total_mb:heapT};});}catch(_){}\n`;
|
|
3128
3187
|
const blankProbe = startUrl ? '' : `try{const __u=page.url();if(__u==='about:blank'||__u===''){console.error('__GM_BLANK__');}}catch(_){}\n`;
|
|
3129
3188
|
const netFmt = `__net.slice().sort((a,b)=>(b.dur_ms||0)-(a.dur_ms||0)).slice(0,30)`;
|
|
3130
3189
|
const consoleFmt = `(__logs.length>50?[...__logs.slice(0,50),{type:'meta',text:'... '+(__logs.length-50)+' more console entries dropped'}]:__logs)`;
|
|
3131
|
-
|
|
3190
|
+
// playwriter's own executor.js truncates the DISPLAYED stdout text at a fixed 10000 chars
|
|
3191
|
+
// ("[Truncated to 10000 characters...]"), which silently ate the __GM_RESULT__ sentinel line
|
|
3192
|
+
// (always appended LAST, after any real console.log volume from the page/debug capture) on
|
|
3193
|
+
// any dispatch whose combined console output exceeded that cap -- the exact common case once
|
|
3194
|
+
// debug capture became always-on. Writing the result to a dedicated file from inside the
|
|
3195
|
+
// executed script, then reading that file directly (bypassing playwriter's own stdout
|
|
3196
|
+
// formatting entirely), makes result delivery immune to output volume.
|
|
3197
|
+
const resultFile = path.join(os.tmpdir(), `gm-browser-result-${process.pid}-${execProfileSeq++}.json`);
|
|
3198
|
+
const emitResult = `try{require('fs').writeFileSync(${JSON.stringify(resultFile)},JSON.stringify(__RET===undefined?null:__RET));}catch(__se){try{require('fs').writeFileSync(${JSON.stringify(resultFile)},JSON.stringify({__unserializable:String(__se&&__se.message||__se)}));}catch(__se2){}}\n`;
|
|
3132
3199
|
if (modeMatch && modeMatch[1] === 'profile') {
|
|
3133
3200
|
const userScript = modeMatch[3];
|
|
3134
3201
|
const intervalUs = sampleIntervalUs;
|
|
@@ -3141,13 +3208,15 @@ function makeHostFunctions(instanceRef) {
|
|
|
3141
3208
|
+ `const __wallUs=(Date.now()-__wallT0)*1000;\n`
|
|
3142
3209
|
+ `if(__cdp){try{const __r=await __cdp.send('Profiler.stop');__profile=__r&&__r.profile||null;}catch(e){__profileError=String(e&&e.message||e);}}\n`
|
|
3143
3210
|
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3211
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3212
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3144
3213
|
+ perfRead
|
|
3145
3214
|
+ AGGREGATE_CPU_PROFILE_SRC + `\n`
|
|
3146
3215
|
+ `const __agg = __profile ? aggregateCpuProfile(__profile, ${profileTopNBrowser}) : {timeframe:null,culprits:[]};\n`
|
|
3147
3216
|
+ `const __cpuUs=__agg.timeframe?__agg.timeframe.total_us:0;\n`
|
|
3148
3217
|
+ `const __wallVsCpu={wall_us:__wallUs,cpu_self_us:__cpuUs,offcpu_us:Math.max(0,__wallUs-__cpuUs),note:'offcpu_us = wall minus on-CPU JS self time = GPU/compositor/raster/IO/idle the CPU sampler is blind to; use trace mode to attribute GPU activity'};\n`
|
|
3149
3218
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3150
|
-
+ `const __RET={result:__result,profile:__agg,profile_error:__profileError,wall_vs_cpu:__wallVsCpu,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf}};\n`
|
|
3219
|
+
+ `const __RET={result:__result,profile:__agg,profile_error:__profileError,wall_vs_cpu:__wallVsCpu,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3151
3220
|
+ emitResult + `return __RET;`;
|
|
3152
3221
|
} else if (modeMatch && modeMatch[1] === 'trace') {
|
|
3153
3222
|
const userScript = modeMatch[3];
|
|
@@ -3160,6 +3229,8 @@ function makeHostFunctions(instanceRef) {
|
|
|
3160
3229
|
+ `const __wallUs=(Date.now()-__wallT0)*1000;\n`
|
|
3161
3230
|
+ `if(__cdp){const __done=new Promise(res=>{__cdp.once('Tracing.tracingComplete',()=>res(true));setTimeout(()=>res(false),Math.min(${Math.min(navTimeout, 10000)},10000));});try{await __cdp.send('Tracing.end');}catch(e){__traceError=(__traceError||'')+' end:'+String(e&&e.message||e);}__traceComplete=await __done;}\n`
|
|
3162
3231
|
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3232
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3233
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3163
3234
|
+ perfRead
|
|
3164
3235
|
+ `const __byCat={};let __minTs=Infinity,__maxTs=-Infinity;for(const ev of __traceEvents){if(typeof ev.ts==='number'){__minTs=Math.min(__minTs,ev.ts);if(typeof ev.dur==='number')__maxTs=Math.max(__maxTs,ev.ts+ev.dur);}if(typeof ev.dur==='number'&&ev.dur>0){const c=ev.cat||'?';__byCat[c]=(__byCat[c]||0)+ev.dur;}}\n`
|
|
3165
3236
|
+ `const __sum=(re)=>Object.entries(__byCat).filter(([k])=>re.test(k)).reduce((a,[,v])=>a+v,0);\n`
|
|
@@ -3167,37 +3238,77 @@ function makeHostFunctions(instanceRef) {
|
|
|
3167
3238
|
+ `const __topCats=Object.entries(__byCat).sort((a,b)=>b[1]-a[1]).slice(0,15).map(([cat,us])=>({cat,wall_us:us}));\n`
|
|
3168
3239
|
+ `const __spanUs=(isFinite(__minTs)&&__maxTs>0)?(__maxTs-__minTs):0;\n`
|
|
3169
3240
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3170
|
-
+ `const __RET={result:__result,trace:{wall_us:__wallUs,trace_span_us:__spanUs,event_count:__traceEvents.length,complete:__traceComplete,gpu_us:__gpuUs,viz_us:__vizUs,cc_us:__ccUs,raster_us:__rasterUs,offcpu_note:'gpu_us/viz_us/cc_us are wall-clock GPU-process activity (compositor/raster/draw) captured via CDP Tracing -- the CPU sampler cannot see these',by_category:__topCats},trace_error:__traceError,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf}};\n`
|
|
3241
|
+
+ `const __RET={result:__result,trace:{wall_us:__wallUs,trace_span_us:__spanUs,event_count:__traceEvents.length,complete:__traceComplete,gpu_us:__gpuUs,viz_us:__vizUs,cc_us:__ccUs,raster_us:__rasterUs,offcpu_note:'gpu_us/viz_us/cc_us are wall-clock GPU-process activity (compositor/raster/draw) captured via CDP Tracing -- the CPU sampler cannot see these',by_category:__topCats},trace_error:__traceError,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3171
3242
|
+ emitResult + `return __RET;`;
|
|
3172
3243
|
} else if (modeMatch && modeMatch[1] === 'capture') {
|
|
3173
3244
|
const userScript = modeMatch[3];
|
|
3174
3245
|
evalBody = debugSetup
|
|
3175
3246
|
+ `const __result = await (async () => {\n${blankProbe}${gotoPrefix}try{${userScript}}catch(e){__errs.push({type:'exec',msg:String(e&&e.message||e),stack:String(e&&e.stack||'')});throw e;}\n})();\n`
|
|
3176
3247
|
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3248
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3249
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3177
3250
|
+ perfRead
|
|
3178
3251
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3179
|
-
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf}};\n`
|
|
3252
|
+
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3180
3253
|
+ emitResult + `return __RET;`;
|
|
3181
3254
|
} else if (screenshotPath) {
|
|
3182
|
-
|
|
3255
|
+
// Every path below (screenshot, DOM query, plain URL/eval, bare eval) now attaches the SAME
|
|
3256
|
+
// debugSetup + debug:{console,pageErrors,network,performance} envelope that `capture` already
|
|
3257
|
+
// had -- console.log/pageerror/uncaught-exception/failed-request/perf data was previously
|
|
3258
|
+
// silently dropped on every dispatch that didn't explicitly type the `capture` prefix, which
|
|
3259
|
+
// meant the single most common case (navigate + do something + screenshot/return a value) had
|
|
3260
|
+
// zero visibility into what the page actually logged or errored on. Debugging a live page
|
|
3261
|
+
// should never require remembering to opt in to seeing its own console/errors.
|
|
3262
|
+
evalBody = debugSetup
|
|
3263
|
+
+ `const __result = await (async () => {\n${blankProbe}${gotoPrefix}try{${evalBody}}catch(e){__errs.push({type:'exec',msg:String(e&&e.message||e),stack:String(e&&e.stack||'')});throw e;}\n})();\n`
|
|
3183
3264
|
+ `let __shotErr=null;try{await page.screenshot({path:${JSON.stringify(screenshotPath)},fullPage:false});}catch(e){__shotErr=String(e&&e.message||e);}\n`
|
|
3184
|
-
+ `const
|
|
3265
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3266
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3267
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3268
|
+
+ perfRead
|
|
3269
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3270
|
+
+ `const __RET={result:__result,screenshot_path:${JSON.stringify(screenshotPath)},screenshot_error:__shotErr,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3185
3271
|
+ emitResult + `return __RET;`;
|
|
3186
3272
|
} else if (domSelector) {
|
|
3187
|
-
evalBody =
|
|
3273
|
+
evalBody = debugSetup
|
|
3274
|
+
+ `${blankProbe}${gotoPrefix}let __RET;try{__RET=await page.evaluate((sel)=>{const out=[];const els=document.querySelectorAll(sel);for(let i=0;i<Math.min(els.length,20);i++){const e=els[i];const r=e.getBoundingClientRect();const attrs={};for(const a of e.attributes)attrs[a.name]=String(a.value).slice(0,120);out.push({tag:e.tagName.toLowerCase(),text:(e.textContent||'').trim().slice(0,200),attrs,visible:!!(r.width&&r.height),rect:{x:Math.round(r.x),y:Math.round(r.y),w:Math.round(r.width),h:Math.round(r.height)}});}return{selector:sel,match_count:els.length,elements:out};},${JSON.stringify(domSelector)});}catch(e){__RET={selector:${JSON.stringify(domSelector)},error:String(e&&e.message||e),match_count:0,elements:[]};}\n`
|
|
3275
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3276
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3277
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3278
|
+
+ perfRead
|
|
3279
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3280
|
+
+ `__RET={...__RET,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3188
3281
|
+ emitResult + `return __RET;`;
|
|
3189
|
-
} else if (startUrl) {
|
|
3190
|
-
evalBody = `${gotoPrefix}const __RET=await (async()=>{${evalBody}})();\n` + emitResult + `return __RET;`;
|
|
3191
|
-
} else if (blankProbe) {
|
|
3192
|
-
evalBody = `${blankProbe}const __RET=await (async()=>{${evalBody}})();\n` + emitResult + `return __RET;`;
|
|
3193
3282
|
} else {
|
|
3194
|
-
|
|
3283
|
+
// startUrl and/or blankProbe and/or bare-eval all collapse into this single branch now --
|
|
3284
|
+
// same debug-envelope treatment as every other path above.
|
|
3285
|
+
evalBody = debugSetup
|
|
3286
|
+
+ `${blankProbe}${gotoPrefix}const __result = await (async () => {try{${evalBody}}catch(e){__errs.push({type:'exec',msg:String(e&&e.message||e),stack:String(e&&e.stack||'')});throw e;}})();\n`
|
|
3287
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3288
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3289
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3290
|
+
+ perfRead
|
|
3291
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3292
|
+
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3293
|
+
+ emitResult + `return __RET;`;
|
|
3195
3294
|
}
|
|
3196
3295
|
const outerTimeoutMs = Math.min(timeoutMs + 6000, 126000);
|
|
3197
3296
|
let r;
|
|
3297
|
+
// Route the script through a temp file (-f) instead of inlining it on the command line (-e).
|
|
3298
|
+
// Bun on Windows has known fixed-size-buffer index-out-of-bounds panics ("index out of bounds:
|
|
3299
|
+
// index N, len 2048/4095/4096...", a real oven-sh/bun bug class, not user-code-triggerable
|
|
3300
|
+
// from a JS bug) that fire on sufficiently long argv/command-line content -- the debug/GL
|
|
3301
|
+
// instrumentation prelude alone is >2KB, so ANY non-trivial user script pushed the combined
|
|
3302
|
+
// argv over that threshold and crashed the whole `bun x` invocation outright (visible as
|
|
3303
|
+
// `panic(main thread): index out of bounds` with a Bun crash-report link, not a script error).
|
|
3304
|
+
// A temp .js file has no such length limit -- this sidesteps the Bun bug class entirely rather
|
|
3305
|
+
// than working around it script-by-script.
|
|
3306
|
+
const scriptFile = path.join(os.tmpdir(), `gm-browser-eval-${process.pid}-${execProfileSeq++}.js`);
|
|
3198
3307
|
try {
|
|
3199
|
-
|
|
3308
|
+
fs.writeFileSync(scriptFile, evalBody, 'utf-8');
|
|
3309
|
+
r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
|
|
3200
3310
|
} finally {
|
|
3311
|
+
try { fs.unlinkSync(scriptFile); } catch (_) {}
|
|
3201
3312
|
clearInflight(sessionId);
|
|
3202
3313
|
stampBrowserLastUse(cwd, sessionId);
|
|
3203
3314
|
}
|
|
@@ -3207,17 +3318,25 @@ function makeHostFunctions(instanceRef) {
|
|
|
3207
3318
|
}
|
|
3208
3319
|
const rawStderr = r.stderr || '';
|
|
3209
3320
|
const landedOnBlank = !startUrl && rawStderr.includes('__GM_BLANK__');
|
|
3210
|
-
|
|
3321
|
+
// Read the real result from resultFile (written by emitResult inside the executed script) --
|
|
3322
|
+
// NOT parsed out of playwriter's own stdout, which truncates its displayed text at a fixed
|
|
3323
|
+
// 10000 chars regardless of how much real data the script actually produced. This is the
|
|
3324
|
+
// authoritative result channel; stdout below is kept only for genuine console.log visibility,
|
|
3325
|
+
// no longer load-bearing for the actual return value.
|
|
3211
3326
|
let parsedResult;
|
|
3212
3327
|
let resultParsed = false;
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3328
|
+
try {
|
|
3329
|
+
const resultFileContent = fs.readFileSync(resultFile, 'utf-8');
|
|
3330
|
+
parsedResult = JSON.parse(resultFileContent);
|
|
3331
|
+
resultParsed = true;
|
|
3332
|
+
} catch (_) {
|
|
3333
|
+
// Script threw before reaching emitResult, or the runner itself failed/timed out before the
|
|
3334
|
+
// page-side code ever ran -- resultParsed stays false, envelope.result is simply absent,
|
|
3335
|
+
// exactly matching the pre-fix behavior for a script that never printed __GM_RESULT__.
|
|
3336
|
+
} finally {
|
|
3337
|
+
try { fs.unlinkSync(resultFile); } catch (_) {}
|
|
3220
3338
|
}
|
|
3339
|
+
const rawStdout = r.stdout || '';
|
|
3221
3340
|
const envelope = {
|
|
3222
3341
|
ok,
|
|
3223
3342
|
stdout: scrubBrowserRunnerText(rawStdout),
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1916",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|