gm-skill 2.0.1913 → 2.0.1915
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 +159 -25
- 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.1915",
|
|
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": {
|
|
@@ -861,7 +861,19 @@ function findBrowserRunner() {
|
|
|
861
861
|
// above), it just skips the registry round-trip for the tag lookup itself.
|
|
862
862
|
const cachedVersion = findCachedBunRunnerVersion();
|
|
863
863
|
const pkgSpec = cachedVersion ? `${BROWSER_RUNNER_BIN}@${cachedVersion}` : `${BROWSER_RUNNER_BIN}@latest`;
|
|
864
|
-
|
|
864
|
+
// bun is a real native binary (resolved to its actual path via `where`/`which` just above), so
|
|
865
|
+
// it can be spawned directly without an intermediate shell. shell:true here was forcing every
|
|
866
|
+
// browser-verb -e script argument through cmd.exe's argv parsing on Windows, which treats an
|
|
867
|
+
// unescaped `&` (extremely common in real target URLs, e.g. `?singleplayer&world=...` query
|
|
868
|
+
// strings) as a command separator EVEN INSIDE A QUOTED ARGUMENT -- silently truncating the
|
|
869
|
+
// script/URL mid-string, corrupting the executed code (observed live: "await page.goto(\"http:
|
|
870
|
+
// //host/path?a" with everything from the `&` onward missing, then a bogus second "command"
|
|
871
|
+
// from the leftover text failing with "'world' is not recognized..."). Passing the resolved
|
|
872
|
+
// absolute exe path with shell:false hands the args array to the OS process-create call
|
|
873
|
+
// directly, with zero shell metacharacter interpretation -- verified safe for arbitrary argv
|
|
874
|
+
// content (long strings containing `&`, quotes, newlines) via direct spawnSync reproduction.
|
|
875
|
+
const bunPath = bunR.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
|
|
876
|
+
return { cmd: bunPath || 'bun', baseArgs: ['x', pkgSpec], shell: false };
|
|
865
877
|
}
|
|
866
878
|
const cachedBin = findCachedBunRunnerBin();
|
|
867
879
|
if (cachedBin) return { cmd: process.execPath, baseArgs: [cachedBin], shell: false };
|
|
@@ -869,11 +881,16 @@ function findBrowserRunner() {
|
|
|
869
881
|
if (r.status === 0 && r.stdout.trim()) {
|
|
870
882
|
const candidates = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
871
883
|
const cmd = candidates.find(c => c.toLowerCase().endsWith('.cmd')) || candidates.find(c => !c.toLowerCase().endsWith('.ps1')) || candidates[0];
|
|
872
|
-
|
|
884
|
+
// A resolved .exe candidate is a real binary and can run shell:false (same reasoning as the bun
|
|
885
|
+
// case above). Only a genuine .cmd/.bat wrapper needs shell:true on Windows -- cmd.exe is the
|
|
886
|
+
// only thing that can directly execute a .cmd file -- so scope the shell path to that case.
|
|
887
|
+
if (cmd) return { cmd, baseArgs: [], shell: process.platform === 'win32' && cmd.toLowerCase().endsWith('.cmd') };
|
|
873
888
|
}
|
|
874
889
|
const npxR = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', shell: true });
|
|
875
890
|
if (npxR.status === 0 && npxR.stdout.trim()) {
|
|
876
|
-
|
|
891
|
+
// npx resolves to npx.cmd on Windows, which genuinely requires shell:true to execute (see
|
|
892
|
+
// above). Non-Windows npx is a real executable/shebang script and needs no shell.
|
|
893
|
+
return { cmd: 'npx', baseArgs: ['-y', BROWSER_RUNNER_BIN], shell: process.platform === 'win32' };
|
|
877
894
|
}
|
|
878
895
|
return null;
|
|
879
896
|
}
|
|
@@ -1164,11 +1181,30 @@ function playwriterHomeFor(cwd, claudeSessionId) {
|
|
|
1164
1181
|
return path.join(cwd, '.gm', `pw-sock-${sessionProfileSlug(claudeSessionId)}`);
|
|
1165
1182
|
}
|
|
1166
1183
|
|
|
1184
|
+
// cmd.exe (the shell Node's spawnSync{shell:true} uses on Windows for a .cmd/.bat target) treats
|
|
1185
|
+
// &|<>^ as command-line metacharacters EVEN INSIDE a double-quoted argument -- double-quoting alone
|
|
1186
|
+
// (the prior logic here) stops whitespace-splitting but does not stop cmd.exe from splitting
|
|
1187
|
+
// `foo&bar` into two separate commands. Real script/URL arguments passed through the browser verb
|
|
1188
|
+
// routinely contain `&` (e.g. `?a=1&b=2` query strings), which was being silently truncated
|
|
1189
|
+
// mid-argument on any shell:true path. Escaping each metacharacter with a caret (^) inside the
|
|
1190
|
+
// quoted string is the standard cmd.exe-safe encoding; this is the remaining defense for the
|
|
1191
|
+
// npx.cmd/.cmd-wrapper fallback paths that genuinely require shell:true (the bun path above no
|
|
1192
|
+
// longer needs this at all, since it now spawns the resolved .exe directly with shell:false).
|
|
1193
|
+
function cmdExeQuote(s) {
|
|
1194
|
+
const str = String(s);
|
|
1195
|
+
const escaped = str.replace(/"/g, '\\"').replace(/[&|<>^]/g, '^$&');
|
|
1196
|
+
return `"${escaped}"`;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1167
1199
|
function runBrowserRunner(pw, args, timeoutMs, cwd, claudeSessionId) {
|
|
1168
1200
|
const allArgs = [...pw.baseArgs, ...args];
|
|
1169
1201
|
const useShell = !!pw.shell;
|
|
1170
1202
|
const spawnCmd = useShell && /\s/.test(pw.cmd) ? `"${pw.cmd}"` : pw.cmd;
|
|
1171
|
-
const spawnArgs = useShell
|
|
1203
|
+
const spawnArgs = useShell
|
|
1204
|
+
? (process.platform === 'win32'
|
|
1205
|
+
? allArgs.map(a => cmdExeQuote(a))
|
|
1206
|
+
: allArgs.map(a => /[\s"]/.test(String(a)) ? `"${String(a).replace(/"/g, '\\"')}"` : a))
|
|
1207
|
+
: allArgs;
|
|
1172
1208
|
const env = { ...process.env };
|
|
1173
1209
|
const sockDir = playwriterHomeFor(cwd, claudeSessionId);
|
|
1174
1210
|
try { fs.mkdirSync(sockDir, { recursive: true }); } catch (_) {}
|
|
@@ -3087,12 +3123,58 @@ function makeHostFunctions(instanceRef) {
|
|
|
3087
3123
|
+ `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(_){}});`
|
|
3088
3124
|
+ `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(_){}});`
|
|
3089
3125
|
+ `page.evaluateOnNewDocument(()=>{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(_){}};});`
|
|
3126
|
+
// Auto-instrument every canvas's WebGL/WebGL2 context: patch getContext once (idempotent,
|
|
3127
|
+
// pre-navigation via evaluateOnNewDocument so it is in place before the page's own first
|
|
3128
|
+
// getContext call) to wrap every draw call (drawArrays/drawElements and their -Instanced
|
|
3129
|
+
// variants) with a post-call gl.getError() drain. This is exactly the manual
|
|
3130
|
+
// gl.*=function(){...gl.getError()...} monkeypatch pattern used ad hoc to root-cause GPU
|
|
3131
|
+
// rendering bugs (stale VAO/buffer bindings, sampler-unit collisions, etc) -- making it a
|
|
3132
|
+
// standing, zero-setup capability means every browser-verb dispatch against a WebGL page
|
|
3133
|
+
// gets GL error visibility for free, without hand-rolling the instrumentation each time.
|
|
3134
|
+
// window.__gmGlErrors accumulates {fn,mode,count,offset,type,error,errorName,ctxLabel} for
|
|
3135
|
+
// up to 40 distinct GL errors per page load (capped to bound memory on a runaway-error page);
|
|
3136
|
+
// window.__gmGlDrawCalls is a running total per draw-fn name for volume context.
|
|
3137
|
+
+ `page.evaluateOnNewDocument(()=>{`
|
|
3138
|
+
+ `window.__gmGlErrors=[];window.__gmGlDrawCalls={};`
|
|
3139
|
+
+ `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;};`
|
|
3140
|
+
+ `const __wrapDraw=(gl,ctxLabel)=>{`
|
|
3141
|
+
+ `['drawArrays','drawElements','drawArraysInstanced','drawElementsInstanced'].forEach(fn=>{`
|
|
3142
|
+
+ `if(typeof gl[fn]!=='function'||gl[fn].__gmWrapped)return;`
|
|
3143
|
+
+ `const __orig=gl[fn].bind(gl);`
|
|
3144
|
+
+ `const __wrapped=function(...args){`
|
|
3145
|
+
+ `const __res=__orig(...args);`
|
|
3146
|
+
+ `window.__gmGlDrawCalls[fn]=(window.__gmGlDrawCalls[fn]||0)+1;`
|
|
3147
|
+
+ `const __err=gl.getError();`
|
|
3148
|
+
+ `if(__err!==gl.NO_ERROR&&window.__gmGlErrors.length<40){`
|
|
3149
|
+
+ `let __bufSize=-1;try{__bufSize=gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER,gl.BUFFER_SIZE);}catch(_){}`
|
|
3150
|
+
+ `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]});`
|
|
3151
|
+
+ `}`
|
|
3152
|
+
+ `return __res;`
|
|
3153
|
+
+ `};`
|
|
3154
|
+
+ `__wrapped.__gmWrapped=true;gl[fn]=__wrapped;`
|
|
3155
|
+
+ `});`
|
|
3156
|
+
+ `};`
|
|
3157
|
+
+ `const __origGetContext=HTMLCanvasElement.prototype.getContext;`
|
|
3158
|
+
+ `HTMLCanvasElement.prototype.getContext=function(type,...rest){`
|
|
3159
|
+
+ `const ctx=__origGetContext.call(this,type,...rest);`
|
|
3160
|
+
+ `if(ctx&&(type==='webgl'||type==='webgl2'||type==='experimental-webgl')&&typeof ctx.getError==='function'){try{__wrapDraw(ctx,type);}catch(_){}}`
|
|
3161
|
+
+ `return ctx;`
|
|
3162
|
+
+ `};`
|
|
3163
|
+
+ `});`
|
|
3090
3164
|
+ `}catch(_){}\n`;
|
|
3091
3165
|
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`;
|
|
3092
3166
|
const blankProbe = startUrl ? '' : `try{const __u=page.url();if(__u==='about:blank'||__u===''){console.error('__GM_BLANK__');}}catch(_){}\n`;
|
|
3093
3167
|
const netFmt = `__net.slice().sort((a,b)=>(b.dur_ms||0)-(a.dur_ms||0)).slice(0,30)`;
|
|
3094
3168
|
const consoleFmt = `(__logs.length>50?[...__logs.slice(0,50),{type:'meta',text:'... '+(__logs.length-50)+' more console entries dropped'}]:__logs)`;
|
|
3095
|
-
|
|
3169
|
+
// playwriter's own executor.js truncates the DISPLAYED stdout text at a fixed 10000 chars
|
|
3170
|
+
// ("[Truncated to 10000 characters...]"), which silently ate the __GM_RESULT__ sentinel line
|
|
3171
|
+
// (always appended LAST, after any real console.log volume from the page/debug capture) on
|
|
3172
|
+
// any dispatch whose combined console output exceeded that cap -- the exact common case once
|
|
3173
|
+
// debug capture became always-on. Writing the result to a dedicated file from inside the
|
|
3174
|
+
// executed script, then reading that file directly (bypassing playwriter's own stdout
|
|
3175
|
+
// formatting entirely), makes result delivery immune to output volume.
|
|
3176
|
+
const resultFile = path.join(os.tmpdir(), `gm-browser-result-${process.pid}-${execProfileSeq++}.json`);
|
|
3177
|
+
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`;
|
|
3096
3178
|
if (modeMatch && modeMatch[1] === 'profile') {
|
|
3097
3179
|
const userScript = modeMatch[3];
|
|
3098
3180
|
const intervalUs = sampleIntervalUs;
|
|
@@ -3105,13 +3187,15 @@ function makeHostFunctions(instanceRef) {
|
|
|
3105
3187
|
+ `const __wallUs=(Date.now()-__wallT0)*1000;\n`
|
|
3106
3188
|
+ `if(__cdp){try{const __r=await __cdp.send('Profiler.stop');__profile=__r&&__r.profile||null;}catch(e){__profileError=String(e&&e.message||e);}}\n`
|
|
3107
3189
|
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3190
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3191
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3108
3192
|
+ perfRead
|
|
3109
3193
|
+ AGGREGATE_CPU_PROFILE_SRC + `\n`
|
|
3110
3194
|
+ `const __agg = __profile ? aggregateCpuProfile(__profile, ${profileTopNBrowser}) : {timeframe:null,culprits:[]};\n`
|
|
3111
3195
|
+ `const __cpuUs=__agg.timeframe?__agg.timeframe.total_us:0;\n`
|
|
3112
3196
|
+ `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`
|
|
3113
3197
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3114
|
-
+ `const __RET={result:__result,profile:__agg,profile_error:__profileError,wall_vs_cpu:__wallVsCpu,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf}};\n`
|
|
3198
|
+
+ `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`
|
|
3115
3199
|
+ emitResult + `return __RET;`;
|
|
3116
3200
|
} else if (modeMatch && modeMatch[1] === 'trace') {
|
|
3117
3201
|
const userScript = modeMatch[3];
|
|
@@ -3124,6 +3208,8 @@ function makeHostFunctions(instanceRef) {
|
|
|
3124
3208
|
+ `const __wallUs=(Date.now()-__wallT0)*1000;\n`
|
|
3125
3209
|
+ `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`
|
|
3126
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`
|
|
3127
3213
|
+ perfRead
|
|
3128
3214
|
+ `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`
|
|
3129
3215
|
+ `const __sum=(re)=>Object.entries(__byCat).filter(([k])=>re.test(k)).reduce((a,[,v])=>a+v,0);\n`
|
|
@@ -3131,37 +3217,77 @@ function makeHostFunctions(instanceRef) {
|
|
|
3131
3217
|
+ `const __topCats=Object.entries(__byCat).sort((a,b)=>b[1]-a[1]).slice(0,15).map(([cat,us])=>({cat,wall_us:us}));\n`
|
|
3132
3218
|
+ `const __spanUs=(isFinite(__minTs)&&__maxTs>0)?(__maxTs-__minTs):0;\n`
|
|
3133
3219
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3134
|
-
+ `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`
|
|
3220
|
+
+ `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`
|
|
3135
3221
|
+ emitResult + `return __RET;`;
|
|
3136
3222
|
} else if (modeMatch && modeMatch[1] === 'capture') {
|
|
3137
3223
|
const userScript = modeMatch[3];
|
|
3138
3224
|
evalBody = debugSetup
|
|
3139
3225
|
+ `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`
|
|
3140
3226
|
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3227
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3228
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3141
3229
|
+ perfRead
|
|
3142
3230
|
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3143
|
-
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf}};\n`
|
|
3231
|
+
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3144
3232
|
+ emitResult + `return __RET;`;
|
|
3145
3233
|
} else if (screenshotPath) {
|
|
3146
|
-
|
|
3234
|
+
// Every path below (screenshot, DOM query, plain URL/eval, bare eval) now attaches the SAME
|
|
3235
|
+
// debugSetup + debug:{console,pageErrors,network,performance} envelope that `capture` already
|
|
3236
|
+
// had -- console.log/pageerror/uncaught-exception/failed-request/perf data was previously
|
|
3237
|
+
// silently dropped on every dispatch that didn't explicitly type the `capture` prefix, which
|
|
3238
|
+
// meant the single most common case (navigate + do something + screenshot/return a value) had
|
|
3239
|
+
// zero visibility into what the page actually logged or errored on. Debugging a live page
|
|
3240
|
+
// should never require remembering to opt in to seeing its own console/errors.
|
|
3241
|
+
evalBody = debugSetup
|
|
3242
|
+
+ `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`
|
|
3147
3243
|
+ `let __shotErr=null;try{await page.screenshot({path:${JSON.stringify(screenshotPath)},fullPage:false});}catch(e){__shotErr=String(e&&e.message||e);}\n`
|
|
3148
|
-
+ `const
|
|
3244
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3245
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3246
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3247
|
+
+ perfRead
|
|
3248
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3249
|
+
+ `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`
|
|
3149
3250
|
+ emitResult + `return __RET;`;
|
|
3150
3251
|
} else if (domSelector) {
|
|
3151
|
-
evalBody =
|
|
3252
|
+
evalBody = debugSetup
|
|
3253
|
+
+ `${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`
|
|
3254
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3255
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3256
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3257
|
+
+ perfRead
|
|
3258
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3259
|
+
+ `__RET={...__RET,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3152
3260
|
+ emitResult + `return __RET;`;
|
|
3153
|
-
} else if (startUrl) {
|
|
3154
|
-
evalBody = `${gotoPrefix}const __RET=await (async()=>{${evalBody}})();\n` + emitResult + `return __RET;`;
|
|
3155
|
-
} else if (blankProbe) {
|
|
3156
|
-
evalBody = `${blankProbe}const __RET=await (async()=>{${evalBody}})();\n` + emitResult + `return __RET;`;
|
|
3157
3261
|
} else {
|
|
3158
|
-
|
|
3262
|
+
// startUrl and/or blankProbe and/or bare-eval all collapse into this single branch now --
|
|
3263
|
+
// same debug-envelope treatment as every other path above.
|
|
3264
|
+
evalBody = debugSetup
|
|
3265
|
+
+ `${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`
|
|
3266
|
+
+ `const __wmErrors=await page.evaluate(()=>window.__gmErrors||[]);\n`
|
|
3267
|
+
+ `const __glErrors=await page.evaluate(()=>window.__gmGlErrors||[]).catch(()=>[]);\n`
|
|
3268
|
+
+ `const __glDrawCalls=await page.evaluate(()=>window.__gmGlDrawCalls||{}).catch(()=>({}));\n`
|
|
3269
|
+
+ perfRead
|
|
3270
|
+
+ `const __allErrors=[...__errs,...__wmErrors];\n`
|
|
3271
|
+
+ `const __RET={result:__result,debug:{console:${consoleFmt},pageErrors:__allErrors,network:${netFmt},performance:__perf,gl:{errors:__glErrors,drawCalls:__glDrawCalls}}};\n`
|
|
3272
|
+
+ emitResult + `return __RET;`;
|
|
3159
3273
|
}
|
|
3160
3274
|
const outerTimeoutMs = Math.min(timeoutMs + 6000, 126000);
|
|
3161
3275
|
let r;
|
|
3276
|
+
// Route the script through a temp file (-f) instead of inlining it on the command line (-e).
|
|
3277
|
+
// Bun on Windows has known fixed-size-buffer index-out-of-bounds panics ("index out of bounds:
|
|
3278
|
+
// index N, len 2048/4095/4096...", a real oven-sh/bun bug class, not user-code-triggerable
|
|
3279
|
+
// from a JS bug) that fire on sufficiently long argv/command-line content -- the debug/GL
|
|
3280
|
+
// instrumentation prelude alone is >2KB, so ANY non-trivial user script pushed the combined
|
|
3281
|
+
// argv over that threshold and crashed the whole `bun x` invocation outright (visible as
|
|
3282
|
+
// `panic(main thread): index out of bounds` with a Bun crash-report link, not a script error).
|
|
3283
|
+
// A temp .js file has no such length limit -- this sidesteps the Bun bug class entirely rather
|
|
3284
|
+
// than working around it script-by-script.
|
|
3285
|
+
const scriptFile = path.join(os.tmpdir(), `gm-browser-eval-${process.pid}-${execProfileSeq++}.js`);
|
|
3162
3286
|
try {
|
|
3163
|
-
|
|
3287
|
+
fs.writeFileSync(scriptFile, evalBody, 'utf-8');
|
|
3288
|
+
r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
|
|
3164
3289
|
} finally {
|
|
3290
|
+
try { fs.unlinkSync(scriptFile); } catch (_) {}
|
|
3165
3291
|
clearInflight(sessionId);
|
|
3166
3292
|
stampBrowserLastUse(cwd, sessionId);
|
|
3167
3293
|
}
|
|
@@ -3171,17 +3297,25 @@ function makeHostFunctions(instanceRef) {
|
|
|
3171
3297
|
}
|
|
3172
3298
|
const rawStderr = r.stderr || '';
|
|
3173
3299
|
const landedOnBlank = !startUrl && rawStderr.includes('__GM_BLANK__');
|
|
3174
|
-
|
|
3300
|
+
// Read the real result from resultFile (written by emitResult inside the executed script) --
|
|
3301
|
+
// NOT parsed out of playwriter's own stdout, which truncates its displayed text at a fixed
|
|
3302
|
+
// 10000 chars regardless of how much real data the script actually produced. This is the
|
|
3303
|
+
// authoritative result channel; stdout below is kept only for genuine console.log visibility,
|
|
3304
|
+
// no longer load-bearing for the actual return value.
|
|
3175
3305
|
let parsedResult;
|
|
3176
3306
|
let resultParsed = false;
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3307
|
+
try {
|
|
3308
|
+
const resultFileContent = fs.readFileSync(resultFile, 'utf-8');
|
|
3309
|
+
parsedResult = JSON.parse(resultFileContent);
|
|
3310
|
+
resultParsed = true;
|
|
3311
|
+
} catch (_) {
|
|
3312
|
+
// Script threw before reaching emitResult, or the runner itself failed/timed out before the
|
|
3313
|
+
// page-side code ever ran -- resultParsed stays false, envelope.result is simply absent,
|
|
3314
|
+
// exactly matching the pre-fix behavior for a script that never printed __GM_RESULT__.
|
|
3315
|
+
} finally {
|
|
3316
|
+
try { fs.unlinkSync(resultFile); } catch (_) {}
|
|
3184
3317
|
}
|
|
3318
|
+
const rawStdout = r.stdout || '';
|
|
3185
3319
|
const envelope = {
|
|
3186
3320
|
ok,
|
|
3187
3321
|
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.1915",
|
|
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",
|