import * as crypto from "node:crypto";import * as fs from "node:fs";import * as path from "node:path"; const key=(v:string)=>crypto.createHash("sha256").update(v).digest("hex");const ownedDir=(p:string)=>{const s=fs.lstatSync(p);if(s.isSymbolicLink()||!s.isDirectory()||(s.mode&0o777)!==0o700)throw Error("unsafe monitor runtime");};const safe=(v:string)=>/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(v)&&!v.includes(".."); export class MonitorRuntime {path:string;private now:()=>Date;constructor(o:any){if(!path.isAbsolute(o.runtimeDir)||!safe(o.profileId)||!safe(o.hubInstanceId))throw Error("unsafe monitor runtime");this.now=o.now??(()=>new Date());fs.mkdirSync(o.runtimeDir,{recursive:true,mode:0o700});ownedDir(o.runtimeDir);const journal=path.join(o.runtimeDir,"journal");fs.mkdirSync(journal,{recursive:true,mode:0o700});ownedDir(journal);const dir=path.join(journal,key(o.profileId));fs.mkdirSync(dir,{recursive:true,mode:0o700});ownedDir(dir);this.path=path.join(dir,`${key(o.hubInstanceId)}.json`);}save(state:any){const clean={tasks:(state.tasks??[]).map((t:any)=>({...t,output:String(t.output??"").slice(-8192),lateHistory:(t.lateHistory??[]).slice(-200)}))};const dir=path.dirname(this.path);ownedDir(dir);const tmp=path.join(dir,`.${path.basename(this.path)}.${crypto.randomUUID()}.tmp`);let fd:number|undefined;try{fd=fs.openSync(tmp,fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_EXCL|fs.constants.O_NOFOLLOW,0o600);fs.writeFileSync(fd,JSON.stringify(clean));fs.fsyncSync(fd);fs.closeSync(fd);fd=undefined;fs.renameSync(tmp,this.path);try{const dfd=fs.openSync(dir,fs.constants.O_RDONLY);fs.fsyncSync(dfd);fs.closeSync(dfd);}catch{}}finally{if(fd!==undefined)try{fs.closeSync(fd)}catch{};try{fs.unlinkSync(tmp)}catch{}}}load(){if(!fs.existsSync(this.path))return{tasks:[]};const st=fs.lstatSync(this.path);if(st.isSymbolicLink()||!st.isFile()||(st.mode&0o777)!==0o600)throw Error("unsafe monitor journal");try{const v=JSON.parse(fs.readFileSync(this.path,"utf8"));if(!Array.isArray(v.tasks))throw Error();return v;}catch{throw Error("unsafe monitor journal");}}reconcile(state:any,e:any){return{...state,tasks:(state.tasks??[]).map((t:any)=>{const lease=Date.parse(t.ownerLeaseExpiresAt??"");const expiry=Number.isFinite(lease)?lease:Date.parse(t.updatedAt??"")+30_000;const expired=this.now().getTime()>=expiry;return expired&&["starting","running","cancelling"].includes(t.state)&&!e.owner&&!e.socket&&!e.session&&!e.herdr?{...t,state:"orphaned"}:t;})};}}