@code-yeongyu/senpi 2026.10.19-2 → 2026.10.20-1
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/CHANGELOG.md +18 -0
- package/dist/bundle/chunks/{anthropic-messages-VVRTT7O5.js → anthropic-messages-3QYMAEH2.js} +1 -1
- package/dist/bundle/chunks/{args-35XTBPC7.js → args-NHDT5JG3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-BUSMTCQ5.js → azure-openai-responses-2DCY6QKO.js} +1 -1
- package/dist/bundle/chunks/chunk-2JGY3V4H.js +395 -0
- package/dist/bundle/chunks/{chunk-2RLZXLXS.js → chunk-2LQZJFKQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-2IK7QUJX.js → chunk-2SM4TR4H.js} +1 -1
- package/dist/bundle/chunks/{chunk-DXOSXUVI.js → chunk-3YKKXTJM.js} +1 -1
- package/dist/bundle/chunks/chunk-6L3H4UVN.js +2 -0
- package/dist/bundle/chunks/{chunk-46NW3UKO.js → chunk-7R4CJDWT.js} +1 -1
- package/dist/bundle/chunks/chunk-AA2L2RHH.js +2 -0
- package/dist/bundle/chunks/chunk-DKBJM22C.js +5 -0
- package/dist/bundle/chunks/{chunk-Z32CYNHC.js → chunk-F4YDU66P.js} +1 -1
- package/dist/bundle/chunks/chunk-FC2YWHEC.js +6 -0
- package/dist/bundle/chunks/{chunk-T2EE5G4S.js → chunk-FRFGJC3I.js} +1 -1
- package/dist/bundle/chunks/{chunk-57RKQONO.js → chunk-GBAS4FFM.js} +1 -1
- package/dist/bundle/chunks/{chunk-FDCYQYBP.js → chunk-JVLIKUBL.js} +1 -1
- package/dist/bundle/chunks/{chunk-EKDY4H2J.js → chunk-JXCHRJA7.js} +1 -1
- package/dist/bundle/chunks/{chunk-YID5W42N.js → chunk-KXTWMPCP.js} +1 -1
- package/dist/bundle/chunks/{chunk-ADNKYKU4.js → chunk-RYQ2FFF5.js} +1 -1
- package/dist/bundle/chunks/chunk-SOMFLLPD.js +3 -0
- package/dist/bundle/chunks/{chunk-4CJJ4SK6.js → chunk-SOWFP3I7.js} +1 -1
- package/dist/bundle/chunks/{chunk-GX7MTG25.js → chunk-TK6RKAB4.js} +4 -4
- package/dist/bundle/chunks/{chunk-NN44F5QF.js → chunk-V6RY5A6T.js} +1 -1
- package/dist/bundle/chunks/{chunk-77PVTNLJ.js → chunk-W557SQYL.js} +2 -2
- package/dist/bundle/chunks/{chunk-F4ZU4RBE.js → chunk-YIRQJCPA.js} +45 -46
- package/dist/bundle/chunks/cli-main-JCQTFUHO.js +3 -0
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-4QEQ7MGK.js +1 -1
- package/dist/bundle/chunks/{dispatch-R37L3FW5.js → dispatch-YPCDRQYI.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-4UOGH7RT.js → google-generative-ai-3SKUSCIL.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-2A6KZ5TX.js → google-vertex-YHSIIE4G.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-Z5JGXNCS.js → help-fast-path-TUCMQOOF.js} +1 -1
- package/dist/bundle/chunks/{host-command-IP3QAZYI.js → host-command-WEOS6VD6.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-FRQK5GAC.js → host-lifecycle-ZDCO62Z4.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +6 -6
- package/dist/bundle/chunks/host-session-command-IGTNZ4F6.js +20 -0
- package/dist/bundle/chunks/interactive-mode-5KYT7LMW.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-EMI54PTJ.js → multi-session-host-25XQFXJC.js} +5 -5
- package/dist/bundle/chunks/openai-codex-responses-4BIPACKV.js +10 -0
- package/dist/bundle/chunks/{openai-completions-AGTH2RRA.js → openai-completions-NHDMDAS7.js} +1 -1
- package/dist/bundle/chunks/{openai-images-5UGZHY27.js → openai-images-PNXQDBSP.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-KGXDGHPI.js → openai-responses-2UOPEJLP.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-TK44VHGK.js → package-manager-cli-CF53LR5X.js} +1 -1
- package/dist/bundle/chunks/{role-F6IEUF2N.js → role-OK7RKBGF.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-XX2NXYL3.js → rotation-stream-QRRJ7G5Y.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-LOODTK3J.js +2 -0
- package/dist/bundle/chunks/{schedule-command-DYHRFIX2.js → schedule-command-LKL6DLOT.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-MLANJVB2.js +3 -0
- package/dist/bundle/chunks/session-picker-RQDVWJRL.js +2 -0
- package/dist/bundle/chunks/session-worker.js +240 -240
- package/dist/bundle/chunks/session-write-fence-66EMOO4Y.js +2 -0
- package/dist/bundle/chunks/typesafe-system-one-W2ZTYF7X.js +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +6 -6
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +0 -2
- package/dist/core/agent-session.js +4 -13
- package/dist/core/credential-pool/classify.d.ts +7 -0
- package/dist/core/credential-pool/classify.js +12 -4
- package/dist/core/credential-pool/failover.js +16 -1
- package/dist/core/credential-pool/rotation-stream.js +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +8 -62
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +25 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +38 -0
- package/dist/core/extensions/builtin/anthropic-subscription/index.d.ts +2 -2
- package/dist/core/extensions/builtin/anthropic-subscription/index.js +4 -6
- package/dist/core/extensions/builtin/anthropic-subscription/long-context-retry.d.ts +18 -0
- package/dist/core/extensions/builtin/anthropic-subscription/long-context-retry.js +74 -0
- package/dist/core/extensions/builtin/anthropic-subscription/long-context.js +13 -5
- package/dist/core/extensions/builtin/anthropic-subscription/options.d.ts +0 -10
- package/dist/core/extensions/builtin/anthropic-subscription/options.js +8 -16
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.js +3 -0
- package/dist/core/extensions/builtin/terminal/lease-file.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/lease-file.js +5 -3
- package/dist/core/foreign-session-holders.d.ts +2 -3
- package/dist/core/provider-failure.d.ts +2 -0
- package/dist/core/provider-failure.js +3 -0
- package/dist/core/reserved-keywords.js +1 -1
- package/dist/core/session-hold-errors.d.ts +2 -0
- package/dist/core/session-holder-records.d.ts +21 -0
- package/dist/core/session-holder-records.js +76 -0
- package/dist/core/session-holders.d.ts +3 -3
- package/dist/core/session-holders.js +4 -41
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.js +8 -1
- package/dist/core/session-write-fence.d.ts +18 -0
- package/dist/core/session-write-fence.js +71 -0
- package/dist/core/user-text-blocks.d.ts +11 -0
- package/dist/core/user-text-blocks.js +19 -1
- package/dist/modes/rpc/host-daemon-registration.js +2 -1
- package/dist/modes/rpc/host-generations.d.ts +6 -0
- package/dist/modes/rpc/host-generations.js +35 -4
- package/dist/modes/rpc/host-lifecycle-scratch.d.ts +9 -2
- package/dist/modes/rpc/host-lifecycle-scratch.js +26 -6
- package/dist/modes/rpc/host-lifecycle.js +1 -1
- package/dist/modes/rpc/host-reservations.d.ts +5 -0
- package/dist/modes/rpc/host-reservations.js +54 -12
- package/dist/modes/rpc/host-stalled-evidence.d.ts +27 -1
- package/dist/modes/rpc/host-stalled-evidence.js +52 -4
- package/dist/modes/rpc/multi-session-host.js +10 -1
- package/dist/modes/rpc/session-command-router.js +2 -0
- package/dist/modes/rpc/session-held.js +2 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-74V6LCWU.js +0 -2
- package/dist/bundle/chunks/chunk-DDQJSVIZ.js +0 -5
- package/dist/bundle/chunks/chunk-DY2EM44T.js +0 -395
- package/dist/bundle/chunks/chunk-HOXW5IHD.js +0 -6
- package/dist/bundle/chunks/chunk-UAQW2T7G.js +0 -2
- package/dist/bundle/chunks/cli-main-RXZVFXK7.js +0 -3
- package/dist/bundle/chunks/host-session-command-KGCQPMES.js +0 -20
- package/dist/bundle/chunks/interactive-mode-DQOPIZSI.js +0 -2
- package/dist/bundle/chunks/openai-codex-responses-I2NSK6AD.js +0 -10
- package/dist/bundle/chunks/rpc-mode-X6J3FC3N.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-XN4BAZTU.js +0 -3
- package/dist/bundle/chunks/session-picker-US5YPKC7.js +0 -2
- /package/dist/bundle/chunks/{chunk-KW6654UH.js → chunk-RPAHOJ6F.js} +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
+
import{resolveMovedPath}from"./chunk-ZMS6YEAO.js";import{advanceSessionFence}from"./chunk-SOMFLLPD.js";import{APP_NAME}from"./chunk-4J4ABCCL.js";import{ownProcessStartedAtMs,processBootAtMs,sameBoot}from"./chunk-QN2BHP3M.js";import{processStartTimeMs,sameProcessStartMs}from"./chunk-7ASLQ243.js";import{randomUUID}from"node:crypto";import{mkdir,readdir,readFile,rename,rm,stat,writeFile}from"node:fs/promises";import{dirname,join}from"node:path";var InvalidScheduledJobError=class extends Error{constructor(message){super(message),this.name="InvalidScheduledJobError"}};function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function requireString(raw,key){let value=raw[key];if(typeof value!="string"||value.length===0)throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a non-empty string`);return value}function requireCount(raw,key){let value=raw[key];if(typeof value!="number"||!Number.isSafeInteger(value)||value<0)throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a non-negative integer`);return value}function nullableCount(raw,key){return raw[key]===null||raw[key]===void 0?null:requireCount(raw,key)}function nullableString(raw,key){let value=raw[key];if(value==null)return null;if(typeof value!="string")throw new InvalidScheduledJobError(`scheduled job field "${key}" must be a string`);return value}function parseScheduledJob(raw){if(!isRecord(raw))throw new InvalidScheduledJobError("scheduled job must be a JSON object");if(raw.version!==1)throw new InvalidScheduledJobError(`unsupported scheduled job version: ${JSON.stringify(raw.version)}`);let id=requireString(raw,"id");if(!/^sch_[a-z0-9]{12}$/.test(id))throw new InvalidScheduledJobError(`invalid scheduled job id: ${id}`);let everyMs=nullableCount(raw,"everyMs");if(everyMs!==null&&everyMs<60*1e3)throw new InvalidScheduledJobError("scheduled job recurrence must be at least 60s");if(everyMs!==null&&everyMs>316224e5)throw new InvalidScheduledJobError("scheduled job recurrence must be at most 366 days");let prompt=requireString(raw,"prompt");if(Buffer.byteLength(prompt,"utf8")>16384)throw new InvalidScheduledJobError("scheduled job prompt exceeds 16384 bytes");let createdAt=requireCount(raw,"createdAt"),dueAt=requireCount(raw,"dueAt");return{version:1,id,sessionId:requireString(raw,"sessionId"),sessionFile:nullableString(raw,"sessionFile"),cwd:requireString(raw,"cwd"),prompt,createdAt,dueAt,everyMs,fireCount:requireCount(raw,"fireCount"),lastFiredAt:nullableCount(raw,"lastFiredAt"),lastError:nullableString(raw,"lastError")}}function nextRecurringDueAt(dueAt,everyMs,now){if(dueAt>now)return dueAt;let missed=Math.floor((now-dueAt)/everyMs)+1;return dueAt+missed*everyMs}function formatScheduledMessage(job,firedAt){let recurrence=job.everyMs===null?"":`, repeats every ${Math.round(job.everyMs/1e3)}s`;return`[Scheduled prompt ${job.id}: created ${new Date(job.createdAt).toISOString()}, due ${new Date(job.dueAt).toISOString()}, fired ${new Date(firedAt).toISOString()}${recurrence}]
|
|
3
|
+
${job.prompt}`}var STATE_DIRS=["pending","firing","failed"],JOB_ID=/^sch_[a-z0-9]{12}$/,RECORD_FILE=/^(sch_[a-z0-9]{12})(?:@(\d+)(?:~(\d+)-(\d+))?)?\.json$/;function scheduleDir(agentDir){return join(agentDir,"schedule")}function isScheduledJobId(id){return JOB_ID.test(id)}function isFileSystemError(error,code){return error instanceof Error&&"code"in error&&error.code===code}async function ensureDir(dir){await mkdir(dir,{recursive:!0,mode:448})}async function writeAtomic(filePath,contents){await ensureDir(dirname(filePath));let tempPath=join(dirname(filePath),`.schedule-${randomUUID()}.tmp`);try{await writeFile(tempPath,contents,{encoding:"utf8",mode:384}),await rename(tempPath,filePath)}catch(error){throw await rm(tempPath,{force:!0}),error}}function serialize(job){return`${JSON.stringify(job,null,2)}
|
|
4
|
+
`}async function readJobFile(path,expectedId){if((await stat(path)).size>65536)throw new InvalidScheduledJobError(`scheduled job file exceeds ${65536} bytes`);let parsed;try{parsed=JSON.parse(await readFile(path,"utf8"))}catch(error){throw isFileSystemError(error,"ENOENT")?error:new InvalidScheduledJobError("scheduled job file is not valid JSON")}let job=parseScheduledJob(parsed);if(job.id!==expectedId)throw new InvalidScheduledJobError(`scheduled job id ${job.id} does not match its file`);return job}async function listScheduledJobs(dir){let jobs=[],invalid=[];for(let state of STATE_DIRS){let names;try{names=await readdir(join(dir,state))}catch(error){if(isFileSystemError(error,"ENOENT"))continue;throw error}for(let name of names.sort()){let match=RECORD_FILE.exec(name);if(match===null)continue;let[,id,occurrence,pid,startedAt]=match,file=join(state,name);if(!(state==="pending"?occurrence===void 0:state==="firing"?pid!==void 0:occurrence!==void 0&&pid===void 0)||id===void 0){invalid.push({state,file,error:`unexpected record name for ${state}/`});continue}try{let job=await readJobFile(join(dir,file),id);jobs.push({state,job,file,...occurrence===void 0?{}:{occurrence:Number(occurrence)},...pid===void 0||startedAt===void 0?{}:{owner:{pid:Number(pid),processStartedAtMs:Number(startedAt)}}})}catch(error){if(isFileSystemError(error,"ENOENT"))continue;invalid.push({state,file,error:error instanceof Error?error.message:String(error)})}}}return jobs.sort((a,b)=>a.job.dueAt-b.job.dueAt),{jobs,invalid}}async function createScheduledJob(dir,input,now){let job=parseScheduledJob({version:1,id:`sch_${randomUUID().replace(/-/g,"").slice(0,12)}`,...input,createdAt:now,fireCount:0,lastFiredAt:null});return await writeAtomic(join(dir,"pending",`${job.id}.json`),serialize(job)),job}function tombstonePath(dir,id){return join(dir,"cancelled",id)}async function isCancelled(dir,id){try{return await stat(tombstonePath(dir,id)),!0}catch(error){if(isFileSystemError(error,"ENOENT"))return!1;throw error}}function runnersDir(dir){return join(dir,"runners")}import{randomUUID as randomUUID2}from"node:crypto";import{link,readdir as readdir2,rename as rename2,rm as rm2,stat as stat2,writeFile as writeFile2}from"node:fs/promises";import{join as join2}from"node:path";var TOMBSTONE_RETENTION_MS=1440*60*1e3;async function cancelScheduledJob(dir,id,options={}){if(!isScheduledJobId(id))return;let records=(await listScheduledJobs(dir)).jobs.filter(record=>record.job.id===id),representative=records.find(record=>record.state==="pending")??records[0];if(representative===void 0||options.sessionId!==void 0&&representative.job.sessionId!==options.sessionId)return;await writeAtomic(tombstonePath(dir,id),`${new Date().toISOString()}
|
|
5
|
+
`);let inFlight=(await occurrenceNumbers(dir,"firing",id)).length>0;await rm2(join2(dir,"pending",`${id}.json`),{force:!0});let failed=records.filter(record=>record.state==="failed");for(let record of failed)await rm2(join2(dir,record.file),{force:!0});return{job:representative.job,inFlight,removedFailed:failed.length}}function occurrenceFile(id,occurrence,owner){return join2("firing",`${id}@${occurrence}~${owner.pid}-${owner.processStartedAtMs}.json`)}async function restorePending(dir,record,id){let pending=join2(dir,"pending",`${id}.json`);try{await link(join2(dir,record),pending)}catch(error){if(!isFileSystemError(error,"EEXIST"))throw error}await rm2(join2(dir,record),{force:!0}),await isCancelled(dir,id)&&await rm2(pending,{force:!0})}async function claimOccurrence(dir,job,owner){let record=occurrenceFile(job.id,job.fireCount+1,owner);await ensureDir(join2(dir,"firing"));try{await rename2(join2(dir,"pending",`${job.id}.json`),join2(dir,record))}catch(error){if(isFileSystemError(error,"ENOENT"))return;throw error}if(await isCancelled(dir,job.id)){await rm2(join2(dir,record),{force:!0});return}if((await readJobFile(join2(dir,record),job.id)).fireCount!==job.fireCount){await restorePending(dir,record,job.id);return}return record}async function rearmRecurringJob(dir,next){if(await isCancelled(dir,next.id))return;let path=join2(dir,"pending",`${next.id}.json`);await writeAtomic(path,serialize(next)),await isCancelled(dir,next.id)&&await rm2(path,{force:!0})}async function occurrenceNumbers(dir,state,id){let names;try{names=await readdir2(join2(dir,state))}catch(error){if(isFileSystemError(error,"ENOENT"))return[];throw error}let prefix=`${id}@`;return names.filter(name=>name.startsWith(prefix)).map(name=>Number.parseInt(name.slice(prefix.length),10)).filter(occurrence=>Number.isSafeInteger(occurrence))}async function restoreAbandonedSchedule(dir,job,occurrence,now){if(job.everyMs===null||await isCancelled(dir,job.id))return;let pending=join2(dir,"pending",`${job.id}.json`);if(await stat2(pending).then(()=>!0,()=>!1)||[...await occurrenceNumbers(dir,"firing",job.id),...await occurrenceNumbers(dir,"failed",job.id)].some(n=>n>occurrence))return;let next={...job,fireCount:occurrence,dueAt:nextRecurringDueAt(job.dueAt,job.everyMs,now),lastError:null};await ensureDir(join2(dir,"pending"));let temp=join2(dir,"pending",`.schedule-${randomUUID2()}.tmp`);try{await writeFile2(temp,serialize(next),{encoding:"utf8",mode:384}),await link(temp,pending)}catch(error){if(!isFileSystemError(error,"EEXIST"))throw error;return}finally{await rm2(temp,{force:!0})}if(await isCancelled(dir,job.id)){await rm2(pending,{force:!0});return}return next}async function settleOccurrence(dir,record,job,occurrence,outcome){if(!outcome.ok&&!await isCancelled(dir,job.id)){let failed=join2(dir,"failed",`${job.id}@${occurrence}.json`);await writeAtomic(failed,serialize({...job,lastError:outcome.error})),await isCancelled(dir,job.id)&&await rm2(failed,{force:!0});let older=(await occurrenceNumbers(dir,"failed",job.id)).sort((a,b)=>b-a);for(let stale of older.slice(10))await rm2(join2(dir,"failed",`${job.id}@${stale}.json`),{force:!0})}await rm2(join2(dir,record),{force:!0})}async function pruneTombstones(dir,listing,now){let names;try{names=await readdir2(join2(dir,"cancelled"))}catch(error){if(isFileSystemError(error,"ENOENT"))return;throw error}let live=new Set(listing.jobs.filter(record=>record.state!=="failed").map(record=>record.job.id));for(let id of names){if(!isScheduledJobId(id)||live.has(id))continue;let info=await stat2(tombstonePath(dir,id)).catch(()=>{});info!==void 0&&now-info.mtimeMs>TOMBSTONE_RETENTION_MS&&await rm2(tombstonePath(dir,id),{force:!0})}}import{mkdir as mkdir2,readdir as readdir3,stat as stat4}from"node:fs/promises";import{join as join3}from"node:path";import{randomUUID as randomUUID3}from"node:crypto";import{link as link2,open,readFile as readFile3,rename as rename3,stat as stat3,unlink}from"node:fs/promises";import{execFile}from"node:child_process";import{readFile as readFile2}from"node:fs/promises";var LINUX_CLOCK_TICKS_PER_SECOND=100;async function linuxProcessStartMs(pid){let[stat5,systemStat]=await Promise.all([readFile2(`/proc/${pid}/stat`,"utf8"),readFile2("/proc/stat","utf8")]),afterComm=stat5.slice(stat5.lastIndexOf(")")+2).split(" "),startTicks=Number(afterComm[19]),bootSeconds=Number(/^btime (\d+)/m.exec(systemStat)?.[1]);if(!(!Number.isFinite(startTicks)||!Number.isFinite(bootSeconds)))return Math.round((bootSeconds+startTicks/LINUX_CLOCK_TICKS_PER_SECOND)*1e3)}function execText(command,args,env){return new Promise((resolve,reject)=>{execFile(command,[...args],{encoding:"utf8",timeout:5e3,windowsHide:!0,...env===void 0?{}:{env}},(error,stdout)=>{error?reject(error):resolve(stdout)})})}async function darwinProcessStartMs(pid){let text=(await execText("ps",["-o","lstart=","-p",String(pid)],{...process.env,LC_ALL:"C",LANG:"C"})).trim();if(text.length!==0)return processStartTimeMs(text)}async function windowsProcessStartMs(pid){let script=`(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().ToString('o')`,text=(await execText("powershell",["-NoProfile","-NonInteractive","-Command",script])).trim();return processStartTimeMs(text)}async function readProcessStartMs(pid){try{switch(process.platform){case"linux":return await linuxProcessStartMs(pid);case"darwin":return await darwinProcessStartMs(pid);case"win32":return await windowsProcessStartMs(pid);default:return}}catch{return}}function errorCode(error){if(typeof error=="object"&&error!==null&&"code"in error&&typeof error.code=="string")return error.code}async function unlinkIfPresent(path){try{await unlink(path)}catch(error){if(errorCode(error)!=="ENOENT")throw error}}async function readLeaseText(path){try{return await readFile3(path,"utf8")}catch(error){if(errorCode(error)==="ENOENT")return;throw error}}async function writeTemp(path,content,mode){let temp=`${path}.${process.pid}.${randomUUID3().slice(0,8)}.tmp`,file=await open(temp,"wx",mode);try{await file.writeFile(content,"utf8")}finally{await file.close()}return temp}async function publishExclusive(path,content,options={}){let temp=await writeTemp(path,content,options.mode);try{await link2(temp,path)}catch(error){let code=errorCode(error);if(code!=="ENOTSUP"&&code!=="EOPNOTSUPP"&&code!=="EPERM")throw error;let file=await open(path,"wx",options.mode);try{await file.writeFile(content,"utf8")}finally{await file.close()}}finally{await unlinkIfPresent(temp)}}async function publishReplace(path,content,options={}){let temp=await writeTemp(path,content,options.mode);try{await rename3(temp,path)}catch(error){throw await unlinkIfPresent(temp),error}}function pidAlive(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}var UNREADABLE_LOCK_STALE_MS=3e4;function readLockHolder(raw){try{let parsed=JSON.parse(raw);if(typeof parsed!="object"||parsed===null||!("pid"in parsed)||!("bootAtMs"in parsed)||!("processStartedAtMs"in parsed))return;let{pid,bootAtMs,processStartedAtMs}=parsed;return typeof pid!="number"||typeof bootAtMs!="number"||typeof processStartedAtMs!="number"?void 0:{pid,bootAtMs,processStartedAtMs}}catch{return}}async function reclaimLockState(lock,probes={}){let raw=await readLeaseText(lock);if(raw===void 0)return{state:"free"};let holder=readLockHolder(raw);if(holder===void 0)return await stat3(lock).then(info=>Date.now()-info.mtimeMs,()=>0)>UNREADABLE_LOCK_STALE_MS?{state:"stale",raw}:{state:"held"};if(!sameBoot(holder.bootAtMs,processBootAtMs()))return{state:"stale",raw};if(holder.pid===process.pid)return sameProcessStartMs(holder.processStartedAtMs,ownProcessStartedAtMs())?{state:"held",holder}:{state:"stale",raw};if(!(probes.isProcessAlive??pidAlive)(holder.pid))return{state:"stale",raw};let observed=await(probes.readProcessStartMs??readProcessStartMs)(holder.pid).catch(()=>{});return observed===void 0?{state:"held",holder,unproven:!0}:sameProcessStartMs(holder.processStartedAtMs,observed)?{state:"held",holder}:{state:"stale",raw}}async function breakStaleLock(lock,raw){let aside=`${lock}.${process.pid}.${randomUUID3().slice(0,8)}.broken`;try{await rename3(lock,aside)}catch(error){if(errorCode(error)==="ENOENT")return;throw error}let moved=await readFile3(aside,"utf8");if(moved!==raw)try{await publishExclusive(lock,moved)}catch(error){if(errorCode(error)!=="EEXIST")throw error}await unlinkIfPresent(aside)}async function takeReclaimLock(lock,probes){let mine=JSON.stringify({pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs()});for(let attempt=0;attempt<3;attempt+=1){try{return await publishExclusive(lock,mine),mine}catch(error){if(errorCode(error)!=="EEXIST")throw error}let seen=await reclaimLockState(lock,probes);if(seen.state==="held")return{holder:seen.holder};seen.state==="stale"&&await breakStaleLock(lock,seen.raw)}return{}}async function reclaimInspected(path,inspected,probes={}){let lock=`${path}.lock`,taken=await takeReclaimLock(lock,probes);if(typeof taken!="string")return{outcome:"busy",...taken.holder?{holder:taken.holder}:{}};try{return await readLeaseText(path)!==inspected?{outcome:"changed"}:(await unlinkIfPresent(path),{outcome:"removed"})}finally{await readLeaseText(lock)===taken&&await unlinkIfPresent(lock)}}var RUNNER_HEARTBEAT_STALE_MS=12e4,RUNNER_HEARTBEAT_INTERVAL_MS=3e4,DEFAULT_MAX_DELIVERY_MS=9e5,PRIVATE={mode:384};function ownRunnerIdentity(){return{pid:process.pid,processStartedAtMs:ownProcessStartedAtMs()}}function leasePath(dir,pid){return join3(runnersDir(dir),`${pid}.json`)}async function writeRunnerLease(dir,lease,now){await mkdir2(runnersDir(dir),{recursive:!0,mode:448});let identity=ownRunnerIdentity();await publishReplace(leasePath(dir,identity.pid),JSON.stringify({...identity,bootAtMs:processBootAtMs(),beatAt:now,...lease}),PRIVATE)}async function removeRunnerLease(dir){await unlinkIfPresent(leasePath(dir,process.pid))}function parseLease(raw){try{let parsed=JSON.parse(raw);if(typeof parsed!="object"||parsed===null)return;let beatAt="beatAt"in parsed?parsed.beatAt:void 0;if(typeof beatAt!="number")return;let watch="watch"in parsed&&parsed.watch===!0,exec="exec"in parsed&&typeof parsed.exec=="string"?parsed.exec:null;return{beatAt,watch,exec}}catch{return}}async function liveRunners(dir){let names;try{names=await readdir3(runnersDir(dir))}catch(error){if(error instanceof Error&&"code"in error&&error.code==="ENOENT")return[];throw error}let runners=[];for(let name of names){if(!/^\d+\.json$/.test(name))continue;let path=join3(runnersDir(dir),name),state=await reclaimLockState(path);if(state.state==="stale"&&await breakStaleLock(path,state.raw),state.state!=="held"||state.holder===void 0)continue;let raw=await readLeaseText(path),lease=raw===void 0?void 0:parseLease(raw);lease!==void 0&&runners.push({pid:state.holder.pid,processStartedAtMs:state.holder.processStartedAtMs,...lease})}return runners}async function isWatchRunnerAvailable(dir,now){return(await liveRunners(dir)).some(runner=>runner.watch&&now-runner.beatAt<=RUNNER_HEARTBEAT_STALE_MS)}function isOwnerAmong(owner,runners){return runners.some(runner=>runner.pid===owner.pid&&sameProcessStartMs(runner.processStartedAtMs,owner.processStartedAtMs))}function sessionLockPath(dir,sessionId){return join3(dir,"sessions",`${encodeURIComponent(sessionId)}.lock`)}function isDeliveryGroupAlive(pid){try{return process.kill(process.platform==="win32"?pid:-pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}function isPidAlive(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}async function orphanedDelivery(raw,lockAgeMs,probes){let parsed;try{parsed=JSON.parse(raw)}catch{return}if(typeof parsed!="object"||parsed===null)return;let field=key=>key in parsed?Reflect.get(parsed,key):void 0,boot=field("bootAtMs");if(typeof boot!="number"||!sameBoot(boot,processBootAtMs()))return;let pid=field("deliveryPid"),startedAt=field("deliveryStartedAtMs");if(typeof pid!="number"||typeof startedAt!="number"){let maxDeliveryMs=field("maxDeliveryMs"),runner=field("pid");return field("gated")===!1&&typeof maxDeliveryMs=="number"&&lockAgeMs<=maxDeliveryMs&&typeof runner=="number"?runner:void 0}if(!isDeliveryGroupAlive(pid))return;if(!isPidAlive(pid))return pid;let leaderStart=await(probes.readProcessStartMs??readProcessStartMs)(pid).catch(()=>{});return leaderStart!==void 0&&sameProcessStartMs(startedAt,leaderStart)?pid:void 0}async function acquireSessionDeliveryLock(dir,sessionId,options={}){let probes=options,path=sessionLockPath(dir,sessionId),identity={pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs(),gated:options.gated??process.platform!=="win32",maxDeliveryMs:options.maxDeliveryMs??DEFAULT_MAX_DELIVERY_MS},mine=JSON.stringify(identity);for(let attempt=0;attempt<3;attempt+=1){try{await publishExclusive(path,mine,PRIVATE);let tail=Promise.resolve(),serialized=operation=>{let run=tail.then(operation);return tail=run.catch(()=>{}),run};return{acquired:!0,attachDelivery:pid=>serialized(async()=>{let startedAt=Math.floor(Date.now()/1e3)*1e3;if(await readLeaseText(path)!==mine)throw new Error("session delivery lock was lost");let next=JSON.stringify({...identity,deliveryPid:pid,deliveryStartedAtMs:startedAt});await publishReplace(path,next,PRIVATE),mine=next}),release:()=>serialized(async()=>{await readLeaseText(path)===mine&&await unlinkIfPresent(path)})}}catch(error){let code=errorCode(error);if(code==="ENOENT"){await mkdir2(join3(dir,"sessions"),{recursive:!0,mode:448});continue}if(code!=="EEXIST")throw error}let state=await reclaimLockState(path);if(state.state==="held")return{acquired:!1,heldByPid:state.holder?.pid};if(state.state==="stale"){let lockAgeMs=await stat4(path).then(info=>Date.now()-info.mtimeMs,()=>0),delivering=await orphanedDelivery(state.raw,lockAgeMs,probes);if(delivering!==void 0)return{acquired:!1,heldByPid:delivering};await breakStaleLock(path,state.raw)}}return{acquired:!1,heldByPid:void 0}}import{randomUUID as randomUUID5}from"node:crypto";import{existsSync,mkdirSync,readFileSync,renameSync,rmdirSync,statSync,unlinkSync,writeFileSync}from"node:fs";import{dirname as dirname3,join as join6}from"node:path";var SessionHeldError=class extends Error{constructor(sessionFile,holders){let who=holders.map(h=>h.cwd?`pid ${h.pid} in ${h.cwd}`:`pid ${h.pid}`).join("; ");super(`This session is open in another ${APP_NAME} process (${who}). Quit it there, then try again.`),this.code="ESESSIONHELD",this.name="SessionHeldError",this.sessionFile=sessionFile,this.holders=holders}},SessionMovingError=class extends Error{constructor(sessionFile,moverPid){let who=moverPid===void 0?"another process":`another process (pid ${moverPid})`;super(`Session ${sessionFile} is being moved by ${who}. Try again in a moment.`),this.code="ESESSIONMOVING",this.name="SessionMovingError",this.moverPid=moverPid}},SessionMovedError=class extends Error{constructor(sessionFile){super(`Session ${sessionFile} no longer exists: another process moved it. Resume it from its new location.`),this.code="ESESSIONMOVED",this.name="SessionMovedError"}};import{readdir as readdir4}from"node:fs/promises";import{join as join4}from"node:path";var HOLDER_RECORD=/^\d+\.json$/;function parseCwd(raw){let parsed=JSON.parse(raw);if(!(typeof parsed!="object"||parsed===null||!("cwd"in parsed)))return typeof parsed.cwd=="string"?parsed.cwd:void 0}async function fenced(fenceSession){return fenceSession===void 0?!1:fenceSession().then(()=>!0,()=>!1)}function holderGeneration(parsed){return typeof parsed!="object"||parsed===null?{}:{..."host_instance"in parsed&&typeof parsed.host_instance=="string"?{host_instance:parsed.host_instance}:{},..."host_daemon_dir"in parsed&&typeof parsed.host_daemon_dir=="string"?{host_daemon_dir:parsed.host_daemon_dir}:{}}}async function liveHolders(dir,probes={},fenceSession){let holders=[];for(let name of await readdir4(dir)){if(!HOLDER_RECORD.test(name))continue;let path=join4(dir,name),before=await readLeaseText(path),state=await reclaimLockState(path,probes);if(state.state==="stale"&&await breakStaleLock(path,state.raw),state.state!=="held"||state.holder===void 0)continue;let raw=await readLeaseText(path),stable=raw!==void 0&&raw===before,parsed=stable?JSON.parse(raw):void 0;if(stable&&state.unproven===!0&&await probes.holderEnded?.(holderGeneration(parsed))===!0&&await fenced(fenceSession)){await breakStaleLock(path,raw);continue}let endpoint=typeof parsed=="object"&&parsed!==null&&"host_instance"in parsed&&typeof parsed.host_instance=="string"?{host_instance:parsed.host_instance,..."host_socket"in parsed&&typeof parsed.host_socket=="string"?{host_socket:parsed.host_socket}:{}}:{};holders.push({pid:state.holder.pid,cwd:stable?parseCwd(raw):void 0,...endpoint})}return holders}import{randomUUID as randomUUID4}from"node:crypto";import{mkdir as mkdir3,readFile as readFile4,rename as rename4,rm as rm3,writeFile as writeFile3}from"node:fs/promises";import{dirname as dirname2,join as join5}from"node:path";var InvalidSidecarStoreError=class extends Error{constructor(message,options){super(message,options),this.name="InvalidSidecarStoreError"}},UnsupportedSidecarStoreVersionError=class extends Error{constructor(message){super(message),this.name="UnsupportedSidecarStoreVersionError"}},mutationTails=new Map,snapshots=new Map;function encodedSessionId(sessionId){return encodeURIComponent(sessionId)}function sidecarFilePath(ref){return join5(ref.baseDir,`${encodedSessionId(ref.sessionId)}.json`)}function createSidecarStore(options){let ref={baseDir:options.baseDir,sessionId:options.sessionId},filePath=sidecarFilePath(ref);async function read(){let raw;try{raw=await readFile4(filePath,"utf8")}catch(error){if(isFileSystemError2(error,"ENOENT"))return null;throw error}let state=parsePayload(raw,options,ref);return snapshots.set(filePath,state),state}async function write(state){await mkdir3(dirname2(filePath),{recursive:!0}),await writeAtomic2(filePath,`${JSON.stringify(state,null,2)}
|
|
6
|
+
`,options.tempPrefix),snapshots.set(filePath,state)}function mutate(fn){return serializeByKey(filePath,async()=>{let next=await fn(await read());return await write(next),next})}return{filePath,read,write,mutate,snapshot:()=>snapshots.get(filePath),clear:()=>{snapshots.delete(filePath)}}}async function writeAtomic2(filePath,contents,tempPrefix){let tempPath=join5(dirname2(filePath),`.${tempPrefix}-${randomUUID4()}.tmp`);try{await writeFile3(tempPath,contents,{encoding:"utf8",mode:384}),await rename4(tempPath,filePath)}catch(error){try{await rm3(tempPath,{force:!0})}catch(cleanupError){throw new AggregateError([error,cleanupError],"sidecar store write failed and its temporary file could not be removed")}throw error}}function parsePayload(raw,options,ref){let parsed;try{parsed=JSON.parse(raw)}catch(error){throw new InvalidSidecarStoreError("sidecar store contains unparseable JSON",{cause:error})}if(!isRecord2(parsed))throw new InvalidSidecarStoreError("sidecar store must be a JSON object");if(parsed.version!==options.version)throw new UnsupportedSidecarStoreVersionError(`unsupported sidecar store version: ${JSON.stringify(parsed.version)} (expected ${options.version})`);if(typeof parsed.sessionId!="string"||parsed.sessionId.length===0)throw new InvalidSidecarStoreError("sidecar store is missing a sessionId");if(parsed.sessionId!==ref.sessionId)throw new InvalidSidecarStoreError(`sidecar store sessionId ${parsed.sessionId} does not match the session it was loaded for`);return options.parse(parsed,ref)}function serializeByKey(key,operation){let run=(mutationTails.get(key)??Promise.resolve()).then(operation),tail=run.then(()=>{},()=>{});return mutationTails.set(key,tail),tail.then(()=>{mutationTails.get(key)===tail&&mutationTails.delete(key)}),run}function isRecord2(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function isFileSystemError2(error,code){return error instanceof Error&&"code"in error&&error.code===code}var HOLDERS_DIR="session-holders",MOVE_LOCK="move.lock",MOVE_LOCK_STALE_MS=3e4,DEFAULT_MOVE_LOCK_WAIT_MS=5e3,MOVE_LOCK_RETRY_MS=100;function holdersDir(sessionFile,sessionId){return join6(dirname3(resolveMovedPath(sessionFile)),HOLDERS_DIR,encodedSessionId(sessionId))}function identityRecord(cwd,endpoint){return JSON.stringify({pid:process.pid,bootAtMs:processBootAtMs(),processStartedAtMs:ownProcessStartedAtMs(),...cwd===void 0?{}:{cwd},...endpoint})}function unlinkIfPresentSync(path){try{unlinkSync(path)}catch(error){if(errorCode(error)!=="ENOENT")throw error}}function removeDirIfEmpty(dir){try{rmdirSync(dir)}catch(error){let code=errorCode(error);if(code!=="ENOENT"&&code!=="ENOTEMPTY"&&code!=="EEXIST")throw error}}var holderRecordRefCounts=new Map,exitCleanupInstalled=!1;function installExitCleanup(){exitCleanupInstalled||(exitCleanupInstalled=!0,process.once("exit",()=>{for(let record of holderRecordRefCounts.keys())unlinkIfPresentSync(record)}))}function pidAlive2(pid){try{return process.kill(pid,0),!0}catch(error){return errorCode(error)==="EPERM"}}function activeMover(lock){let raw,ageMs;try{raw=readFileSync(lock,"utf8"),ageMs=Date.now()-statSync(lock).mtimeMs}catch(error){if(errorCode(error)==="ENOENT")return;throw error}if(ageMs>MOVE_LOCK_STALE_MS)return;let pid=parsePid(raw);return pid===void 0?{pid}:pid===process.pid||pidAlive2(pid)?{pid}:void 0}function parsePid(raw){try{let parsed=JSON.parse(raw);return typeof parsed!="object"||parsed===null||!("pid"in parsed)?void 0:typeof parsed.pid=="number"?parsed.pid:void 0}catch(error){if(error instanceof SyntaxError)return;throw error}}function holdSessionFile(sessionFile,sessionId,options){let dir=holdersDir(sessionFile,sessionId),record=join6(dir,`${process.pid}.json`),shared=holderRecordRefCounts.get(record)??0;shared===0&&(publishHolderRecord(dir,record,identityRecord(options.cwd,options.holderEndpoint)),installExitCleanup()),holderRecordRefCounts.set(record,shared+1);let released=!1,release=()=>{if(released)return;released=!0;let left=(holderRecordRefCounts.get(record)??1)-1;if(left>0){holderRecordRefCounts.set(record,left);return}holderRecordRefCounts.delete(record),unlinkIfPresentSync(record),removeDirIfEmpty(dir)},mover=activeMover(join6(dir,MOVE_LOCK));if(mover!==void 0)throw release(),new SessionMovingError(sessionFile,mover.pid);if(options.expectExisting&&!existsSync(resolveMovedPath(sessionFile)))throw release(),new SessionMovedError(sessionFile);return{release}}function publishHolderRecord(dir,record,content){for(let attempt=0;;attempt+=1){mkdirSync(dir,{recursive:!0});let temp=`${record}.${randomUUID5().slice(0,8)}.tmp`;try{writeFileSync(temp,content),renameSync(temp,record);return}catch(error){if(errorCode(error)!=="ENOENT"||attempt>0)throw error}}}async function liveSessionHolders(sessionFile,sessionId,probes={}){try{return await liveHolders(holdersDir(sessionFile,sessionId),probes,()=>advanceSessionFence(sessionFile))}catch(error){if(errorCode(error)==="ENOENT")return[];throw error}}async function takeMoveLock(sessionFile,lock,waitMs){let mine=identityRecord(),deadline=Date.now()+waitMs;for(;;){try{return await publishExclusive(lock,mine),mine}catch(error){let code=errorCode(error);if(code==="ENOENT"){mkdirSync(dirname3(lock),{recursive:!0});continue}if(code!=="EEXIST")throw error}let state=await reclaimLockState(lock);if(state.state==="stale"){await breakStaleLock(lock,state.raw);continue}if(state.state!=="free"){if(Date.now()>=deadline)throw new SessionMovingError(sessionFile,state.holder?.pid);await new Promise(resolve=>setTimeout(resolve,MOVE_LOCK_RETRY_MS))}}}async function withSessionMoveLock(sessionFile,sessionId,move,options={}){let dir=holdersDir(sessionFile,sessionId),lock=join6(dir,MOVE_LOCK),mine=await takeMoveLock(sessionFile,lock,options.waitMs??DEFAULT_MOVE_LOCK_WAIT_MS);try{let holders=await liveHolders(dir);if(holders.length>0)throw new SessionHeldError(sessionFile,holders);return await move()}finally{await readLeaseText(lock)===mine&&await unlinkIfPresent(lock),removeDirIfEmpty(dir)}}export{InvalidSidecarStoreError,UnsupportedSidecarStoreVersionError,encodedSessionId,sidecarFilePath,createSidecarStore,serializeByKey,nextRecurringDueAt,formatScheduledMessage,scheduleDir,readJobFile,listScheduledJobs,createScheduledJob,isCancelled,cancelScheduledJob,restorePending,claimOccurrence,rearmRecurringJob,restoreAbandonedSchedule,settleOccurrence,pruneTombstones,readProcessStartMs,errorCode,unlinkIfPresent,readLeaseText,publishExclusive,publishReplace,reclaimLockState,breakStaleLock,reclaimInspected,RUNNER_HEARTBEAT_STALE_MS,RUNNER_HEARTBEAT_INTERVAL_MS,ownRunnerIdentity,writeRunnerLease,removeRunnerLease,liveRunners,isWatchRunnerAvailable,isOwnerAmong,acquireSessionDeliveryLock,holdSessionFile,liveSessionHolders,withSessionMoveLock};
|