gm-skill 2.0.1914 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1914",
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": {
@@ -3123,12 +3123,58 @@ function makeHostFunctions(instanceRef) {
3123
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(_){}});`
3124
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(_){}});`
3125
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
+ + `});`
3126
3164
  + `}catch(_){}\n`;
3127
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`;
3128
3166
  const blankProbe = startUrl ? '' : `try{const __u=page.url();if(__u==='about:blank'||__u===''){console.error('__GM_BLANK__');}}catch(_){}\n`;
3129
3167
  const netFmt = `__net.slice().sort((a,b)=>(b.dur_ms||0)-(a.dur_ms||0)).slice(0,30)`;
3130
3168
  const consoleFmt = `(__logs.length>50?[...__logs.slice(0,50),{type:'meta',text:'... '+(__logs.length-50)+' more console entries dropped'}]:__logs)`;
3131
- const emitResult = `try{console.log('__GM_RESULT__'+JSON.stringify(__RET===undefined?null:__RET));}catch(__se){console.log('__GM_RESULT__'+JSON.stringify({__unserializable:String(__se&&__se.message||__se)}));}\n`;
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`;
3132
3178
  if (modeMatch && modeMatch[1] === 'profile') {
3133
3179
  const userScript = modeMatch[3];
3134
3180
  const intervalUs = sampleIntervalUs;
@@ -3141,13 +3187,15 @@ function makeHostFunctions(instanceRef) {
3141
3187
  + `const __wallUs=(Date.now()-__wallT0)*1000;\n`
3142
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`
3143
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`
3144
3192
  + perfRead
3145
3193
  + AGGREGATE_CPU_PROFILE_SRC + `\n`
3146
3194
  + `const __agg = __profile ? aggregateCpuProfile(__profile, ${profileTopNBrowser}) : {timeframe:null,culprits:[]};\n`
3147
3195
  + `const __cpuUs=__agg.timeframe?__agg.timeframe.total_us:0;\n`
3148
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`
3149
3197
  + `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`
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`
3151
3199
  + emitResult + `return __RET;`;
3152
3200
  } else if (modeMatch && modeMatch[1] === 'trace') {
3153
3201
  const userScript = modeMatch[3];
@@ -3160,6 +3208,8 @@ function makeHostFunctions(instanceRef) {
3160
3208
  + `const __wallUs=(Date.now()-__wallT0)*1000;\n`
3161
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`
3162
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`
3163
3213
  + perfRead
3164
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`
3165
3215
  + `const __sum=(re)=>Object.entries(__byCat).filter(([k])=>re.test(k)).reduce((a,[,v])=>a+v,0);\n`
@@ -3167,37 +3217,77 @@ function makeHostFunctions(instanceRef) {
3167
3217
  + `const __topCats=Object.entries(__byCat).sort((a,b)=>b[1]-a[1]).slice(0,15).map(([cat,us])=>({cat,wall_us:us}));\n`
3168
3218
  + `const __spanUs=(isFinite(__minTs)&&__maxTs>0)?(__maxTs-__minTs):0;\n`
3169
3219
  + `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`
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`
3171
3221
  + emitResult + `return __RET;`;
3172
3222
  } else if (modeMatch && modeMatch[1] === 'capture') {
3173
3223
  const userScript = modeMatch[3];
3174
3224
  evalBody = debugSetup
3175
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`
3176
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`
3177
3229
  + perfRead
3178
3230
  + `const __allErrors=[...__errs,...__wmErrors];\n`
3179
- + `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`
3180
3232
  + emitResult + `return __RET;`;
3181
3233
  } else if (screenshotPath) {
3182
- evalBody = `const __result = await (async () => {\n${blankProbe}${gotoPrefix}try{${evalBody}}catch(e){throw e;}\n})();\n`
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`
3183
3243
  + `let __shotErr=null;try{await page.screenshot({path:${JSON.stringify(screenshotPath)},fullPage:false});}catch(e){__shotErr=String(e&&e.message||e);}\n`
3184
- + `const __RET={result:__result,screenshot_path:${JSON.stringify(screenshotPath)},screenshot_error:__shotErr};\n`
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`
3185
3250
  + emitResult + `return __RET;`;
3186
3251
  } else if (domSelector) {
3187
- evalBody = `${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`
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`
3188
3260
  + 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
3261
  } else {
3194
- evalBody = `const __RET=await (async()=>{${evalBody}})();\n` + emitResult + `return __RET;`;
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;`;
3195
3273
  }
3196
3274
  const outerTimeoutMs = Math.min(timeoutMs + 6000, 126000);
3197
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`);
3198
3286
  try {
3199
- r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-e', evalBody], outerTimeoutMs, cwd, sessionId);
3287
+ fs.writeFileSync(scriptFile, evalBody, 'utf-8');
3288
+ r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', String(timeoutMs), '-f', scriptFile], outerTimeoutMs, cwd, sessionId);
3200
3289
  } finally {
3290
+ try { fs.unlinkSync(scriptFile); } catch (_) {}
3201
3291
  clearInflight(sessionId);
3202
3292
  stampBrowserLastUse(cwd, sessionId);
3203
3293
  }
@@ -3207,17 +3297,25 @@ function makeHostFunctions(instanceRef) {
3207
3297
  }
3208
3298
  const rawStderr = r.stderr || '';
3209
3299
  const landedOnBlank = !startUrl && rawStderr.includes('__GM_BLANK__');
3210
- let rawStdout = r.stdout || '';
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.
3211
3305
  let parsedResult;
3212
3306
  let resultParsed = false;
3213
- const resIdx = rawStdout.lastIndexOf('__GM_RESULT__');
3214
- if (resIdx >= 0) {
3215
- const tail = rawStdout.slice(resIdx + '__GM_RESULT__'.length);
3216
- const nl = tail.indexOf('\n');
3217
- const jsonStr = nl >= 0 ? tail.slice(0, nl) : tail;
3218
- try { parsedResult = JSON.parse(jsonStr); resultParsed = true; } catch (_) {}
3219
- rawStdout = (rawStdout.slice(0, resIdx) + (nl >= 0 ? tail.slice(nl + 1) : '')).replace(/\n+$/, '\n');
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 (_) {}
3220
3317
  }
3318
+ const rawStdout = r.stdout || '';
3221
3319
  const envelope = {
3222
3320
  ok,
3223
3321
  stdout: scrubBrowserRunnerText(rawStdout),
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1914",
3
+ "version": "2.0.1915",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1914",
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",