Files
pi-goals/prototype/capture-workers.mjs
T

29 lines
1.6 KiB
JavaScript

// Event-driven trial observer. Reads only child panes named by this trial's launch trace.
import {existsSync,readFileSync,writeFileSync,watch} from 'node:fs';
import {execFile} from 'node:child_process';
import {join} from 'node:path';
const root=process.argv[2];if(!root)throw new Error('trial root required');
const trace=join(root,'launch.jsonl');if(!existsSync(trace))writeFileSync(trace,'');
const seen=new Set();const watchers=[];let count=0;
const shutdown=()=>{for(const w of watchers)w.close();process.exit(count?0:1);};
const deadline=setTimeout(shutdown,600000);
function observe(){
for(const line of readFileSync(trace,'utf8').trim().split('\n')){
let row;try{row=JSON.parse(line);}catch{continue;}
if(!['interactive.send','interactive.watch.start'].includes(row.event)||!row.sessionFile?.startsWith(root+'/agent/sessions/')||!row.surface)continue;
const pane=row.surface.replace(/^herdr:/,'');
if(seen.has(pane))continue;
seen.add(pane);
// Herdr IDs are opaque (p1A is valid); use the trace value, not a numeric-ID assumption.
row.id = row.id ?? pane.replaceAll(':','-');
// Capture once Pi renders its model/footer, without sending input or taking focus.
execFile('herdr',['pane','wait-output',pane,'--regex','gpt-|Claude|Gemini','--timeout','45000','--lines','120'],{maxBuffer:1024*1024},(error,stdout,stderr)=>{
writeFileSync(join(root,`worker-${row.id}-pane.txt`),stdout+stderr);
console.log(JSON.stringify({event:'worker-pane-captured',id:row.id,pane,ok:!error,path:join(root,`worker-${row.id}-pane.txt`)}));
count++;
if(count>=2){clearTimeout(deadline);shutdown();}
});
}
}
watchers.push(watch(trace,observe));observe();