@sma1lboy/rove 0.9.147 → 0.9.148
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/dist/cli/{chunk-e3ye4kmk.js → chunk-0b48bpkd.js} +1 -1
- package/dist/cli/{chunk-zy6vgjdp.js → chunk-0dv2xssg.js} +1 -1
- package/dist/cli/{chunk-dcb59tsn.js → chunk-1x0vahnn.js} +1 -1
- package/dist/cli/{chunk-wpgmq5z6.js → chunk-39s1ndtm.js} +9 -9
- package/dist/cli/{chunk-fe34e0w7.js → chunk-3rqa4pyt.js} +3 -3
- package/dist/cli/{chunk-w48zjgxv.js → chunk-5ar3vspk.js} +1 -1
- package/dist/cli/{chunk-d4bqay6d.js → chunk-7ngv7v9g.js} +1 -1
- package/dist/cli/{chunk-88qjhytd.js → chunk-7pfnda5z.js} +1 -1
- package/dist/cli/{chunk-w298ayjc.js → chunk-8g456mmt.js} +1 -1
- package/dist/cli/{chunk-rgcv1wcs.js → chunk-8sbevss4.js} +1 -1
- package/dist/cli/{chunk-jvssy49v.js → chunk-91j0an0j.js} +1 -1
- package/dist/cli/{chunk-jdbfawrm.js → chunk-a4a4qaa2.js} +1 -1
- package/dist/cli/{chunk-8arm7k43.js → chunk-adez6hq4.js} +1 -1
- package/dist/cli/{chunk-q9x6d0k6.js → chunk-b8d162tf.js} +1 -1
- package/dist/cli/{chunk-0r8333z3.js → chunk-bj59a9mg.js} +1 -1
- package/dist/cli/{chunk-cehhk94g.js → chunk-cbkje3aw.js} +1 -1
- package/dist/cli/{chunk-0m9j3yap.js → chunk-dvabncrk.js} +1 -1
- package/dist/cli/{chunk-myfjt6dw.js → chunk-dx6b3dv8.js} +1 -1
- package/dist/cli/{chunk-dnb2bmtb.js → chunk-eq5f3z2f.js} +1 -1
- package/dist/cli/{chunk-0vy8351p.js → chunk-jfqbq12b.js} +1 -1
- package/dist/cli/{chunk-7ydwn358.js → chunk-jvpzy378.js} +1 -1
- package/dist/cli/{chunk-8r1shxz0.js → chunk-m0fz6g32.js} +2 -2
- package/dist/cli/{chunk-bdbgmjt7.js → chunk-me77sx3d.js} +1 -1
- package/dist/cli/{chunk-hgq2gtxx.js → chunk-mggca3k4.js} +1 -1
- package/dist/cli/{chunk-zwzkve1e.js → chunk-nrnjt5b6.js} +1 -1
- package/dist/cli/{chunk-j9g1m4dy.js → chunk-pbqwegjy.js} +1 -1
- package/dist/cli/{chunk-ryhrw647.js → chunk-pcvk98rr.js} +1 -1
- package/dist/cli/{chunk-cb0pgyq1.js → chunk-s66eh0pw.js} +2 -2
- package/dist/cli/chunk-t0kta7rb.js +5 -0
- package/dist/cli/{chunk-dwsgeq06.js → chunk-vqpa27cf.js} +1 -1
- package/dist/cli/{chunk-86j25g7y.js → chunk-vzxbe80f.js} +2 -2
- package/dist/cli/{chunk-vkch2spv.js → chunk-wsvf9eka.js} +1 -1
- package/dist/cli/{chunk-9n458sdy.js → chunk-x930m5fs.js} +1 -1
- package/dist/cli/{chunk-psx9tk3x.js → chunk-xzrqnpmh.js} +2 -2
- package/dist/cli/{chunk-p56s3ekm.js → chunk-y2yq6zca.js} +1 -1
- package/dist/cli/{chunk-p5spmf7e.js → chunk-yn5a7xcv.js} +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/kobe.js +1 -1
- package/dist/cli/pty-host-node.mjs +17 -2
- package/dist/cli/rove.js +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-6rkc7efm.js +0 -5
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{applyOnboardingChoices,detectShell,envReadyForTasks,installCompletions,maybeRunOnboarding}from"./chunk-
|
|
2
|
+
import{applyOnboardingChoices,detectShell,envReadyForTasks,installCompletions,maybeRunOnboarding}from"./chunk-s66eh0pw.js";import"./chunk-bj59a9mg.js";import"./chunk-np07kccr.js";import"./chunk-mggca3k4.js";import"./chunk-g1ybdgad.js";import"./chunk-eq5f3z2f.js";import"./chunk-q7dr9eh6.js";import"./chunk-jvxh93nz.js";import"./chunk-b8d162tf.js";import"./chunk-8g456mmt.js";import"./chunk-sdyzxayr.js";import"./chunk-gbasmvsc.js";import"./chunk-yhcz2d2r.js";import"./chunk-df7qe9y7.js";import"./chunk-gwcneg6c.js";import"./chunk-7nhdvr5v.js";import"./chunk-2fypr41w.js";import"./chunk-k3ksts6g.js";import"./chunk-gjf604bb.js";import"./chunk-qtke4x42.js";import"./chunk-xy8be792.js";import"./chunk-2brn8e1y.js";import"./chunk-9c1e5z6a.js";import"./chunk-6dn6h640.js";import"./chunk-h82kzdsn.js";import"./chunk-ef11h2am.js";import"./chunk-8ddjfc2v.js";import"./chunk-cb2fpajx.js";import"./chunk-grh6dvfv.js";import"./chunk-pw6yfj10.js";import"./chunk-x760j10f.js";import"./chunk-3bfm4hrs.js";import"./chunk-wqfp8d9w.js";export{maybeRunOnboarding,installCompletions,envReadyForTasks,detectShell,applyOnboardingChoices};
|
|
@@ -3,7 +3,7 @@ import{countCronBetween,isValidCron,latestCronAtOrBefore,nextCronAfter,tightenIn
|
|
|
3
3
|
`,{mode:384})}catch{}}async function runPluginHook(opts){let{pluginId,spec,kind,label,homeDir}=opts,timeoutMs=hookTimeoutMs(spec,kind),startedAt=Date.now();mkdirSync(pluginConfigDir(pluginId,homeDir),{recursive:!0,mode:448}),mkdirSync(pluginStateDir(pluginId,homeDir),{recursive:!0,mode:448});let exitCode=null,stdout="",stderr="",spawnError,timedOut=!1;if(await new Promise((resolve)=>{let[cmd,...args]=spec.command,child=spawn(cmd,args,{cwd:opts.pluginRoot,env:buildPluginEnv({homeDir,socketPath:opts.socketPath,binPath:opts.binPath,pluginId,pluginRoot:opts.pluginRoot,extra:opts.extraEnv}),stdio:["ignore","pipe","pipe"],detached:!0}),kill=()=>{try{if(child.pid)process.kill(-child.pid,"SIGKILL");else child.kill("SIGKILL")}catch{child.kill("SIGKILL")}},deadline=setTimeout(()=>{timedOut=!0,kill()},timeoutMs);deadline.unref?.();let slow=setTimeout(()=>{appendRecord(pluginId,homeDir,{at:startedAt,kind,label,command:spec.command,phase:"running",runningMs:Date.now()-startedAt,timeoutMs})},Math.min(HOOK_SLOW_MS,Math.floor(timeoutMs/2)));slow.unref?.(),opts.inFlight?.add(kill);let finish=()=>{clearTimeout(deadline),clearTimeout(slow),opts.inFlight?.delete(kill),resolve()};child.stdout.on("data",(chunk)=>{if(stdout.length<OUTPUT_CAP)stdout+=chunk.toString().slice(0,OUTPUT_CAP-stdout.length)}),child.stderr.on("data",(chunk)=>{if(stderr.length<OUTPUT_CAP)stderr+=chunk.toString().slice(0,OUTPUT_CAP-stderr.length)}),child.on("error",(err)=>{spawnError=String(err),finish()}),child.on("close",(code)=>{exitCode=code,finish()})}),appendRecord(pluginId,homeDir,{at:startedAt,kind,label,command:spec.command,exitCode,durationMs:Date.now()-startedAt,...timedOut?{timedOut:!0,timeoutMs}:{},...stdout?{stdout}:{},...stderr?{stderr}:{},...spawnError?{spawnError}:{}}),timedOut)opts.log?.(`plugin ${pluginId} ${label}: killed after ${timeoutMs}ms (hook did not exit)`);else if(spawnError||exitCode!==null&&exitCode!==0)opts.log?.(`plugin ${pluginId} ${label}: ${spawnError??`exit ${exitCode}`}`)}var RELOAD_DEBOUNCE_MS=150,REGISTRY_POLL_MS=200;function fileStamp(path){if(!path)return"absent";try{let s=statSync(path,{bigint:!0});return`${s.mtimeNs}:${s.size}:${s.ino}`}catch{return"absent"}}function startPluginHost(bus,opts){let host=new PluginHost(opts);bus.onPublish((event)=>host.handleChannel(event));for(let event of bus.snapshot())host.handleChannel(event);return host.start(),host}function maybeStartPluginHost(bus,options,socketPath,log){if(!options.plugins)return null;return startPluginHost(bus,{homeDir:options.homeDir,socketPath,binPath:options.plugins.binPath,log})}class PluginHost{opts;reducer=new PluginEventReducer;plugins=[];enabledIds=new Set;manifestStamps=new Map;pollTimer;registryStamp="";reloadTimer;inFlight=new Set;stopped=!1;constructor(opts){this.opts=opts}start(){this.watchRegistry(),this.plugins=this.loadPlugins();for(let plugin of this.plugins)for(let[i,hook]of plugin.manifest.startup.entries()){if(!supportsPlatform(hook,plugin.manifest,currentPluginPlatform()))continue;this.run(plugin,hook,"startup",{ROVE_PLUGIN_EVENT:"startup"},`startup[${i}]`)}}async stop(){if(this.stopped)return;if(this.stopped=!0,this.reloadTimer)clearTimeout(this.reloadTimer);if(this.pollTimer)clearInterval(this.pollTimer);for(let kill of[...this.inFlight])kill();let runs=[];for(let plugin of this.plugins)for(let[i,hook]of plugin.manifest.shutdown.entries()){if(!supportsPlatform(hook,plugin.manifest,currentPluginPlatform()))continue;runs.push(this.run(plugin,hook,"shutdown",{ROVE_PLUGIN_EVENT:"shutdown"},`shutdown[${i}]`))}await Promise.all(runs)}handleChannel(event){if(this.stopped)return;try{for(let derived of this.reducer.reduce(event))this.dispatch(derived)}catch(err){this.opts.log?.(`plugin event reduce failed on ${event.channel} \u2014 ${String(err)}`)}}handleUiReport(report){if(this.stopped)return;try{this.dispatch({event:report.kind,...report.taskId?{taskId:report.taskId,task:this.reducer.contextFor(report.taskId)}:{},...report.detail?{detail:report.detail}:{},at:Date.now()})}catch(err){this.opts.log?.(`plugin ui-report dispatch failed for ${report.kind} \u2014 ${String(err)}`)}}handleEngineReport(report){if(this.stopped)return;let event=lifecycleEventFor(report.kind,report.detail);if(!event)return;try{this.dispatch({event,taskId:report.taskId,task:this.reducer.contextFor(report.taskId),...report.vendor?{vendor:report.vendor}:{},...report.tabId?{tabId:report.tabId}:{},...report.sessionId?{sessionId:report.sessionId}:{},...report.detail?{detail:report.detail}:{},at:Date.now()})}catch(err){this.opts.log?.(`plugin engine-report dispatch failed for ${event} \u2014 ${String(err)}`)}}dispatchTo(plugin,event){let platform=currentPluginPlatform();for(let hook of plugin.manifest.events){if(hook.on!==event.event)continue;if(!supportsPlatform(hook,plugin.manifest,platform))continue;this.run(plugin,hook,"event",{ROVE_PLUGIN_EVENT:event.event,ROVE_PLUGIN_EVENT_JSON:JSON.stringify(event)},hook.on)}}dispatch(event){let platform=currentPluginPlatform();for(let plugin of this.plugins)for(let hook of plugin.manifest.events){if(hook.on!==event.event)continue;if(!supportsPlatform(hook,plugin.manifest,platform))continue;this.run(plugin,hook,"event",{ROVE_PLUGIN_EVENT:event.event,ROVE_PLUGIN_EVENT_JSON:JSON.stringify(event),...event.taskId?{ROVE_PLUGIN_TASK_ID:event.taskId}:{},...event.task?.title?{ROVE_PLUGIN_TASK_TITLE:event.task.title}:{}},hook.on)}}loadPlugins(){let registry=loadPluginRegistry(this.opts.homeDir),platform=currentPluginPlatform(),out=[],enabled=new Set,stamps=new Map;for(let entry of registry.plugins){if(!entry.enabled)continue;enabled.add(entry.id),stamps.set(entry.root,fileStamp(pluginManifestPath(entry.root)));let manifest;try{manifest=readPluginManifest(entry.root).manifest}catch(err){this.opts.log?.(`plugin ${entry.id}: manifest unreadable, skipping \u2014 ${String(err)}`);continue}if(!supportsPlatform({},manifest,platform))continue;out.push({manifest,root:entry.root})}return this.enabledIds=enabled,this.manifestStamps=stamps,out}sourcesChanged(){let stamp=fileStamp(pluginRegistryPath(this.opts.homeDir));if(stamp!==this.registryStamp)return this.registryStamp=stamp,!0;for(let[root,was]of this.manifestStamps)if(fileStamp(pluginManifestPath(root))!==was)return!0;return!1}watchRegistry(){this.registryStamp=fileStamp(pluginRegistryPath(this.opts.homeDir)),this.pollTimer=setInterval(()=>{if(!this.sourcesChanged())return;if(this.reloadTimer)clearTimeout(this.reloadTimer);this.reloadTimer=setTimeout(()=>{if(this.stopped)return;let loadedBefore=new Map(this.plugins.map((p)=>[p.manifest.id,p])),enabledBefore=this.enabledIds;this.plugins=this.loadPlugins(),this.opts.log?.(`plugin registry reloaded (${this.plugins.length} enabled)`);let at=Date.now();for(let plugin of this.plugins)if(!enabledBefore.has(plugin.manifest.id))this.dispatchTo(plugin,{event:"plugin.enabled",detail:{pluginId:plugin.manifest.id},at});for(let[id,plugin]of loadedBefore)if(!this.enabledIds.has(id))this.dispatchTo(plugin,{event:"plugin.disabled",detail:{pluginId:id},at})},RELOAD_DEBOUNCE_MS)},REGISTRY_POLL_MS),this.pollTimer.unref?.()}run(plugin,spec,kind,extraEnv,label){return runPluginHook({pluginId:plugin.manifest.id,pluginRoot:plugin.root,spec,kind,label,extraEnv,homeDir:this.opts.homeDir,socketPath:this.opts.socketPath,binPath:this.opts.binPath,log:this.opts.log,inFlight:this.inFlight})}}function isCriticalFrame(frame){if(frame.type==="event")return frame.name==="daemon.stopping";return!0}function writeFrame(client,frame){client.writer.write(frameToLine(frame),isCriticalFrame(frame))}function broadcast(clients,frame){let channel=frame.type==="event"&&frame.name!=="daemon.stopping"?frame.name:null,critical=isCriticalFrame(frame),line=null;for(let client of clients){if(!client.subscribed&&frame.type==="event")continue;if(channel&&client.channels&&!client.channels.has(channel))continue;line??=frameToLine(frame),client.writer.write(line,critical)}}function drainClientBuffer(client,onRequest){let nl=client.buffer.indexOf(`
|
|
4
4
|
`);while(nl!==-1){let line=client.buffer.slice(0,nl);if(client.buffer=client.buffer.slice(nl+1),line.trim().length>0)try{let frame=JSON.parse(line);if(frame.type!=="request")throw Error("daemon only accepts request frames from clients");onRequest(frame,client)}catch(err){writeFrame(client,{type:"response",id:"parse-error",error:{message:err instanceof Error?err.message:String(err)}})}nl=client.buffer.indexOf(`
|
|
5
5
|
`)}}import{statSync as statSync2}from"fs";import{basename,dirname,join}from"path";var POLL_MS=200;function startFileWatchTrigger(opts){if(opts.debounceMs<=0)return()=>{};let dir=dirname(opts.filePath),names=[...new Set([basename(opts.filePath),...opts.matchBasenames??[]])],timer=null,trigger=()=>{try{opts.onTrigger()}catch(err){opts.onError(err)}},schedule=()=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>{timer=null,trigger()},opts.debounceMs),timer.unref?.()},stampOf=(name)=>{try{let s=statSync2(join(dir,name),{bigint:!0});return`${s.mtimeNs}:${s.size}:${s.ino}`}catch{return"absent"}},stamps=new Map;for(let name of names)stamps.set(name,stampOf(name));let poll=setInterval(()=>{for(let name of names){let stamp=stampOf(name);if(stamp===stamps.get(name))continue;stamps.set(name,stamp),schedule()}},POLL_MS);return poll.unref?.(),()=>{if(clearInterval(poll),timer)clearTimeout(timer),timer=null}}var DEBOUNCE_MS=250,KEY_RE=/^(.+?)::(.+)$/;function startPtyExitWatch(opts){let path=opts.path??defaultPtyExitsPath(opts.homeDir),seen=new Map,baselined=!1,sweep=()=>{let host=opts.plugins();if(!host)return;let{records,readable}=readPtyExitStore(path);if(!readable)return;for(let[storeKey,record]of Object.entries(records)){if(seen.get(storeKey)===record.at)continue;if(seen.set(storeKey,record.at),!baselined)continue;host.handleUiReport({kind:"session.exited",...exitReport(record)}),publishDeath(record)}baselined=!0;for(let key of seen.keys())if(!(key in records))seen.delete(key)},publishDeath=(record)=>{let registry=opts.activity;if(!registry&&!opts.inbox)return;let death=engineDeathOf(record);if(!death)return;let{taskId,tabId,exit,at}=death;registry?.recordEngineDeath(taskId,tabId,exit,at),opts.inbox?.recordEngineDeath(taskId,tabId,{exit},at).catch((err)=>opts.log?.(`pty-exit inbox: ${String(err)}`))},stop=startFileWatchTrigger({filePath:path,debounceMs:DEBOUNCE_MS,onTrigger:sweep,onError:(err)=>opts.log?.(`pty-exit watch: ${String(err)}`)}),initial=readPtyExitStore(path);if(initial.readable){for(let[storeKey,record]of Object.entries(initial.records))seen.set(storeKey,record.at);baselined=!0}return stop}function engineDeathOf(record){let match=KEY_RE.exec(record.key),taskId=match?.[1],tabId=match?.[2];if(!taskId||!tabId)return null;let at=Date.parse(record.at);if(!Number.isFinite(at))return null;let lastLine=lastErrorLine(record.tail);return{taskId,tabId,exit:{code:record.code,signal:record.signal,...lastLine?{lastLine}:{}},at}}function lastErrorLine(tail){let last;for(let i=tail.length-1;i>=0;i--){let line=(tail[i]??"").trim();if(line.length===0)continue;if(ENGINE_EXIT_BANNER.test(line))return line;last??=line}return last}function exitReport(record){let match=KEY_RE.exec(record.key);return{...match?.[1]?{taskId:match[1]}:{},detail:{key:record.key,...match?.[2]?{tabId:match[2]}:{},pid:record.pid,code:record.code,signal:record.signal,exitedAt:record.at,tail:record.tail}}}function createActivityObserverIo(homeDir,runtime,activity){let peek=async(key)=>{let client=new KobeDaemonClient(defaultPtyHostSocketPath(homeDir));try{await client.connect();let result=await client.request("pty.peek",{key});return Buffer.from(result.data??"","base64").toString("utf8")}catch{return""}finally{client.close()}};return{onEngineExit({taskId,tabId,vendor,pid}){let key=`${taskId}::${tabId}`;peek(key).then((tail)=>recordEngineExit({key,vendor,pid,at:new Date().toISOString(),tail},defaultPtyExitsPath(homeDir))).catch((err)=>logDaemonInfo("engine-exit",`record failed for ${key}: ${String(err)}`)),logDaemonInfo("engine-exit",`${vendor} (pid ${pid??"?"}) gone from live session ${key}`)},onEngineAbsentAtStart({taskId,tabId}){let key=`${taskId}::${tabId}`,path=defaultPtyExitsPath(homeDir),existing=readPtyExitRecords(path)[`${key}#engine`];if(existing){let death=engineDeathOf(existing);if(death)activity?.recordEngineDeath(death.taskId,death.tabId,death.exit,death.at);return}peek(key).then((raw)=>{if(engineExitCodeFromTail(plainTail(raw))===null)return;recordEngineExit({key,pid:null,at:new Date().toISOString(),tail:raw,atApproximate:!0},path),logDaemonInfo("engine-exit",`recorded an unwatched death in ${key} (exit time unknown)`)}).catch((err)=>logDaemonInfo("engine-exit",`boot record failed for ${key}: ${String(err)}`))},async listSessions(){let client=new KobeDaemonClient(defaultPtyHostSocketPath(homeDir));try{return await client.connect(),((await client.request("pty.list")).sessions??[]).map((s)=>({key:s.key??"",alive:s.alive===!0,pid:typeof s.pid==="number"?s.pid:null,title:typeof s.title==="string"?s.title:"",totalBytes:typeof s.totalBytes==="number"?s.totalBytes:0}))}catch{return null}finally{client.close()}},foregroundEngines:(pids)=>runtime.foregroundEngines(pids),titleTurnHint:(vendor,title)=>runtime.titleTurnHint(vendor,title)}}var DEFAULT_OBSERVER_POLL_MS=1e4,DEFAULT_SILENCE_MS=30000,DEFAULT_WALK_EVERY_TICKS=6,DEFAULT_UNSUBSCRIBED_EVERY_TICKS=6,DEFAULT_CORRECT_AFTER_MS=20000;function startActivityObserver(activity,io,hasSubscribers,options={}){let pollMs=options.pollMs??DEFAULT_OBSERVER_POLL_MS,silenceMs=options.silenceMs??DEFAULT_SILENCE_MS,walkEvery=Math.max(1,options.walkEveryTicks??DEFAULT_WALK_EVERY_TICKS),unsubscribedEvery=Math.max(1,options.unsubscribedEveryTicks??DEFAULT_UNSUBSCRIBED_EVERY_TICKS),correctAfterMs=options.correctAfterMs??DEFAULT_CORRECT_AFTER_MS,log=options.log??logDaemonInfo,tracks=new Map,tickCount=0,unsubscribedTicks=0,inFlight=!1,bootWalkDone=!1,applyRest=(track,why)=>{if(activity.observeTab(track.taskId,track.tabId,"rest",{...track.vendor?{vendor:track.vendor}:{},correctHookRunningAfterMs:correctAfterMs})==="corrected-hook-running")log("activity-observe",`idled hook-claimed running for ${track.taskId}/${track.tabId} (${why})`)},tick=async()=>{if(inFlight)return;inFlight=!0;try{let subscribed=hasSubscribers();if(!subscribed&&unsubscribedTicks++%unsubscribedEvery!==0)return;let walkTick=!subscribed||tickCount%walkEvery===0;tickCount++;let listed=await io.listSessions();if(listed===null){for(let[key,track]of tracks)activity.observeTab(track.taskId,track.tabId,"rest",{}),tracks.delete(key);return}let sessions=listed,seen=new Set,now=Date.now(),live=[],newborn=new Set;for(let s of sessions){let parts=s.key.split("::");if(parts.length!==2||!parts[0]||!parts[1])continue;if(seen.add(s.key),!s.alive){let track2=tracks.get(s.key);applyRest(track2??{taskId:parts[0],tabId:parts[1],vendor:void 0},"session exited"),tracks.delete(s.key);continue}let track=tracks.get(s.key);if(!track)track={taskId:parts[0],tabId:parts[1],firstSeenAt:now,lastTotalBytes:s.totalBytes,lastTitle:s.title,lastActivityAt:null,vendor:void 0,enginePid:null},tracks.set(s.key,track),newborn.add(s.key);else if(s.totalBytes!==track.lastTotalBytes||s.title!==track.lastTitle)track.lastTotalBytes=s.totalBytes,track.lastTitle=s.title,track.lastActivityAt=now;live.push(track)}for(let[key,track]of tracks){if(seen.has(key)&&sessions.some((s)=>s.key===key&&s.alive))continue;applyRest(track,"session gone"),tracks.delete(key)}let toWalk=live.filter((t)=>walkTick||newborn.has(`${t.taskId}::${t.tabId}`)||t.vendor===void 0);if(toWalk.length>0){let pidByKey=new Map;for(let s of sessions)if(s.alive&&s.pid!==null)pidByKey.set(s.key,s.pid);try{let pids=toWalk.map((t)=>pidByKey.get(`${t.taskId}::${t.tabId}`)).filter((pid)=>pid!==void 0),verdicts=await io.foregroundEngines(pids),bootWalk=!bootWalkDone;bootWalkDone=!0;for(let track of toWalk){let pid=pidByKey.get(`${track.taskId}::${track.tabId}`);if(pid===void 0||!verdicts.has(pid))continue;let found=verdicts.get(pid)??null;if(found===null&&typeof track.vendor==="string")io.onEngineExit?.({taskId:track.taskId,tabId:track.tabId,vendor:track.vendor,pid:track.enginePid});else if(found===null&&track.vendor===void 0&&bootWalk)io.onEngineAbsentAtStart?.({taskId:track.taskId,tabId:track.tabId});track.vendor=found?.vendor??null,track.enginePid=found?.pid??null}}catch{}}for(let track of live){let key=`${track.taskId}::${track.tabId}`,session=sessions.find((s)=>s.key===key);if(!session||track.vendor===void 0)continue;if(io.onEngineEvidence?.(track.taskId,track.tabId,{walkVendor:track.vendor,title:session.title}),track.vendor===null){applyRest(track,"no engine in foreground");continue}let silentForMs=now-(track.lastActivityAt??track.firstSeenAt),hint=io.titleTurnHint(track.vendor,session.title),claimWorking=(why)=>{if(activity.observeTab(track.taskId,track.tabId,"working",{vendor:track.vendor??void 0})==="observed-running")log("activity-observe",`seeded running for ${track.taskId}/${track.tabId} (${why})`)};if(hint==="rest")applyRest(track,"engine title at rest");else if(track.lastActivityAt!==null&&now-track.lastActivityAt<silenceMs)claimWorking("live output");else if(hint==="working"&&silentForMs<silenceMs)claimWorking("working title frame");else if(silentForMs>=silenceMs)applyRest(track,`no output for ${Math.round(silentForMs/1000)}s`)}}catch{}finally{inFlight=!1}};tick();let timer=setInterval(()=>void tick(),pollMs);return timer.unref?.(),()=>clearInterval(timer)}function isThenable(value){return typeof value?.then==="function"}function startTicker(opts){if(opts.tickMs<=0)return()=>{};let running=!1,tick=()=>{if(opts.gate&&!opts.gate())return;if(running)return;let result;try{result=opts.run()}catch(err){logDaemonError(opts.name,err);return}if(!isThenable(result))return;running=!0,result.catch((err)=>logDaemonError(opts.name,err)).finally(()=>{running=!1})};if(opts.immediate)tick();let timer=setInterval(tick,opts.tickMs);return timer.unref?.(),()=>{clearInterval(timer),opts.onStop?.()}}var DEFAULT_AUTO_TITLE_POLL_MS=4000;async function runAutoTitlePass(orch,derive,placeholderTitle="(new task)",defaultVendor="claude"){let renamed=[],snapshot=orch.listTasks();for(let task of snapshot){if(task.title!==placeholderTitle||!task.worktreePath)continue;try{let derived=await derive(task.worktreePath,task.vendor??defaultVendor);if(!derived)continue;let title=withGroupOrdinal(derived,task.id,task.groupId,snapshot),current=orch.getTask(task.id);if(!current||current.title!==placeholderTitle)continue;await orch.setTitle(task.id,title),renamed.push({id:task.id,title})}catch(err){logDaemonError("auto-title-poller",err)}}return renamed}function withGroupOrdinal(title,taskId,groupId,snapshot){if(!groupId)return title;let siblings=snapshot.filter((t)=>t.groupId===groupId);if(siblings.length<2)return title;let ordinal=siblings.findIndex((t)=>t.id===taskId);if(ordinal<0)return title;return`${title} #${ordinal+1}/${siblings.length}`}function startAutoTitlePoller(orch,runtime,intervalMs=DEFAULT_AUTO_TITLE_POLL_MS,hasSubscribers){return startTicker({name:"auto-title-poller",tickMs:intervalMs,...hasSubscribers?{gate:hasSubscribers}:{},run:()=>runAutoTitlePass(orch,runtime.deriveTitleFromSession,runtime.placeholderTaskTitle,runtime.defaultTaskVendor)})}import{randomUUID as randomUUID2}from"crypto";import{readFile}from"fs/promises";import{homedir}from"os";import{join as join2}from"path";import{randomUUID}from"crypto";import{mkdir,rename,writeFile}from"fs/promises";import{dirname as dirname2}from"path";async function writeJsonAtomic(path,body,{mode,compact=!1}={}){await mkdir(dirname2(path),{recursive:!0});let tmp=`${path}.tmp-${process.pid}-${randomUUID()}`,text=compact?JSON.stringify(body):JSON.stringify(body,null,2);await writeFile(tmp,`${text}
|
|
6
|
-
`,{encoding:"utf8",mode}),await rename(tmp,path)}var locks=new Map;function serialized(key,fn){let run=(locks.get(key)??Promise.resolve()).then(fn),settled=run.then(()=>{return},()=>{return});return locks.set(key,settled),settled.then(()=>{if(locks.get(key)===settled)locks.delete(key)}),run}var DEFERRED_PROMPT_TTL_MS=86400000;class DeferredPromptPendingError extends Error{existing;constructor(existing){super(`task ${existing.taskId} tab ${existing.tabId} already has a deferred prompt (${existing.id})`);this.existing=existing;this.name="DeferredPromptPendingError"}}function defaultDeferredPromptsPath(homeDir=readRoveHomeDirEnv()??homedir()){return join2(homeDir,ROVE_STATE_DIR_BASENAME,"deferred-prompts.json")}function normalizeRecord(value){if(!value||typeof value!=="object"||Array.isArray(value))return null;let raw=value;if(typeof raw.id!=="string"||raw.id.length===0)return null;if(typeof raw.taskId!=="string"||raw.taskId.length===0)return null;if(typeof raw.tabId!=="string"||raw.tabId.length===0)return null;if(typeof raw.prompt!=="string"||raw.prompt.length===0)return null;if(raw.layer!=="recent-human-write"&&raw.layer!=="composer-not-empty")return null;if(typeof raw.at!=="number"||!Number.isFinite(raw.at))return null;return{id:raw.id,taskId:raw.taskId,tabId:raw.tabId,prompt:raw.prompt,layer:raw.layer,...typeof raw.senderLabel==="string"?{senderLabel:raw.senderLabel}:{},...typeof raw.senderTaskId==="string"?{senderTaskId:raw.senderTaskId}:{},at:raw.at,...typeof raw.deliveredAt==="number"&&Number.isFinite(raw.deliveredAt)?{deliveredAt:raw.deliveredAt}:{},...typeof raw.deliveryStartedAt==="number"&&Number.isFinite(raw.deliveryStartedAt)?{deliveryStartedAt:raw.deliveryStartedAt}:{}}}async function readStore(path){try{let parsed=JSON.parse(await readFile(path,"utf8"));if(!Array.isArray(parsed.records))return{version:1,records:[]};return{version:1,records:parsed.records.map(normalizeRecord).filter((r)=>r!==null)}}catch(err){if(err.code==="ENOENT")return{version:1,records:[]};throw err}}async function writeStore(path,records){let body={version:1,records:[...records]};await writeJsonAtomic(path,body)}var SUBSYSTEM="deferred-prompts";class DeferredPromptsStore{path;now;claims=new Map;constructor(path=defaultDeferredPromptsPath(),now=()=>Date.now()){this.path=path;this.now=now}enqueue(operation){return serialized(this.path,operation)}async file(record){return await this.enqueue(async()=>{let now=this.now(),store=await readStore(this.path),kept=[],occupied=null;for(let existing of store.records){if(now-existing.at>DEFERRED_PROMPT_TTL_MS){if(this.claims.has(existing.id)){if(existing.taskId===record.taskId&&existing.tabId===record.tabId)throw Error(`deferred prompt ${existing.id} expiry cleanup is in flight`);kept.push(existing);continue}if(existing.taskId===record.taskId&&existing.tabId===record.tabId){logDaemonInfo(SUBSYSTEM,`expired deferred prompt ${existing.id} for ${existing.taskId}::${existing.tabId} replaced by a new deferral`);continue}kept.push(existing);continue}if(existing.taskId===record.taskId&&existing.tabId===record.tabId)occupied=existing;kept.push(existing)}if(occupied)throw new DeferredPromptPendingError(occupied);let next={...record,id:randomUUID2()};return kept.push(next),await writeStore(this.path,kept),next})}async get(id){return await this.enqueue(async()=>(await readStore(this.path)).records.find((r)=>r.id===id)??null)}async claim(id){return await this.enqueue(async()=>{if(this.claims.has(id))return{kind:"in-flight"};let record=(await readStore(this.path)).records.find((candidate)=>candidate.id===id);if(!record)return{kind:"missing"};let claimId=randomUUID2(),finish=()=>{},done=new Promise((resolve)=>{finish=resolve});return this.claims.set(id,{claimId,done,finish}),{kind:"claimed",claim:{claimId,record}}})}async beginDelivery(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);let started=current.deliveryStartedAt!==void 0?current:{...current,deliveryStartedAt:this.now()};if(current.deliveryStartedAt===void 0)await writeStore(this.path,store.records.map((record)=>record.id===current.id?started:record));return started})}async resetDelivery(claim){await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);if(current.deliveryStartedAt===void 0)return;let{deliveryStartedAt:_deliveryStartedAt,...reset}=current;await writeStore(this.path,store.records.map((record)=>record.id===current.id?reset:record))})}async markDelivered(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);let delivered=current.deliveredAt?current:{...current,deliveredAt:this.now()};if(!current.deliveredAt)await writeStore(this.path,store.records.map((record)=>record.id===current.id?delivered:record));return delivered})}async completeClaim(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),kept=store.records.filter((record)=>record.id!==claim.record.id);if(kept.length!==store.records.length)await writeStore(this.path,kept);return this.finishClaim(claim.record.id),kept.length!==store.records.length})}async releaseClaim(claim){await this.enqueue(async()=>{this.requireClaim(claim),this.finishClaim(claim.record.id)})}requireClaim(claim){if(this.claims.get(claim.record.id)?.claimId!==claim.claimId)throw Error(`deferred prompt ${claim.record.id} claim is no longer owned`)}finishClaim(id){let active=this.claims.get(id);this.claims.delete(id),active?.finish()}async waitForClaims(taskId,tabId){for(;;){let waiting=await this.enqueue(async()=>{return(await readStore(this.path)).records.filter((record)=>record.taskId===taskId&&(tabId===void 0||record.tabId===tabId)).flatMap((record)=>{let active=this.claims.get(record.id);return active?[active.done]:[]})});if(waiting.length===0)return;await Promise.all(waiting)}}async list(){return await this.enqueue(async()=>{let now=this.now(),store=await readStore(this.path),expired=[];return{records:store.records.filter((record)=>{if(now-record.at<=DEFERRED_PROMPT_TTL_MS)return!0;return expired.push(record),logDaemonInfo(SUBSYSTEM,`expired deferred prompt ${record.id} for ${record.taskId}::${record.tabId} (age ${Math.round((now-record.at)/1000)}s) \u2014 cleanup requested`),!1}),expired}})}async resolve(id){return await this.enqueue(async()=>{let store=await readStore(this.path),kept=store.records.filter((r)=>r.id!==id);if(kept.length===store.records.length)return!1;return await writeStore(this.path,kept),!0})}async discardTab(taskId,tabId,reason){return await this.waitForClaims(taskId,tabId),await this.enqueue(async()=>{let store=await readStore(this.path),dropped=store.records.filter((record)=>record.taskId===taskId&&record.tabId===tabId);if(dropped.length===0)return[];for(let record of dropped)logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${record.id} for ${taskId}::${tabId} \u2014 ${reason}`);return await writeStore(this.path,store.records.filter((record)=>record.taskId!==taskId||record.tabId!==tabId)),dropped})}async discard(id,reason){for(;;){let waiting=await this.enqueue(async()=>this.claims.get(id)?.done);if(!waiting)break;await waiting}return await this.enqueue(async()=>{let store=await readStore(this.path),dropped=store.records.find((record)=>record.id===id);if(!dropped)return null;return logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${dropped.id} for ${dropped.taskId}::${dropped.tabId} \u2014 ${reason}`),await writeStore(this.path,store.records.filter((record)=>record.id!==id)),dropped})}async listForTask(taskId){return await this.enqueue(async()=>(await readStore(this.path)).records.filter((r)=>r.taskId===taskId))}async deleteTask(taskId){await this.waitForClaims(taskId),await this.enqueue(async()=>{let store=await readStore(this.path),kept=store.records.filter((r)=>r.taskId!==taskId);if(kept.length===store.records.length)return;for(let dropped of store.records)if(dropped.taskId===taskId)logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${dropped.id} \u2014 task ${taskId} deleted`);await writeStore(this.path,kept)})}}function resolveStandingTask(orch,automation){let id=automation.sessionTaskId;if(!id)return{task:null,hadStaleLink:!1};let task=orch.getTask(id);if(!task||task.deletion||!task.worktreePath)return{task:null,hadStaleLink:!0};return{task}}async function createRunTask(deps,automation){return await deps.orch.createTask({repo:automation.repo,title:automation.name,...automation.vendor?{vendor:automation.vendor}:{},...automation.baseRef?{baseRef:automation.baseRef}:{},...automation.persistentSession?{routine:{automationId:automation.id}}:{}})}async function spawnWithPrompt(deps,automation,taskId,status){let outcome=await deps.runtime.startTaskSessionWithPrompt(deps.link(),taskId,automation.prompt);if(!outcome.started)return{status:"dispatch_failed",taskId,error:outcome.error??"engine session did not start"};return{status,taskId}}async function deferBlockedPrompt(deps,automation,taskId,tabId,layer){if(!deps.deferred||!deps.inbox)return{status:"dispatch_failed",taskId,error:`composer busy (${layer}) and no deferred-prompt store`};let record;try{record=await deps.deferred.file({taskId,tabId,prompt:automation.prompt,layer,senderLabel:`routine: ${automation.name}`,at:(deps.now??Date.now)()})}catch(error){if(!(error instanceof DeferredPromptPendingError))throw error;return await deps.inbox.recordPromptDeferred(error.existing.taskId,error.existing.tabId,error.existing.id,error.existing.layer,error.existing.at+DEFERRED_PROMPT_TTL_MS),{status:"dispatch_failed",taskId,error:`tab ${tabId} already has a deferred prompt (${error.existing.id})`}}return await deps.inbox.recordPromptDeferred(taskId,tabId,record.id,layer,record.at+DEFERRED_PROMPT_TTL_MS),logDaemonInfo("automation",`deferred ${automation.name} task=${taskId} tab=${tabId} layer=${layer}`),{status:"deferred",taskId}}async function dispatchAutomation(deps,automation){if(!automation.persistentSession){let task2=await createRunTask(deps,automation);return await spawnWithPrompt(deps,automation,task2.id,"dispatched")}let standing=resolveStandingTask(deps.orch,automation);if(standing.task===null){let task2=await createRunTask(deps,automation);return{...await spawnWithPrompt(deps,automation,task2.id,"dispatched"),sessionTaskIdToSet:task2.id,...standing.hadStaleLink?{sessionTaskIdToClear:!0}:{}}}let task=standing.task,result=await deps.runtime.deliverPromptToLiveEngineDetailed({id:task.id,vendor:task.vendor,command:task.command,worktreePath:task.worktreePath},automation.prompt);if(result.outcome==="delivered")return logDaemonInfo("automation",`delivered ${automation.name} task=${task.id} tab=${result.tabId}`),{status:"dispatched",taskId:task.id};if(result.outcome==="busy")return await deferBlockedPrompt(deps,automation,task.id,result.tabId,result.layer);return await spawnWithPrompt(deps,automation,task.id,"revived")}import{spawn as spawn2}from"child_process";import{existsSync}from"fs";var MAX_OUTPUT_CHARS=4000,MAX_TIMEOUT_SECONDS=600;function tail(chunks){let text=Buffer.concat(chunks.map((c)=>typeof c==="string"?Buffer.from(c):c)).toString("utf8");if(text.length<=MAX_OUTPUT_CHARS)return text;let points=Array.from(text);return points.length<=MAX_OUTPUT_CHARS?text:points.slice(-MAX_OUTPUT_CHARS).join("")}function runAutomationPrecheck(precheck,cwd,shell=resolveLoginShell({fallback:"/bin/sh"})){let startedAt=Date.now(),timeoutMs=Math.min(Math.max(precheck.timeoutSeconds,1),MAX_TIMEOUT_SECONDS)*1000;if(!existsSync(cwd))return Promise.resolve({exitCode:null,timedOut:!1,stdout:"",stderr:`working directory does not exist: ${cwd}`,durationMs:0});return new Promise((resolve)=>{let out=[],err=[],settled=!1,timedOut=!1,controller=new AbortController,finish=(exitCode,timedOut2)=>{if(settled)return;settled=!0,clearTimeout(timer),resolve({exitCode,timedOut:timedOut2,stdout:tail(out),stderr:tail(err),durationMs:Date.now()-startedAt})},timer=setTimeout(()=>{timedOut=!0,controller.abort(),finish(null,!0)},timeoutMs);timer.unref?.();let child;try{child=spawn2(shell,["-ilc",precheck.command],{cwd,stdio:["ignore","pipe","pipe"],signal:controller.signal,killSignal:"SIGKILL"})}catch(spawnError){err.push(spawnError instanceof Error?spawnError.message:String(spawnError)),finish(null,!1);return}child.stdout?.on("data",(chunk)=>out.push(chunk)),child.stderr?.on("data",(chunk)=>err.push(chunk)),child.on("error",(spawnError)=>{if(timedOut){finish(null,!0);return}err.push(spawnError.message),finish(null,!1)}),child.on("close",(code)=>finish(code,timedOut))})}function precheckPassed(result){return result.exitCode===0&&!result.timedOut}function formatPrecheckSkip(result){if(result.timedOut)return`precheck timed out after ${result.durationMs}ms`;if(result.exitCode===null)return`precheck could not run: ${result.stderr.trim()||"unknown error"}`;return`precheck exited ${result.exitCode}`}var DEFAULT_AUTOMATION_TICK_MS=60000;function dueAutomations(automations,nowMs){return automations.filter((a)=>{if(!a.enabled)return!1;let at=Date.parse(a.nextRunAt);return Number.isFinite(at)&&at<=nowMs})}function resolveDueOccurrence(automation,nowMs,tickMs=DEFAULT_AUTOMATION_TICK_MS){let notBefore=Date.parse(automation.createdAt),scheduledFor=latestCronAtOrBefore(automation.schedule,nowMs,Number.isFinite(notBefore)?notBefore:0);if(scheduledFor===null)return null;let graceMs=automation.missedRunGraceMinutes*60000+Math.max(tickMs,0);return{scheduledFor,missed:nowMs-scheduledFor>graceMs}}function runEventFor(status){if(status==="dispatched"||status==="revived"||status==="deferred")return"automation.dispatched";if(status==="dispatch_failed")return"automation.failed";return"automation.skipped"}function resolveLink(link){return typeof link==="function"?link():link}function emitRunEvent(deps,automation,status,args,extra){deps.plugins?.()?.handleUiReport({kind:runEventFor(status),...extra.taskId?{taskId:extra.taskId}:{},detail:{automationId:automation.id,name:automation.name,repo:automation.repo,status,trigger:args.trigger,scheduledFor:new Date(args.scheduledFor).toISOString(),...extra.error?{error:extra.error}:{}}})}async function raiseOrClearInboxEpisode(deps,automation,status,extra){let inbox=deps.inbox;if(!inbox)return;let now=deps.now??Date.now;await(automationRunNeedsAttention(status)?inbox.recordRoutineFailure({automationId:automation.id,name:automation.name,status,...extra.error?{error:extra.error}:{}},extra.taskId??null,now()):inbox.deleteRoutineEpisode?.(automation.id))?.catch((err)=>logDaemonError("automation-inbox",err))}async function recordSkip(deps,automation,status,scheduledFor,error){let now=deps.now??Date.now;await deps.store.recordRun({automationId:automation.id,scheduledFor:new Date(scheduledFor).toISOString(),status,trigger:"scheduled",at:new Date(now()).toISOString(),error}),emitRunEvent(deps,automation,status,{scheduledFor,trigger:"scheduled"},{error})}function droppedOccurrences(automation,scheduledFor){let armedAt=Date.parse(automation.nextRunAt);if(!Number.isFinite(armedAt)||armedAt>=scheduledFor)return null;let count=countCronBetween(automation.schedule,armedAt,scheduledFor);return count>0?{count,firstMs:armedAt}:null}async function runAutomationOnce(deps,automation,args){let now=deps.now??Date.now,record=async(status,extra={})=>{return await deps.store.recordRun({automationId:automation.id,scheduledFor:new Date(args.scheduledFor).toISOString(),status,trigger:args.trigger,at:new Date(now()).toISOString(),...extra}),emitRunEvent(deps,automation,status,args,extra),await raiseOrClearInboxEpisode(deps,automation,status,extra),status};if(args.trigger==="scheduled"&&automation.precheck){let result=await runAutomationPrecheck(automation.precheck,automation.repo);if(!precheckPassed(result))return logDaemonInfo("automation",`skip ${automation.name}: ${formatPrecheckSkip(result)}`),await record("skipped_precheck",{precheckResult:result,error:formatPrecheckSkip(result)})}let outcome;try{outcome=await dispatchAutomation({orch:deps.orch,runtime:deps.runtime,link:()=>resolveLink(deps.link),...deps.deferred?{deferred:deps.deferred}:{},...deps.inbox?{inbox:deps.inbox}:{},...deps.now?{now:deps.now}:{}},automation)}catch(err){let error=err instanceof Error?err.message:String(err);return logDaemonError("automation-dispatch",err),await record("skipped_unavailable",{error})}if(outcome.sessionTaskIdToSet)await deps.store.update(automation.id,{sessionTaskId:outcome.sessionTaskIdToSet}).catch((err)=>logDaemonError("automation-session-link",err));else if(outcome.sessionTaskIdToClear)await deps.store.update(automation.id,{sessionTaskId:null}).catch((err)=>logDaemonError("automation-session-unlink",err));if(outcome.status==="dispatched"||outcome.status==="revived")logDaemonInfo("automation",`${outcome.status} ${automation.name} task=${outcome.taskId}`);return await record(outcome.status,{...outcome.taskId?{taskId:outcome.taskId}:{},...outcome.error?{error:outcome.error}:{}})}async function sweepAutomations(deps,tickMs=DEFAULT_AUTOMATION_TICK_MS){let now=deps.now??Date.now;for(let automation of dueAutomations(deps.store.list(),now())){let nowMs=now(),occurrence=resolveDueOccurrence(automation,nowMs,tickMs);if(!occurrence){await deps.store.advanceNextRun(automation.id,nowMs);continue}let dropped=droppedOccurrences(automation,occurrence.scheduledFor);if(await deps.store.advanceNextRun(automation.id,occurrence.scheduledFor),dropped){let first=new Date(dropped.firstMs).toISOString();await recordSkip(deps,automation,"skipped_missed",dropped.firstMs,`${dropped.count} earlier occurrence${dropped.count===1?"":"s"} never ran (from ${first})`).catch((err)=>logDaemonError("automation-dropped",err))}if(occurrence.missed){await recordSkip(deps,automation,"skipped_missed",occurrence.scheduledFor,`missed by more than the ${automation.missedRunGraceMinutes}m grace window`);continue}await runAutomationOnce(deps,automation,{scheduledFor:occurrence.scheduledFor,trigger:"scheduled"}).catch((err)=>logDaemonError("automation-run",err))}}function startAutomationRunner(deps,tickMs=DEFAULT_AUTOMATION_TICK_MS){return startTicker({name:"automation-sweep",tickMs,run:()=>sweepAutomations(deps,tickMs)})}var DEFAULT_CONTEXT_USAGE_TICK_MS=1e4,CONTEXT_USAGE_TIMEOUT_MS=4000,CONTEXT_USAGE_SLOW_RETRY_MS=60000,CONTEXT_USAGE_MIN_INTERVAL_MS=5000;function sameContextUsage(a,b){return a.contextTokens===b.contextTokens&&a.contextWindowTokens===b.contextWindowTokens&&a.approximate===b.approximate&&a.inputTokens===b.inputTokens&&a.outputTokens===b.outputTokens&&a.cacheReadTokens===b.cacheReadTokens&&a.cacheCreationTokens===b.cacheCreationTokens}function contextUsageTargets(states,getVendor){let seen=new Set,targets=[];for(let state of states){if(!state.tabId||!state.sessionId)continue;let vendor=getVendor(state.taskId);if(!vendor)continue;let key=`${state.taskId}::${state.tabId}`;if(seen.has(key))continue;seen.add(key),targets.push({key,vendor,sessionId:state.sessionId})}return targets}class ContextUsageCollector{registry;orch;bus;runtime;options;entries=new Map;stopped=!1;constructor(registry,orch,bus,runtime,options={}){this.registry=registry;this.orch=orch;this.bus=bus;this.runtime=runtime;this.options=options}tick(){if(this.stopped)return;if(this.options.hasSubscribers&&!this.options.hasSubscribers())return;try{let targets=contextUsageTargets(this.registry.currentNonIdle(),(id)=>this.orch.getTask(id)?.vendor),live=new Set(targets.map((t)=>t.key)),pruned=!1;for(let key of[...this.entries.keys()]){if(live.has(key))continue;if(this.entries.get(key)?.value)pruned=!0;this.entries.delete(key)}if(pruned)this.publish();for(let target of targets)this.collect(target)}catch(err){logDaemonError("context-usage",err)}}stop(){this.stopped=!0}collect(target){let entry=this.entries.get(target.key);if(!entry)entry={inFlight:!1,nextAllowedAt:0},this.entries.set(target.key,entry);if(entry.sessionId!==void 0&&entry.sessionId!==target.sessionId){let had=entry.value!==void 0;if(entry.value=void 0,entry.nextAllowedAt=0,entry.sessionId=target.sessionId,had)this.publish()}entry.sessionId=target.sessionId;let cadence=this.options.cadence??{timeoutMs:CONTEXT_USAGE_TIMEOUT_MS,slowRetryMs:CONTEXT_USAGE_SLOW_RETRY_MS,minIntervalMs:CONTEXT_USAGE_MIN_INTERVAL_MS},read=this.options.read??((t)=>this.runtime.readEngineContextUsage(t.vendor,t.sessionId)),startedAt=Date.now();if(entry.inFlight||startedAt<entry.nextAllowedAt)return;entry.inFlight=!0;let controller=new AbortController,timer=setTimeout(()=>controller.abort(),cadence.timeoutMs),held=entry;read(target,controller.signal).then((value)=>{if(controller.signal.aborted||this.stopped)return;if(this.entries.get(target.key)!==held)return;if(!value)return;if(held.value&&sameContextUsage(held.value,value))return;held.value=value,this.publish()}).catch(()=>{}).finally(()=>{clearTimeout(timer);let finishedAt=Date.now();held.nextAllowedAt=controller.signal.aborted?startedAt+cadence.slowRetryMs:finishedAt+Math.max(cadence.minIntervalMs,(finishedAt-startedAt)*5),held.inFlight=!1})}publish(){let context={};for(let[key,entry]of this.entries)if(entry.value)context[key]=entry.value;this.bus.publish("usage.context",{context})}}function startContextUsageCollector(registry,orch,bus,runtime,tickMs=DEFAULT_CONTEXT_USAGE_TICK_MS,hasSubscribers){if(tickMs<=0)return()=>{};let collector=new ContextUsageCollector(registry,orch,bus,runtime,{hasSubscribers});collector.tick();let timer=setInterval(()=>collector.tick(),tickMs);return timer.unref?.(),()=>{clearInterval(timer),collector.stop()}}var DEFAULT_DEFERRED_SWEEP_TICK_MS=3600000;function errorText(error){return error instanceof Error?error.message:String(error)}async function sweepExpiredDeferredPrompts(deps){let report={expired:[],cleanupPending:[]},listed=await deps.store.list();for(let expired of listed.expired){let claimed=await deps.store.claim(expired.id);if(claimed.kind!=="claimed"){report.cleanupPending.push(failure(expired,claimed.kind));continue}try{await deps.inbox.recordPromptExpired(expired.taskId,expired.tabId,expired.id,(deps.now??Date.now)()),await deps.store.completeClaim(claimed.claim),report.expired.push(expired.id)}catch(error){await deps.store.releaseClaim(claimed.claim).catch(()=>{}),report.cleanupPending.push(failure(expired,errorText(error)))}}return report}function failure(record,error){return{id:record.id,taskId:record.taskId,tabId:record.tabId,error}}function startDeferredPromptSweep(deps,tickMs=DEFAULT_DEFERRED_SWEEP_TICK_MS){return startTicker({name:"deferred-prompt-sweep",tickMs,immediate:!0,run:()=>sweepExpiredDeferredPrompts(deps)})}import{basename as basename2}from"path";var DEFAULT_KEYBINDINGS_DEBOUNCE_MS=200;function startKeybindingsWatcher(bus,options={}){let debounceMs=options.debounceMs??DEFAULT_KEYBINDINGS_DEBOUNCE_MS;if(debounceMs<=0)return()=>{};let filePath=options.path??defaultKeybindingsPath(),baseYaml=basename2(filePath),baseYml=baseYaml.replace(/\.yaml$/,".yml"),rev=0;return bus.publish("keybindings",{rev}),startFileWatchTrigger({filePath,matchBasenames:[baseYaml,baseYml],debounceMs,onTrigger:()=>{try{rev+=1,bus.publish("keybindings",{rev})}catch(err){logDaemonError("keybindings-watcher",err)}},onError:(err)=>logDaemonError("keybindings-watcher",err)})}import{spawn as spawn3}from"child_process";var PR_STATE_RANK={OPEN:2,MERGED:1,CLOSED:1};function pickPr(views){let best,bestRank=-1;for(let view of views){let rank=PR_STATE_RANK[(view.state??"").toUpperCase()]??0;if(rank>bestRank)best=view,bestRank=rank}return best}var DEFAULT_PR_STATUS_POLL_MS=30000,NO_PR_BACKOFF_MS=300000,SETTLED_BACKOFF_MS=600000,PR_FAILURE_BASE_MS=DEFAULT_PR_STATUS_POLL_MS,PR_FAILURE_CAP_MS=900000,NO_REMOTE_BACKOFF_MS=1800000,PR_POLL_JITTER_RATIO=0.2,PR_VIEW_TIMEOUT_MS=1e4,PR_POLL_CONCURRENCY=8;function decodeSpawnChunks(chunks){return Buffer.concat(chunks.map((c)=>typeof c==="string"?Buffer.from(c):c)).toString("utf8")}function spawnGh(args,cwd,signal){return new Promise((resolve)=>{let outChunks=[],errChunks=[],settled=!1,finish=(status,spawnError)=>{if(settled)return;settled=!0,resolve({status,stdout:decodeSpawnChunks(outChunks),stderr:decodeSpawnChunks(errChunks),spawnError})},child=spawn3("gh",args.slice(),{cwd,stdio:["ignore","pipe","pipe"],signal,killSignal:"SIGKILL"});child.stdout?.on("data",(chunk)=>{outChunks.push(chunk)}),child.stderr?.on("data",(chunk)=>{errChunks.push(chunk)}),child.on("error",()=>finish(null,!signal.aborted)),child.on("close",(code)=>finish(code,!1))})}function makeGhPrViewRunner(classify,viewFields){return async(worktreePath,branch)=>{let controller=new AbortController,timedOut=!1,timer=setTimeout(()=>{timedOut=!0,controller.abort()},PR_VIEW_TIMEOUT_MS);try{let res=await spawnGh(["pr","list","--head",branch,"--state","all","--json",viewFields],worktreePath,controller.signal);if(res.status===0&&!timedOut)try{let views=JSON.parse(res.stdout),picked=Array.isArray(views)?pickPr(views):void 0;return picked?{kind:"pr",view:picked}:{kind:"empty"}}catch{return classify({parseError:!0})}return classify({spawnError:res.spawnError,timedOut,exitCode:res.status,stderr:res.stderr})}finally{clearTimeout(timer)}}}function isPrPollable(task){if(task.kind==="main")return!1;if(!task.branch||!task.worktreePath)return!1;if(task.repo.startsWith("ssh://")||task.worktreePath.startsWith("ssh://"))return!1;return!0}async function setPrStaleMarker(orch,taskId,error){let prev=orch.getTask(taskId)?.prStatus;if(!prev||prev.lastError===error)return!1;let{lastError:_cleared,...rest}=prev;return await orch.setPRStatus(taskId,error===void 0?rest:{...rest,lastError:error}),!0}async function runPrStatusPass(orch,opts){let tickMs=opts.tickMs??DEFAULT_PR_STATUS_POLL_MS,rand=opts.rand,cfg={tickMs,settledMs:SETTLED_BACKOFF_MS,noPrMs:NO_PR_BACKOFF_MS,noRemoteMs:NO_REMOTE_BACKOFF_MS,failureBaseMs:PR_FAILURE_BASE_MS,failureCapMs:PR_FAILURE_CAP_MS,jitterRatio:PR_POLL_JITTER_RATIO},tasks=orch.listTasks(),live=new Set(tasks.map((task)=>task.id));for(let id of opts.schedule.keys())if(!live.has(id))opts.schedule.delete(id);let due=[];for(let task of tasks){if(!isPrPollable(task)){opts.schedule.delete(task.id);continue}let entry=opts.schedule.get(task.id);if(entry&&opts.now<entry.nextAllowedAt)continue;due.push({task,prevFailures:entry?.failures??0})}let pollOne=async(task,prevFailures)=>{try{let result=await opts.run(task.worktreePath,task.branch);if(result.kind==="error"){logDaemonInfo("pr-status-poller",`gh pr list failed (${result.error}) for task ${task.id} [${task.branch}] \u2014 keeping last PR status, backing off`);let marked=await setPrStaleMarker(orch,task.id,result.error);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"error",error:result.error},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}if(result.kind==="empty"){let marked=await setPrStaleMarker(orch,task.id,void 0);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"empty"},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}let next=opts.runtime.prStatus.mapView(result.view,opts.at),current=orch.getTask(task.id);if(!current){opts.schedule.delete(task.id);return}let wasStale=current.prStatus?.lastError!==void 0,changedId;if(wasStale||!opts.runtime.prStatus.sameStatus(current.prStatus,next??void 0))await orch.setPRStatus(task.id,next),changedId=task.id;let settled=next?.lifecycle==="merged"||next?.lifecycle==="closed";return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"pr",settled},prevFailures,opts.now,cfg,rand)),changedId}catch(err){logDaemonError("pr-status-poller",err);let marked=await setPrStaleMarker(orch,task.id,"network").catch(()=>!1);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"error",error:"network"},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}},slots=Array(due.length),cursor=0,worker=async()=>{for(let i=cursor++;i<due.length;i=cursor++){let item=due[i];if(item)slots[i]=await pollOne(item.task,item.prevFailures)}};return await Promise.all(Array.from({length:Math.min(PR_POLL_CONCURRENCY,due.length)},worker)),slots.filter((id)=>id!==void 0)}function startPrStatusPoller(orch,runtime,intervalMs=DEFAULT_PR_STATUS_POLL_MS,hasSubscribers,run=makeGhPrViewRunner(runtime.prStatus.classify,runtime.prStatus.viewFields),hasWorkingAgent){let schedule=new Map;return startTicker({name:"pr-status-poller",tickMs:intervalMs,...hasSubscribers?{gate:()=>hasSubscribers()||(hasWorkingAgent?.()??!1)}:{},run:()=>runPrStatusPass(orch,{runtime,run,now:Date.now(),at:new Date().toISOString(),schedule,tickMs:intervalMs})})}function quotaResumeContinuePrompt(language){return language==="zh"?"\u4ECE\u4E2D\u65AD\u7684\u5730\u65B9\u7EE7\u7EED\u8FD9\u4E2A\u4EFB\u52A1\u3002\u4E0A\u9762\u7684\u5BF9\u8BDD\u8BB0\u5F55\u91CC\u5DF2\u7ECF\u5B8C\u6210\u7684\u5DE5\u4F5C\u4E0D\u8981\u91CD\u505A\u3002":"Continue the task from where it stopped. The transcript above shows the work already completed \u2014 do not redo it."}var DEFAULT_QUOTA_RESUME_TICK_MS=60000;function dueQuotaResumes(tasks,nowMs){return tasks.filter((task)=>{if(!task.quotaResume||!task.worktreePath)return!1;if(task.deletion)return!1;let at=Date.parse(task.quotaResume.resumeAt);return Number.isFinite(at)&&at<=nowMs})}function exhaustedResetAtMs(usage,nowMs){let candidates=usage.windows.filter((w)=>w.percent>=100&&w.resetsAt!=null&&w.resetsAt>nowMs).map((w)=>w.resetsAt);return candidates.length>0?Math.min(...candidates):null}async function scheduleQuotaResume(orch,runtime,cache,taskId,now=Date.now,plugins){let task=orch.getTask(taskId);if(!task||!task.worktreePath||task.deletion)return;let usage=await cache.get(task.vendor??runtime.defaultTaskVendor,0),resetAtMs=usage?exhaustedResetAtMs(usage,now()):null;if(resetAtMs==null)return;let resumeAt=new Date(resetAtMs).toISOString();await orch.setQuotaResume(taskId,{resumeAt,requestedAt:new Date(now()).toISOString()}),logDaemonInfo("quota-resume",`armed task=${taskId} resumeAt=${resumeAt}`),plugins?.()?.handleUiReport({kind:"quota.exhausted",taskId,detail:{vendor:task.vendor??runtime.defaultTaskVendor,resumeAt}})}async function resumeDueTask(orch,runtime,task,plugins){await orch.setQuotaResume(task.id,null);let delivered=await runtime.deliverPromptToLiveEngine({id:task.id,vendor:task.vendor,command:task.command,worktreePath:task.worktreePath},quotaResumeContinuePrompt(task.observedLanguage));logDaemonInfo("quota-resume",`resume task=${task.id} delivered=${delivered}`),plugins?.()?.handleUiReport({kind:"quota.resumed",taskId:task.id,detail:{delivered}})}function startQuotaResumeRunner(orch,runtime,tickMs=DEFAULT_QUOTA_RESUME_TICK_MS,now=Date.now,plugins){return startTicker({name:"quota-resume",tickMs,run:async()=>{for(let task of dueQuotaResumes(orch.listTasks(),now()))await resumeDueTask(orch,runtime,task,plugins).catch((err)=>logDaemonError("quota-resume",err))}})}var FRESH_POLL_MS=900000,RETRY_BASE_MS=60000,MIN_FETCH_INTERVAL_MS=60000;class QuotaUsageCache{runtime;bus;now;entries=new Map;constructor(runtime,bus,now=Date.now){this.runtime=runtime;this.bus=bus;this.now=now}peek(vendor){return this.entries.get(vendor)?.usage??null}async get(vendor,maxAgeMs){let entry=this.entry(vendor),age=this.now()-(entry.usage?.capturedAt??0);if(entry.usage&&age<=maxAgeMs)return entry.usage;if(entry.inFlight)return entry.inFlight;if(this.now()-entry.attemptedAt<MIN_FETCH_INTERVAL_MS)return entry.usage;return this.fetch(vendor,entry)}async refreshIfDue(vendor){let entry=this.entry(vendor),retryAfter=entry.failures===0?0:Math.min(RETRY_BASE_MS*2**(entry.failures-1),FRESH_POLL_MS),dueAfter=entry.usage?FRESH_POLL_MS:retryAfter;if(this.now()-entry.attemptedAt<dueAfter)return;await this.fetch(vendor,entry)}entry(vendor){let existing=this.entries.get(vendor);if(existing)return existing;let fresh={usage:null,attemptedAt:0,failures:0,inFlight:null};return this.entries.set(vendor,fresh),fresh}fetch(vendor,entry){if(entry.inFlight)return entry.inFlight;return entry.attemptedAt=this.now(),entry.inFlight=this.runtime.quotaUsage(vendor).catch(()=>null).then((usage)=>{if(entry.inFlight=null,usage){entry.failures=0;let changed=JSON.stringify(usage.windows)!==JSON.stringify(entry.usage?.windows);if(entry.usage=usage,changed)this.publish()}else entry.failures+=1;return entry.usage}),entry.inFlight}publish(){let usage={};for(let[vendor,entry]of this.entries)if(entry.usage)usage[vendor]=entry.usage;this.bus.publish("usage.snapshot",{usage})}}function startQuotaUsagePoller(cache,listVendors,hasSubscribers,tickMs=MIN_FETCH_INTERVAL_MS){return startTicker({name:"quota-usage-poller",tickMs,gate:hasSubscribers,immediate:!0,run:()=>{for(let vendor of new Set(listVendors()))cache.refreshIfDue(vendor)}})}function isRemoteRepoKey(value){return value.startsWith("ssh://")}var DEFAULT_TRANSCRIPT_ACTIVITY_TICK_MS=1500,TRANSCRIPT_ACTIVITY_TIMEOUT_MS=4000,TRANSCRIPT_ACTIVITY_SLOW_RETRY_MS=60000,TRANSCRIPT_ACTIVITY_MIN_INTERVAL_MS=1500;function sameTranscriptActivityEntry(a,b){return a.mtimeMs===b.mtimeMs&&a.completionId===b.completionId&&a.completionAt===b.completionAt}async function runTranscriptActivity(worktreePath,vendor,detector,_signal){let{marker,mtimeMs}=await detector.latestActivity(worktreePath);return{mtimeMs:detector.supportsCompletionMarkers()?mtimeMs:0,completionId:marker?.id??null,completionAt:marker?.timestampMs??0}}function trackedWorktrees(tasks,defaultVendor="claude"){let map=new Map;for(let task of tasks){if(!task.worktreePath)continue;if(isRemoteRepoKey(task.repo)||isRemoteRepoKey(task.worktreePath))continue;if(map.has(task.worktreePath))continue;map.set(task.worktreePath,task.vendor??defaultVendor)}return map}class TranscriptActivityCollector{orch;bus;options;entries=new Map;stopped=!1;constructor(orch,bus,options={}){this.orch=orch;this.bus=bus;this.options=options}tick(){if(this.stopped)return;if(this.options.hasSubscribers&&!this.options.hasSubscribers())return;try{let tracked=trackedWorktrees(this.orch.listTasks(),this.options.defaultVendor),pruned=!1;for(let path of this.entries.keys()){if(tracked.has(path))continue;if(this.entries.get(path)?.value)pruned=!0;this.entries.delete(path)}if(pruned)this.publish();for(let[path,vendor]of tracked)this.maybeCollect(path,vendor)}catch(err){logDaemonError("transcript-activity",err)}}stop(){this.stopped=!0}maybeCollect(worktreePath,vendor){let entry=this.entries.get(worktreePath);if(!entry)entry={inFlight:!1,nextAllowedAt:0,vendor,detector:this.createDetector(vendor)},this.entries.set(worktreePath,entry);else if(entry.vendor!==vendor)entry.vendor=vendor,entry.detector=this.createDetector(vendor);let cadence=this.options.cadence??{timeoutMs:TRANSCRIPT_ACTIVITY_TIMEOUT_MS,slowRetryMs:TRANSCRIPT_ACTIVITY_SLOW_RETRY_MS,minIntervalMs:TRANSCRIPT_ACTIVITY_MIN_INTERVAL_MS},run=this.options.run??runTranscriptActivity,current=entry;maybeStartScheduledRun(current,cadence,(signal)=>run(worktreePath,current.vendor,current.detector,signal),(value)=>{if(this.stopped)return;if(this.entries.get(worktreePath)!==current)return;if(current.value&&sameTranscriptActivityEntry(current.value,value))return;current.value=value,this.publish()})}publish(){let activity={};for(let[path,entry]of this.entries)if(entry.value)activity[path]=entry.value;this.bus.publish("transcript.activity",{activity})}createDetector(vendor){return this.options.createDetector?.(vendor)??{latestActivity:async()=>({marker:null,mtimeMs:0}),latestActivityInFile:async()=>null,supportsCompletionMarkers:()=>!1}}}function startTranscriptActivityCollector(orch,runtime,bus,tickMs=DEFAULT_TRANSCRIPT_ACTIVITY_TICK_MS,hasSubscribers){let collector=new TranscriptActivityCollector(orch,bus,{hasSubscribers,createDetector:runtime.createEngineTurnDetector,defaultVendor:runtime.defaultTaskVendor,run:async(path,vendor,detector,signal)=>{let{marker,mtimeMs}=await detector.latestActivity(path);return{mtimeMs:detector.supportsCompletionMarkers()?mtimeMs:await runtime.latestTranscriptMtime(vendor,path),completionId:marker?.id??null,completionAt:marker?.timestampMs??0}}});return startTicker({name:"transcript-activity-collector",tickMs,immediate:!0,run:()=>collector.tick(),onStop:()=>collector.stop()})}import{readFileSync}from"fs";import{basename as basename3}from"path";var DEFAULT_UI_PREFS_DEBOUNCE_MS=200,FOCUS_ACCENT_SLOT_NAMES=["primary","success","info"];function readUiPrefsFromStateFile(statePath){let parsed={};try{let raw=JSON.parse(readFileSync(statePath,"utf8"));if(raw&&typeof raw==="object"&&!Array.isArray(raw))parsed=raw}catch{}let theme=typeof parsed.activeTheme==="string"&&parsed.activeTheme.length>0?parsed.activeTheme:"claude",transparentBackground=parsed.transparentBackground!==!1,focusAccent=typeof parsed.focusAccent==="string"&&FOCUS_ACCENT_SLOT_NAMES.includes(parsed.focusAccent)?parsed.focusAccent:null,sortMode=parsed.activeSortMode==="recent"?"recent":"default",keysCollapsed=parsed["tasksPane.keysCollapsed"]===!0,projectFilter=typeof parsed["tasksPane.projectFilter"]==="string"&&parsed["tasksPane.projectFilter"].length>0?parsed["tasksPane.projectFilter"]:null,locale=typeof parsed.locale==="string"&&parsed.locale.length>0?parsed.locale:"en";return{theme,transparentBackground,focusAccent,locale,sortMode,keysCollapsed,projectFilter}}function samePrefs(a,b){return a.theme===b.theme&&a.transparentBackground===b.transparentBackground&&a.focusAccent===b.focusAccent&&a.locale===b.locale&&a.sortMode===b.sortMode&&a.keysCollapsed===b.keysCollapsed&&a.projectFilter===b.projectFilter}function startUiPrefsWatcher(bus,options={}){let debounceMs=options.debounceMs??DEFAULT_UI_PREFS_DEBOUNCE_MS;if(debounceMs<=0)return()=>{};let statePath=options.statePath??defaultUiPrefsStatePath(),stateFile=basename3(statePath),last=null,publishIfChanged=()=>{try{let next=readUiPrefsFromStateFile(statePath);if(last&&samePrefs(last,next))return;last=next,bus.publish("ui-prefs",next)}catch(err){logDaemonError("ui-prefs-watcher",err)}},stop=startFileWatchTrigger({filePath:statePath,matchBasenames:[stateFile],debounceMs,onTrigger:publishIfChanged,onError:(err)=>logDaemonError("ui-prefs-watcher",err)});return publishIfChanged(),stop}import{spawn as spawn4}from"child_process";var EMPTY_PATHS=new Set;function isRemoteRepoKey2(value){return value.startsWith("ssh://")}function sameWorktreeChanges(a,b){return a.added===b.added&&a.deleted===b.deleted&&a.behind===b.behind&&a.ahead===b.ahead}var DEFAULT_WORKTREE_CHANGES_TICK_MS=2000,WORKTREE_CHANGES_TIMEOUT_MS=4000,WORKTREE_CHANGES_SLOW_RETRY_MS=60000,WORKTREE_CHANGES_MIN_INTERVAL_MS=1500,WORKTREE_CHANGES_QUIET_INTERVAL_MS=15000;function runGit(worktreePath,args,signal){return new Promise((resolve)=>{let stdout="",settled=!1,finish=(status)=>{if(settled)return;settled=!0,resolve(status===0?stdout:null)},child=spawn4("git",args.slice(),{cwd:worktreePath,stdio:["ignore","pipe","ignore"],env:{...process.env,GIT_OPTIONAL_LOCKS:"0"},signal,killSignal:"SIGKILL"});child.stdout?.on("data",(chunk)=>{stdout+=String(chunk)}),child.on("error",()=>finish(null)),child.on("close",finish)})}async function countDrift(worktreePath,baseRef,signal){return parseAheadBehind(await runGit(worktreePath,["rev-list","--left-right","--count",`${baseRef}...HEAD`],signal))}function parseAheadBehind(stdout){if(stdout===null)return null;let parts=stdout.trim().split(/\s+/);if(parts.length!==2)return null;let behind=Number.parseInt(parts[0]??"",10),ahead=Number.parseInt(parts[1]??"",10);if(!Number.isInteger(behind)||behind<0||!Number.isInteger(ahead)||ahead<0)return null;return{behind,ahead}}function countPorcelain(stdout){let added=0,deleted=0;for(let line of stdout.split(`
|
|
6
|
+
`,{encoding:"utf8",mode}),await rename(tmp,path)}var locks=new Map;function serialized(key,fn){let run=(locks.get(key)??Promise.resolve()).then(fn),settled=run.then(()=>{return},()=>{return});return locks.set(key,settled),settled.then(()=>{if(locks.get(key)===settled)locks.delete(key)}),run}var DEFERRED_PROMPT_TTL_MS=86400000;class DeferredPromptPendingError extends Error{existing;constructor(existing){super(`task ${existing.taskId} tab ${existing.tabId} already has a deferred prompt (${existing.id})`);this.existing=existing;this.name="DeferredPromptPendingError"}}function defaultDeferredPromptsPath(homeDir=readRoveHomeDirEnv()??homedir()){return join2(homeDir,ROVE_STATE_DIR_BASENAME,"deferred-prompts.json")}function normalizeRecord(value){if(!value||typeof value!=="object"||Array.isArray(value))return null;let raw=value;if(typeof raw.id!=="string"||raw.id.length===0)return null;if(typeof raw.taskId!=="string"||raw.taskId.length===0)return null;if(typeof raw.tabId!=="string"||raw.tabId.length===0)return null;if(typeof raw.prompt!=="string"||raw.prompt.length===0)return null;if(raw.layer!=="recent-human-write"&&raw.layer!=="composer-not-empty")return null;if(typeof raw.at!=="number"||!Number.isFinite(raw.at))return null;return{id:raw.id,taskId:raw.taskId,tabId:raw.tabId,prompt:raw.prompt,layer:raw.layer,...typeof raw.senderLabel==="string"?{senderLabel:raw.senderLabel}:{},...typeof raw.senderTaskId==="string"?{senderTaskId:raw.senderTaskId}:{},at:raw.at,...typeof raw.deliveredAt==="number"&&Number.isFinite(raw.deliveredAt)?{deliveredAt:raw.deliveredAt}:{},...typeof raw.deliveryStartedAt==="number"&&Number.isFinite(raw.deliveryStartedAt)?{deliveryStartedAt:raw.deliveryStartedAt}:{}}}async function readStore(path){try{let parsed=JSON.parse(await readFile(path,"utf8"));if(!Array.isArray(parsed.records))return{version:1,records:[]};return{version:1,records:parsed.records.map(normalizeRecord).filter((r)=>r!==null)}}catch(err){if(err.code==="ENOENT")return{version:1,records:[]};throw err}}async function writeStore(path,records){let body={version:1,records:[...records]};await writeJsonAtomic(path,body)}var SUBSYSTEM="deferred-prompts";class DeferredPromptsStore{path;now;claims=new Map;constructor(path=defaultDeferredPromptsPath(),now=()=>Date.now()){this.path=path;this.now=now}enqueue(operation){return serialized(this.path,operation)}async file(record){return await this.enqueue(async()=>{let now=this.now(),store=await readStore(this.path),kept=[],occupied=null;for(let existing of store.records){if(now-existing.at>DEFERRED_PROMPT_TTL_MS){if(this.claims.has(existing.id)){if(existing.taskId===record.taskId&&existing.tabId===record.tabId)throw Error(`deferred prompt ${existing.id} expiry cleanup is in flight`);kept.push(existing);continue}if(existing.taskId===record.taskId&&existing.tabId===record.tabId){logDaemonInfo(SUBSYSTEM,`expired deferred prompt ${existing.id} for ${existing.taskId}::${existing.tabId} replaced by a new deferral`);continue}kept.push(existing);continue}if(existing.taskId===record.taskId&&existing.tabId===record.tabId)occupied=existing;kept.push(existing)}if(occupied)throw new DeferredPromptPendingError(occupied);let next={...record,id:randomUUID2()};return kept.push(next),await writeStore(this.path,kept),next})}async get(id){return await this.enqueue(async()=>(await readStore(this.path)).records.find((r)=>r.id===id)??null)}async claim(id){return await this.enqueue(async()=>{if(this.claims.has(id))return{kind:"in-flight"};let record=(await readStore(this.path)).records.find((candidate)=>candidate.id===id);if(!record)return{kind:"missing"};let claimId=randomUUID2(),finish=()=>{},done=new Promise((resolve)=>{finish=resolve});return this.claims.set(id,{claimId,done,finish}),{kind:"claimed",claim:{claimId,record}}})}async beginDelivery(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);let started=current.deliveryStartedAt!==void 0?current:{...current,deliveryStartedAt:this.now()};if(current.deliveryStartedAt===void 0)await writeStore(this.path,store.records.map((record)=>record.id===current.id?started:record));return started})}async resetDelivery(claim){await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);if(current.deliveryStartedAt===void 0)return;let{deliveryStartedAt:_deliveryStartedAt,...reset}=current;await writeStore(this.path,store.records.map((record)=>record.id===current.id?reset:record))})}async markDelivered(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),current=store.records.find((record)=>record.id===claim.record.id);if(!current)throw Error(`deferred prompt ${claim.record.id} disappeared while claimed`);let delivered=current.deliveredAt?current:{...current,deliveredAt:this.now()};if(!current.deliveredAt)await writeStore(this.path,store.records.map((record)=>record.id===current.id?delivered:record));return delivered})}async completeClaim(claim){return await this.enqueue(async()=>{this.requireClaim(claim);let store=await readStore(this.path),kept=store.records.filter((record)=>record.id!==claim.record.id);if(kept.length!==store.records.length)await writeStore(this.path,kept);return this.finishClaim(claim.record.id),kept.length!==store.records.length})}async releaseClaim(claim){await this.enqueue(async()=>{this.requireClaim(claim),this.finishClaim(claim.record.id)})}requireClaim(claim){if(this.claims.get(claim.record.id)?.claimId!==claim.claimId)throw Error(`deferred prompt ${claim.record.id} claim is no longer owned`)}finishClaim(id){let active=this.claims.get(id);this.claims.delete(id),active?.finish()}async waitForClaims(taskId,tabId){for(;;){let waiting=await this.enqueue(async()=>{return(await readStore(this.path)).records.filter((record)=>record.taskId===taskId&&(tabId===void 0||record.tabId===tabId)).flatMap((record)=>{let active=this.claims.get(record.id);return active?[active.done]:[]})});if(waiting.length===0)return;await Promise.all(waiting)}}async list(){return await this.enqueue(async()=>{let now=this.now(),store=await readStore(this.path),expired=[];return{records:store.records.filter((record)=>{if(now-record.at<=DEFERRED_PROMPT_TTL_MS)return!0;return expired.push(record),logDaemonInfo(SUBSYSTEM,`expired deferred prompt ${record.id} for ${record.taskId}::${record.tabId} (age ${Math.round((now-record.at)/1000)}s) \u2014 cleanup requested`),!1}),expired}})}async resolve(id){return await this.enqueue(async()=>{let store=await readStore(this.path),kept=store.records.filter((r)=>r.id!==id);if(kept.length===store.records.length)return!1;return await writeStore(this.path,kept),!0})}async discardTab(taskId,tabId,reason){return await this.waitForClaims(taskId,tabId),await this.enqueue(async()=>{let store=await readStore(this.path),dropped=store.records.filter((record)=>record.taskId===taskId&&record.tabId===tabId);if(dropped.length===0)return[];for(let record of dropped)logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${record.id} for ${taskId}::${tabId} \u2014 ${reason}`);return await writeStore(this.path,store.records.filter((record)=>record.taskId!==taskId||record.tabId!==tabId)),dropped})}async discard(id,reason){for(;;){let waiting=await this.enqueue(async()=>this.claims.get(id)?.done);if(!waiting)break;await waiting}return await this.enqueue(async()=>{let store=await readStore(this.path),dropped=store.records.find((record)=>record.id===id);if(!dropped)return null;return logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${dropped.id} for ${dropped.taskId}::${dropped.tabId} \u2014 ${reason}`),await writeStore(this.path,store.records.filter((record)=>record.id!==id)),dropped})}async listForTask(taskId){return await this.enqueue(async()=>(await readStore(this.path)).records.filter((r)=>r.taskId===taskId))}async deleteTask(taskId){await this.waitForClaims(taskId),await this.enqueue(async()=>{let store=await readStore(this.path),kept=store.records.filter((r)=>r.taskId!==taskId);if(kept.length===store.records.length)return;for(let dropped of store.records)if(dropped.taskId===taskId)logDaemonInfo(SUBSYSTEM,`dropped deferred prompt ${dropped.id} \u2014 task ${taskId} deleted`);await writeStore(this.path,kept)})}}function resolveStandingTask(orch,automation){let id=automation.sessionTaskId;if(!id)return{task:null,hadStaleLink:!1};let task=orch.getTask(id);if(!task||task.deletion||!task.worktreePath)return{task:null,hadStaleLink:!0};return{task}}async function createRunTask(deps,automation){return await deps.orch.createTask({repo:automation.repo,title:automation.name,...automation.vendor?{vendor:automation.vendor}:{},...automation.baseRef?{baseRef:automation.baseRef}:{},...automation.persistentSession?{routine:{automationId:automation.id}}:{}})}async function spawnWithPrompt(deps,automation,taskId,status){let outcome=await deps.runtime.startTaskSessionWithPrompt(deps.link(),taskId,automation.prompt);if(!outcome.started)return{status:"dispatch_failed",taskId,error:outcome.error??"engine session did not start"};return{status,taskId}}async function deferBlockedPrompt(deps,automation,taskId,tabId,layer){if(!deps.deferred||!deps.inbox)return{status:"dispatch_failed",taskId,error:`composer busy (${layer}) and no deferred-prompt store`};let record;try{record=await deps.deferred.file({taskId,tabId,prompt:automation.prompt,layer,senderLabel:`routine: ${automation.name}`,at:(deps.now??Date.now)()})}catch(error){if(!(error instanceof DeferredPromptPendingError))throw error;return await deps.inbox.recordPromptDeferred(error.existing.taskId,error.existing.tabId,error.existing.id,error.existing.layer,error.existing.at+DEFERRED_PROMPT_TTL_MS),{status:"dispatch_failed",taskId,error:`tab ${tabId} already has a deferred prompt (${error.existing.id})`}}return await deps.inbox.recordPromptDeferred(taskId,tabId,record.id,layer,record.at+DEFERRED_PROMPT_TTL_MS),logDaemonInfo("automation",`deferred ${automation.name} task=${taskId} tab=${tabId} layer=${layer}`),{status:"deferred",taskId}}async function dispatchAutomation(deps,automation){if(!automation.persistentSession){let task2=await createRunTask(deps,automation);return await spawnWithPrompt(deps,automation,task2.id,"dispatched")}let standing=resolveStandingTask(deps.orch,automation);if(standing.task===null){let task2=await createRunTask(deps,automation);return{...await spawnWithPrompt(deps,automation,task2.id,"dispatched"),sessionTaskIdToSet:task2.id,...standing.hadStaleLink?{sessionTaskIdToClear:!0}:{}}}let task=standing.task,result=await deps.runtime.deliverPromptToLiveEngineDetailed({id:task.id,vendor:task.vendor,command:task.command,worktreePath:task.worktreePath},automation.prompt);if(result.outcome==="delivered")return logDaemonInfo("automation",`delivered ${automation.name} task=${task.id} tab=${result.tabId}`),{status:"dispatched",taskId:task.id};if(result.outcome==="busy")return await deferBlockedPrompt(deps,automation,task.id,result.tabId,result.layer);return await spawnWithPrompt(deps,automation,task.id,"revived")}import{spawn as spawn2}from"child_process";import{existsSync}from"fs";var MAX_OUTPUT_CHARS=4000,MAX_TIMEOUT_SECONDS=600;function tail(chunks){let text=Buffer.concat(chunks.map((c)=>typeof c==="string"?Buffer.from(c):c)).toString("utf8");if(text.length<=MAX_OUTPUT_CHARS)return text;let points=Array.from(text);return points.length<=MAX_OUTPUT_CHARS?text:points.slice(-MAX_OUTPUT_CHARS).join("")}function runAutomationPrecheck(precheck,cwd,shell=resolveLoginShell({fallback:"/bin/sh"})){let startedAt=Date.now(),timeoutMs=Math.min(Math.max(precheck.timeoutSeconds,1),MAX_TIMEOUT_SECONDS)*1000;if(!existsSync(cwd))return Promise.resolve({exitCode:null,timedOut:!1,stdout:"",stderr:`working directory does not exist: ${cwd}`,durationMs:0});return new Promise((resolve)=>{let out=[],err=[],settled=!1,timedOut=!1,controller=new AbortController,finish=(exitCode,timedOut2)=>{if(settled)return;settled=!0,clearTimeout(timer),resolve({exitCode,timedOut:timedOut2,stdout:tail(out),stderr:tail(err),durationMs:Date.now()-startedAt})},timer=setTimeout(()=>{timedOut=!0,controller.abort(),finish(null,!0)},timeoutMs);timer.unref?.();let child;try{child=spawn2(shell,["-ilc",precheck.command],{cwd,stdio:["ignore","pipe","pipe"],signal:controller.signal,killSignal:"SIGKILL"})}catch(spawnError){err.push(spawnError instanceof Error?spawnError.message:String(spawnError)),finish(null,!1);return}child.stdout?.on("data",(chunk)=>out.push(chunk)),child.stderr?.on("data",(chunk)=>err.push(chunk)),child.on("error",(spawnError)=>{if(timedOut){finish(null,!0);return}err.push(spawnError.message),finish(null,!1)}),child.on("close",(code)=>finish(code,timedOut))})}function precheckPassed(result){return result.exitCode===0&&!result.timedOut}function formatPrecheckSkip(result){if(result.timedOut)return`precheck timed out after ${result.durationMs}ms`;if(result.exitCode===null)return`precheck could not run: ${result.stderr.trim()||"unknown error"}`;return`precheck exited ${result.exitCode}`}var DEFAULT_AUTOMATION_TICK_MS=60000;function dueAutomations(automations,nowMs){return automations.filter((a)=>{if(!a.enabled)return!1;let at=Date.parse(a.nextRunAt);return Number.isFinite(at)&&at<=nowMs})}function resolveDueOccurrence(automation,nowMs,tickMs=DEFAULT_AUTOMATION_TICK_MS){let notBefore=Date.parse(automation.createdAt),scheduledFor=latestCronAtOrBefore(automation.schedule,nowMs,Number.isFinite(notBefore)?notBefore:0);if(scheduledFor===null)return null;let graceMs=automation.missedRunGraceMinutes*60000+Math.max(tickMs,0);return{scheduledFor,missed:nowMs-scheduledFor>graceMs}}function runEventFor(status){if(status==="dispatched"||status==="revived"||status==="deferred")return"automation.dispatched";if(status==="dispatch_failed")return"automation.failed";return"automation.skipped"}function resolveLink(link){return typeof link==="function"?link():link}function emitRunEvent(deps,automation,status,args,extra){deps.plugins?.()?.handleUiReport({kind:runEventFor(status),...extra.taskId?{taskId:extra.taskId}:{},detail:{automationId:automation.id,name:automation.name,repo:automation.repo,status,trigger:args.trigger,scheduledFor:new Date(args.scheduledFor).toISOString(),...extra.error?{error:extra.error}:{}}})}async function raiseOrClearInboxEpisode(deps,automation,status,extra){let inbox=deps.inbox;if(!inbox)return;let now=deps.now??Date.now;await(automationRunNeedsAttention(status)?inbox.recordRoutineFailure({automationId:automation.id,name:automation.name,status,...extra.error?{error:extra.error}:{}},extra.taskId??null,now()):inbox.deleteRoutineEpisode?.(automation.id))?.catch((err)=>logDaemonError("automation-inbox",err))}async function recordSkip(deps,automation,status,scheduledFor,error){let now=deps.now??Date.now;await deps.store.recordRun({automationId:automation.id,scheduledFor:new Date(scheduledFor).toISOString(),status,trigger:"scheduled",at:new Date(now()).toISOString(),error}),emitRunEvent(deps,automation,status,{scheduledFor,trigger:"scheduled"},{error})}function droppedOccurrences(automation,scheduledFor){let armedAt=Date.parse(automation.nextRunAt);if(!Number.isFinite(armedAt)||armedAt>=scheduledFor)return null;let count=countCronBetween(automation.schedule,armedAt,scheduledFor);return count>0?{count,firstMs:armedAt}:null}async function runAutomationOnce(deps,automation,args){let now=deps.now??Date.now,record=async(status,extra={})=>{return await deps.store.recordRun({automationId:automation.id,scheduledFor:new Date(args.scheduledFor).toISOString(),status,trigger:args.trigger,at:new Date(now()).toISOString(),...extra}),emitRunEvent(deps,automation,status,args,extra),await raiseOrClearInboxEpisode(deps,automation,status,extra),status};if(args.trigger==="scheduled"&&automation.precheck){let result=await runAutomationPrecheck(automation.precheck,automation.repo);if(!precheckPassed(result))return logDaemonInfo("automation",`skip ${automation.name}: ${formatPrecheckSkip(result)}`),await record("skipped_precheck",{precheckResult:result,error:formatPrecheckSkip(result)})}let outcome;try{outcome=await dispatchAutomation({orch:deps.orch,runtime:deps.runtime,link:()=>resolveLink(deps.link),...deps.deferred?{deferred:deps.deferred}:{},...deps.inbox?{inbox:deps.inbox}:{},...deps.now?{now:deps.now}:{}},automation)}catch(err){let error=err instanceof Error?err.message:String(err);return logDaemonError("automation-dispatch",err),await record("skipped_unavailable",{error})}if(outcome.sessionTaskIdToSet)await deps.store.update(automation.id,{sessionTaskId:outcome.sessionTaskIdToSet}).catch((err)=>logDaemonError("automation-session-link",err));else if(outcome.sessionTaskIdToClear)await deps.store.update(automation.id,{sessionTaskId:null}).catch((err)=>logDaemonError("automation-session-unlink",err));if(outcome.status==="dispatched"||outcome.status==="revived")logDaemonInfo("automation",`${outcome.status} ${automation.name} task=${outcome.taskId}`);return await record(outcome.status,{...outcome.taskId?{taskId:outcome.taskId}:{},...outcome.error?{error:outcome.error}:{}})}async function sweepAutomations(deps,tickMs=DEFAULT_AUTOMATION_TICK_MS){let now=deps.now??Date.now;for(let automation of dueAutomations(deps.store.list(),now())){let nowMs=now(),occurrence=resolveDueOccurrence(automation,nowMs,tickMs);if(!occurrence){await deps.store.advanceNextRun(automation.id,nowMs);continue}let dropped=droppedOccurrences(automation,occurrence.scheduledFor);if(await deps.store.advanceNextRun(automation.id,occurrence.scheduledFor),dropped){let first=new Date(dropped.firstMs).toISOString();await recordSkip(deps,automation,"skipped_missed",dropped.firstMs,`${dropped.count} earlier occurrence${dropped.count===1?"":"s"} never ran (from ${first})`).catch((err)=>logDaemonError("automation-dropped",err))}if(occurrence.missed){await recordSkip(deps,automation,"skipped_missed",occurrence.scheduledFor,`missed by more than the ${automation.missedRunGraceMinutes}m grace window`);continue}await runAutomationOnce(deps,automation,{scheduledFor:occurrence.scheduledFor,trigger:"scheduled"}).catch((err)=>logDaemonError("automation-run",err))}}function startAutomationRunner(deps,tickMs=DEFAULT_AUTOMATION_TICK_MS){return startTicker({name:"automation-sweep",tickMs,run:()=>sweepAutomations(deps,tickMs)})}var DEFAULT_CONTEXT_USAGE_TICK_MS=1e4,CONTEXT_USAGE_TIMEOUT_MS=4000,CONTEXT_USAGE_SLOW_RETRY_MS=60000,CONTEXT_USAGE_MIN_INTERVAL_MS=5000;function sameContextUsage(a,b){return a.contextTokens===b.contextTokens&&a.contextWindowTokens===b.contextWindowTokens&&a.approximate===b.approximate&&a.inputTokens===b.inputTokens&&a.outputTokens===b.outputTokens&&a.cacheReadTokens===b.cacheReadTokens&&a.cacheCreationTokens===b.cacheCreationTokens}function contextUsageTargets(states,getVendor){let seen=new Set,targets=[];for(let state of states){if(!state.tabId||!state.sessionId)continue;let vendor=getVendor(state.taskId);if(!vendor)continue;let key=`${state.taskId}::${state.tabId}`;if(seen.has(key))continue;seen.add(key),targets.push({key,vendor,sessionId:state.sessionId})}return targets}class ContextUsageCollector{registry;orch;bus;runtime;options;entries=new Map;stopped=!1;constructor(registry,orch,bus,runtime,options={}){this.registry=registry;this.orch=orch;this.bus=bus;this.runtime=runtime;this.options=options}tick(){if(this.stopped)return;if(this.options.hasSubscribers&&!this.options.hasSubscribers())return;try{let targets=contextUsageTargets(this.registry.currentNonIdle(),(id)=>this.orch.getTask(id)?.vendor),live=new Set(targets.map((t)=>t.key)),pruned=!1;for(let key of[...this.entries.keys()]){if(live.has(key))continue;if(this.entries.get(key)?.value)pruned=!0;this.entries.delete(key)}if(pruned)this.publish();for(let target of targets)this.collect(target)}catch(err){logDaemonError("context-usage",err)}}stop(){this.stopped=!0}collect(target){let entry=this.entries.get(target.key);if(!entry)entry={inFlight:!1,nextAllowedAt:0},this.entries.set(target.key,entry);if(entry.sessionId!==void 0&&entry.sessionId!==target.sessionId){let had=entry.value!==void 0;if(entry.value=void 0,entry.nextAllowedAt=0,entry.sessionId=target.sessionId,had)this.publish()}entry.sessionId=target.sessionId;let cadence=this.options.cadence??{timeoutMs:CONTEXT_USAGE_TIMEOUT_MS,slowRetryMs:CONTEXT_USAGE_SLOW_RETRY_MS,minIntervalMs:CONTEXT_USAGE_MIN_INTERVAL_MS},read=this.options.read??((t)=>this.runtime.readEngineContextUsage(t.vendor,t.sessionId)),startedAt=Date.now();if(entry.inFlight||startedAt<entry.nextAllowedAt)return;entry.inFlight=!0;let controller=new AbortController,timer=setTimeout(()=>controller.abort(),cadence.timeoutMs),held=entry;read(target,controller.signal).then((value)=>{if(controller.signal.aborted||this.stopped)return;if(this.entries.get(target.key)!==held)return;if(!value)return;if(held.value&&sameContextUsage(held.value,value))return;held.value=value,this.publish()}).catch(()=>{}).finally(()=>{clearTimeout(timer);let finishedAt=Date.now();held.nextAllowedAt=controller.signal.aborted?startedAt+cadence.slowRetryMs:finishedAt+Math.max(cadence.minIntervalMs,(finishedAt-startedAt)*5),held.inFlight=!1})}publish(){let context={};for(let[key,entry]of this.entries)if(entry.value)context[key]=entry.value;this.bus.publish("usage.context",{context})}}function startContextUsageCollector(registry,orch,bus,runtime,tickMs=DEFAULT_CONTEXT_USAGE_TICK_MS,hasSubscribers){if(tickMs<=0)return()=>{};let collector=new ContextUsageCollector(registry,orch,bus,runtime,{hasSubscribers});collector.tick();let timer=setInterval(()=>collector.tick(),tickMs);return timer.unref?.(),()=>{clearInterval(timer),collector.stop()}}var DEFAULT_DEFERRED_SWEEP_TICK_MS=3600000;function errorText(error){return error instanceof Error?error.message:String(error)}async function sweepExpiredDeferredPrompts(deps){let report={expired:[],cleanupPending:[]},listed=await deps.store.list();for(let expired of listed.expired){let claimed=await deps.store.claim(expired.id);if(claimed.kind!=="claimed"){report.cleanupPending.push(failure(expired,claimed.kind));continue}try{await deps.inbox.recordPromptExpired(expired.taskId,expired.tabId,expired.id,(deps.now??Date.now)()),await deps.store.completeClaim(claimed.claim),report.expired.push(expired.id)}catch(error){await deps.store.releaseClaim(claimed.claim).catch(()=>{}),report.cleanupPending.push(failure(expired,errorText(error)))}}return report}function failure(record,error){return{id:record.id,taskId:record.taskId,tabId:record.tabId,error}}function startDeferredPromptSweep(deps,tickMs=DEFAULT_DEFERRED_SWEEP_TICK_MS){return startTicker({name:"deferred-prompt-sweep",tickMs,immediate:!0,run:()=>sweepExpiredDeferredPrompts(deps)})}import{basename as basename2}from"path";var DEFAULT_KEYBINDINGS_DEBOUNCE_MS=200;function startKeybindingsWatcher(bus,options={}){let debounceMs=options.debounceMs??DEFAULT_KEYBINDINGS_DEBOUNCE_MS;if(debounceMs<=0)return()=>{};let filePath=options.path??defaultKeybindingsPath(),baseYaml=basename2(filePath),baseYml=baseYaml.replace(/\.yaml$/,".yml"),rev=0;return bus.publish("keybindings",{rev}),startFileWatchTrigger({filePath,matchBasenames:[baseYaml,baseYml],debounceMs,onTrigger:()=>{try{rev+=1,bus.publish("keybindings",{rev})}catch(err){logDaemonError("keybindings-watcher",err)}},onError:(err)=>logDaemonError("keybindings-watcher",err)})}import{spawn as spawn3}from"child_process";var PR_STATE_RANK={OPEN:2,MERGED:1,CLOSED:1};function pickPr(views){let best,bestRank=-1;for(let view of views){let rank=PR_STATE_RANK[(view.state??"").toUpperCase()]??0;if(rank>bestRank)best=view,bestRank=rank}return best}var DEFAULT_PR_STATUS_POLL_MS=30000,NO_PR_BACKOFF_MS=300000,SETTLED_BACKOFF_MS=600000,PR_FAILURE_BASE_MS=DEFAULT_PR_STATUS_POLL_MS,PR_FAILURE_CAP_MS=900000,NO_REMOTE_BACKOFF_MS=1800000,PR_POLL_JITTER_RATIO=0.2,PR_VIEW_TIMEOUT_MS=1e4,PR_POLL_CONCURRENCY=8;function decodeSpawnChunks(chunks){return Buffer.concat(chunks.map((c)=>typeof c==="string"?Buffer.from(c):c)).toString("utf8")}function spawnGh(args,cwd,signal){return new Promise((resolve)=>{let outChunks=[],errChunks=[],settled=!1,finish=(status,spawnError)=>{if(settled)return;settled=!0,resolve({status,stdout:decodeSpawnChunks(outChunks),stderr:decodeSpawnChunks(errChunks),spawnError})},child=spawn3("gh",args.slice(),{cwd,stdio:["ignore","pipe","pipe"],signal,killSignal:"SIGKILL"});child.stdout?.on("data",(chunk)=>{outChunks.push(chunk)}),child.stderr?.on("data",(chunk)=>{errChunks.push(chunk)}),child.on("error",()=>finish(null,!signal.aborted)),child.on("close",(code)=>finish(code,!1))})}function makeGhPrViewRunner(classify,viewFields){return async(worktreePath,branch)=>{let controller=new AbortController,timedOut=!1,timer=setTimeout(()=>{timedOut=!0,controller.abort()},PR_VIEW_TIMEOUT_MS);try{let res=await spawnGh(["pr","list","--head",branch,"--state","all","--json",viewFields],worktreePath,controller.signal);if(res.status===0&&!timedOut)try{let views=JSON.parse(res.stdout),picked=Array.isArray(views)?pickPr(views):void 0;return picked?{kind:"pr",view:picked}:{kind:"empty"}}catch{return classify({parseError:!0})}return classify({spawnError:res.spawnError,timedOut,exitCode:res.status,stderr:res.stderr})}finally{clearTimeout(timer)}}}function isPrPollable(task){if(task.kind==="main")return!1;if(!task.branch||!task.worktreePath)return!1;if(task.repo.startsWith("ssh://")||task.worktreePath.startsWith("ssh://"))return!1;return!0}async function setPrStaleMarker(orch,taskId,error){let prev=orch.getTask(taskId)?.prStatus;if(!prev||prev.lastError===error)return!1;let{lastError:_cleared,...rest}=prev;return await orch.setPRStatus(taskId,error===void 0?rest:{...rest,lastError:error}),!0}async function runPrStatusPass(orch,opts){let tickMs=opts.tickMs??DEFAULT_PR_STATUS_POLL_MS,rand=opts.rand,cfg={tickMs,settledMs:SETTLED_BACKOFF_MS,noPrMs:NO_PR_BACKOFF_MS,noRemoteMs:NO_REMOTE_BACKOFF_MS,failureBaseMs:PR_FAILURE_BASE_MS,failureCapMs:PR_FAILURE_CAP_MS,jitterRatio:PR_POLL_JITTER_RATIO},tasks=orch.listTasks(),live=new Set(tasks.map((task)=>task.id));for(let id of opts.schedule.keys())if(!live.has(id))opts.schedule.delete(id);let due=[];for(let task of tasks){if(!isPrPollable(task)){opts.schedule.delete(task.id);continue}let entry=opts.schedule.get(task.id);if(entry&&opts.now<entry.nextAllowedAt)continue;due.push({task,prevFailures:entry?.failures??0})}let pollOne=async(task,prevFailures)=>{try{let result=await opts.run(task.worktreePath,task.branch);if(result.kind==="error"){logDaemonInfo("pr-status-poller",`gh pr list failed (${result.error}) for task ${task.id} [${task.branch}] \u2014 keeping last PR status, backing off`);let marked=await setPrStaleMarker(orch,task.id,result.error);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"error",error:result.error},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}if(result.kind==="empty"){let marked=await setPrStaleMarker(orch,task.id,void 0);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"empty"},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}let next=opts.runtime.prStatus.mapView(result.view,opts.at),current=orch.getTask(task.id);if(!current){opts.schedule.delete(task.id);return}let wasStale=current.prStatus?.lastError!==void 0,changedId;if(wasStale||!opts.runtime.prStatus.sameStatus(current.prStatus,next??void 0))await orch.setPRStatus(task.id,next),changedId=task.id;let settled=next?.lifecycle==="merged"||next?.lifecycle==="closed";return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"pr",settled},prevFailures,opts.now,cfg,rand)),changedId}catch(err){logDaemonError("pr-status-poller",err);let marked=await setPrStaleMarker(orch,task.id,"network").catch(()=>!1);return opts.schedule.set(task.id,opts.runtime.prStatus.nextPoll({kind:"error",error:"network"},prevFailures,opts.now,cfg,rand)),marked?task.id:void 0}},slots=Array(due.length),cursor=0,worker=async()=>{for(let i=cursor++;i<due.length;i=cursor++){let item=due[i];if(item)slots[i]=await pollOne(item.task,item.prevFailures)}};return await Promise.all(Array.from({length:Math.min(PR_POLL_CONCURRENCY,due.length)},worker)),slots.filter((id)=>id!==void 0)}function startPrStatusPoller(orch,runtime,intervalMs=DEFAULT_PR_STATUS_POLL_MS,hasSubscribers,run=makeGhPrViewRunner(runtime.prStatus.classify,runtime.prStatus.viewFields),hasWorkingAgent){let schedule=new Map;return startTicker({name:"pr-status-poller",tickMs:intervalMs,...hasSubscribers?{gate:()=>hasSubscribers()||(hasWorkingAgent?.()??!1)}:{},run:()=>runPrStatusPass(orch,{runtime,run,now:Date.now(),at:new Date().toISOString(),schedule,tickMs:intervalMs})})}function quotaResumeContinuePrompt(language){return language==="zh"?"\u4ECE\u4E2D\u65AD\u7684\u5730\u65B9\u7EE7\u7EED\u8FD9\u4E2A\u4EFB\u52A1\u3002\u4E0A\u9762\u7684\u5BF9\u8BDD\u8BB0\u5F55\u91CC\u5DF2\u7ECF\u5B8C\u6210\u7684\u5DE5\u4F5C\u4E0D\u8981\u91CD\u505A\u3002":"Continue the task from where it stopped. The transcript above shows the work already completed \u2014 do not redo it."}var DEFAULT_QUOTA_RESUME_TICK_MS=60000;function dueQuotaResumes(tasks,nowMs){return tasks.filter((task)=>{if(!task.quotaResume||!task.worktreePath)return!1;if(task.deletion)return!1;let at=Date.parse(task.quotaResume.resumeAt);return Number.isFinite(at)&&at<=nowMs})}function exhaustedResetAtMs(usage,nowMs){let candidates=usage.windows.filter((w)=>w.percent>=100&&w.resetsAt!=null&&w.resetsAt>nowMs).map((w)=>w.resetsAt);return candidates.length>0?Math.min(...candidates):null}async function scheduleQuotaResume(orch,runtime,cache,taskId,now=Date.now,plugins){let task=orch.getTask(taskId);if(!task||!task.worktreePath||task.deletion)return;let usage=await cache.get(task.vendor??runtime.defaultTaskVendor,0),resetAtMs=usage?exhaustedResetAtMs(usage,now()):null;if(resetAtMs==null)return;let resumeAt=new Date(resetAtMs).toISOString();await orch.setQuotaResume(taskId,{resumeAt,requestedAt:new Date(now()).toISOString()}),logDaemonInfo("quota-resume",`armed task=${taskId} resumeAt=${resumeAt}`),plugins?.()?.handleUiReport({kind:"quota.exhausted",taskId,detail:{vendor:task.vendor??runtime.defaultTaskVendor,resumeAt}})}async function resumeDueTask(orch,runtime,task,plugins){await orch.setQuotaResume(task.id,null);let delivered=await runtime.deliverPromptToLiveEngine({id:task.id,vendor:task.vendor,command:task.command,worktreePath:task.worktreePath},quotaResumeContinuePrompt(task.observedLanguage));logDaemonInfo("quota-resume",`resume task=${task.id} delivered=${delivered}`),plugins?.()?.handleUiReport({kind:"quota.resumed",taskId:task.id,detail:{delivered}})}function startQuotaResumeRunner(orch,runtime,tickMs=DEFAULT_QUOTA_RESUME_TICK_MS,now=Date.now,plugins){return startTicker({name:"quota-resume",tickMs,run:async()=>{for(let task of dueQuotaResumes(orch.listTasks(),now()))await resumeDueTask(orch,runtime,task,plugins).catch((err)=>logDaemonError("quota-resume",err))}})}var FRESH_POLL_MS=900000,RETRY_BASE_MS=60000,MIN_FETCH_INTERVAL_MS=60000;class QuotaUsageCache{runtime;bus;now;entries=new Map;constructor(runtime,bus,now=Date.now){this.runtime=runtime;this.bus=bus;this.now=now}peek(vendor){return this.entries.get(vendor)?.usage??null}async get(vendor,maxAgeMs){let entry=this.entry(vendor),age=this.now()-(entry.usage?.capturedAt??0);if(entry.usage&&age<=maxAgeMs)return entry.usage;if(entry.inFlight)return entry.inFlight;if(this.now()-entry.attemptedAt<MIN_FETCH_INTERVAL_MS)return entry.usage;return this.fetch(vendor,entry)}async refreshIfDue(vendor){let entry=this.entry(vendor),retryAfter=entry.failures===0?0:Math.min(RETRY_BASE_MS*2**(entry.failures-1),FRESH_POLL_MS),dueAfter=entry.usage?FRESH_POLL_MS:retryAfter;if(this.now()-entry.attemptedAt<dueAfter)return;await this.fetch(vendor,entry)}entry(vendor){let existing=this.entries.get(vendor);if(existing)return existing;let fresh={usage:null,attemptedAt:0,failures:0,inFlight:null};return this.entries.set(vendor,fresh),fresh}fetch(vendor,entry){if(entry.inFlight)return entry.inFlight;return entry.attemptedAt=this.now(),entry.inFlight=this.runtime.quotaUsage(vendor).catch(()=>null).then((usage)=>{if(entry.inFlight=null,usage){entry.failures=0;let changed=JSON.stringify(usage.windows)!==JSON.stringify(entry.usage?.windows);if(entry.usage=usage,changed)this.publish()}else entry.failures+=1;return entry.usage}),entry.inFlight}publish(){let usage={};for(let[vendor,entry]of this.entries)if(entry.usage)usage[vendor]=entry.usage;this.bus.publish("usage.snapshot",{usage})}}function startQuotaUsagePoller(cache,listVendors,hasSubscribers,tickMs=MIN_FETCH_INTERVAL_MS){return startTicker({name:"quota-usage-poller",tickMs,gate:hasSubscribers,immediate:!0,run:()=>{for(let vendor of new Set(listVendors()))cache.refreshIfDue(vendor)}})}function isRemoteRepoKey(value){return value.startsWith("ssh://")}var DEFAULT_TRANSCRIPT_ACTIVITY_TICK_MS=1500,TRANSCRIPT_ACTIVITY_TIMEOUT_MS=4000,TRANSCRIPT_ACTIVITY_SLOW_RETRY_MS=60000,TRANSCRIPT_ACTIVITY_MIN_INTERVAL_MS=1500;function sameTranscriptActivityEntry(a,b){return a.mtimeMs===b.mtimeMs&&a.completionId===b.completionId&&a.completionAt===b.completionAt}async function runTranscriptActivity(worktreePath,vendor,detector,_signal){let{marker,mtimeMs}=await detector.latestActivity(worktreePath);return{mtimeMs:detector.supportsCompletionMarkers()?mtimeMs:0,completionId:marker?.id??null,completionAt:marker?.timestampMs??0}}function trackedWorktrees(tasks,defaultVendor="claude"){let map=new Map;for(let task of tasks){if(!task.worktreePath)continue;if(isRemoteRepoKey(task.repo)||isRemoteRepoKey(task.worktreePath))continue;if(map.has(task.worktreePath))continue;map.set(task.worktreePath,task.vendor??defaultVendor)}return map}class TranscriptActivityCollector{orch;bus;options;entries=new Map;stopped=!1;constructor(orch,bus,options={}){this.orch=orch;this.bus=bus;this.options=options}tick(){if(this.stopped)return;if(this.options.hasSubscribers&&!this.options.hasSubscribers())return;try{let tracked=trackedWorktrees(this.orch.listTasks(),this.options.defaultVendor),pruned=!1;for(let path of this.entries.keys()){if(tracked.has(path))continue;if(this.entries.get(path)?.value)pruned=!0;this.entries.delete(path)}if(pruned)this.publish();for(let[path,vendor]of tracked)this.maybeCollect(path,vendor)}catch(err){logDaemonError("transcript-activity",err)}}stop(){this.stopped=!0}maybeCollect(worktreePath,vendor){let entry=this.entries.get(worktreePath);if(!entry)entry={inFlight:!1,nextAllowedAt:0,vendor,detector:this.createDetector(vendor)},this.entries.set(worktreePath,entry);else if(entry.vendor!==vendor)entry.vendor=vendor,entry.detector=this.createDetector(vendor);let cadence=this.options.cadence??{timeoutMs:TRANSCRIPT_ACTIVITY_TIMEOUT_MS,slowRetryMs:TRANSCRIPT_ACTIVITY_SLOW_RETRY_MS,minIntervalMs:TRANSCRIPT_ACTIVITY_MIN_INTERVAL_MS},run=this.options.run??runTranscriptActivity,current=entry;maybeStartScheduledRun(current,cadence,(signal)=>run(worktreePath,current.vendor,current.detector,signal),(value)=>{if(this.stopped)return;if(this.entries.get(worktreePath)!==current)return;if(current.value&&sameTranscriptActivityEntry(current.value,value))return;current.value=value,this.publish()})}publish(){let activity={};for(let[path,entry]of this.entries)if(entry.value)activity[path]=entry.value;this.bus.publish("transcript.activity",{activity})}createDetector(vendor){return this.options.createDetector?.(vendor)??{latestActivity:async()=>({marker:null,mtimeMs:0}),latestActivityInFile:async()=>null,supportsCompletionMarkers:()=>!1}}}function startTranscriptActivityCollector(orch,runtime,bus,tickMs=DEFAULT_TRANSCRIPT_ACTIVITY_TICK_MS,hasSubscribers){let collector=new TranscriptActivityCollector(orch,bus,{hasSubscribers,createDetector:runtime.createEngineTurnDetector,defaultVendor:runtime.defaultTaskVendor,run:async(path,vendor,detector,signal)=>{let{marker,mtimeMs}=await detector.latestActivity(path);return{mtimeMs:detector.supportsCompletionMarkers()?mtimeMs:await runtime.latestTranscriptMtime(vendor,path),completionId:marker?.id??null,completionAt:marker?.timestampMs??0}}});return startTicker({name:"transcript-activity-collector",tickMs,immediate:!0,run:()=>collector.tick(),onStop:()=>collector.stop()})}import{readFileSync}from"fs";import{basename as basename3}from"path";var DEFAULT_UI_PREFS_DEBOUNCE_MS=200,FOCUS_ACCENT_SLOT_NAMES=["primary","success","info"];function readUiPrefsFromStateFile(statePath){let parsed={};try{let raw=JSON.parse(readFileSync(statePath,"utf8"));if(raw&&typeof raw==="object"&&!Array.isArray(raw))parsed=raw}catch{}let theme=typeof parsed.activeTheme==="string"&&parsed.activeTheme.length>0?parsed.activeTheme:"claude",transparentBackground=parsed.transparentBackground!==!1,focusAccent=typeof parsed.focusAccent==="string"&&FOCUS_ACCENT_SLOT_NAMES.includes(parsed.focusAccent)?parsed.focusAccent:null,sortMode=parsed.activeSortMode==="recent"?"recent":"default",keysCollapsed=parsed["tasksPane.keysCollapsed"]===!0,projectFilter=typeof parsed["tasksPane.projectFilter"]==="string"&&parsed["tasksPane.projectFilter"].length>0?parsed["tasksPane.projectFilter"]:null,locale=typeof parsed.locale==="string"&&parsed.locale.length>0?parsed.locale:"en";return{theme,transparentBackground,focusAccent,locale,sortMode,keysCollapsed,projectFilter}}function samePrefs(a,b){return a.theme===b.theme&&a.transparentBackground===b.transparentBackground&&a.focusAccent===b.focusAccent&&a.locale===b.locale&&a.sortMode===b.sortMode&&a.keysCollapsed===b.keysCollapsed&&a.projectFilter===b.projectFilter}function startUiPrefsWatcher(bus,options={}){let debounceMs=options.debounceMs??DEFAULT_UI_PREFS_DEBOUNCE_MS;if(debounceMs<=0)return()=>{};let statePath=options.statePath??defaultUiPrefsStatePath(),stateFile=basename3(statePath),last=null,publishIfChanged=()=>{try{let next=readUiPrefsFromStateFile(statePath);if(last&&samePrefs(last,next))return;last=next,bus.publish("ui-prefs",next)}catch(err){logDaemonError("ui-prefs-watcher",err)}},stop=startFileWatchTrigger({filePath:statePath,matchBasenames:[stateFile],debounceMs,onTrigger:publishIfChanged,onError:(err)=>logDaemonError("ui-prefs-watcher",err)});return publishIfChanged(),stop}import{spawn as spawn4}from"child_process";var EMPTY_PATHS=new Set;function isRemoteRepoKey2(value){return value.startsWith("ssh://")}function sameWorktreeChanges(a,b){return a.added===b.added&&a.deleted===b.deleted&&a.behind===b.behind&&a.ahead===b.ahead}var DEFAULT_WORKTREE_CHANGES_TICK_MS=2000,WORKTREE_CHANGES_TIMEOUT_MS=4000,WORKTREE_CHANGES_SLOW_RETRY_MS=60000,WORKTREE_CHANGES_MIN_INTERVAL_MS=1500,WORKTREE_CHANGES_QUIET_INTERVAL_MS=60000;function runGit(worktreePath,args,signal){return new Promise((resolve)=>{let stdout="",settled=!1,finish=(status)=>{if(settled)return;settled=!0,resolve(status===0?stdout:null)},child=spawn4("git",args.slice(),{cwd:worktreePath,stdio:["ignore","pipe","ignore"],env:{...process.env,GIT_OPTIONAL_LOCKS:"0"},signal,killSignal:"SIGKILL"});child.stdout?.on("data",(chunk)=>{stdout+=String(chunk)}),child.on("error",()=>finish(null)),child.on("close",finish)})}async function countDrift(worktreePath,baseRef,signal){return parseAheadBehind(await runGit(worktreePath,["rev-list","--left-right","--count",`${baseRef}...HEAD`],signal))}function parseAheadBehind(stdout){if(stdout===null)return null;let parts=stdout.trim().split(/\s+/);if(parts.length!==2)return null;let behind=Number.parseInt(parts[0]??"",10),ahead=Number.parseInt(parts[1]??"",10);if(!Number.isInteger(behind)||behind<0||!Number.isInteger(ahead)||ahead<0)return null;return{behind,ahead}}function countPorcelain(stdout){let added=0,deleted=0;for(let line of stdout.split(`
|
|
7
7
|
`)){if(line.length<3||line.startsWith("##"))continue;if(line[0]==="D"||line[1]==="D")deleted++;else added++}return{added,deleted}}async function runGitStatus(worktreePath,signal,baseRef){let stdout=await runGit(worktreePath,["status","--porcelain=v1"],signal);if(stdout===null)throw Error("git status failed");let counts=countPorcelain(stdout);if(!baseRef)return counts;let drift=await countDrift(worktreePath,baseRef,signal);return drift===null?counts:{...counts,...drift}}function trackedWorktreePaths(tasks){let paths=new Map;for(let task of tasks){if(!task.worktreePath)continue;if(isRemoteRepoKey2(task.repo)||isRemoteRepoKey2(task.worktreePath))continue;if(paths.get(task.worktreePath)===void 0)paths.set(task.worktreePath,task.baseRef)}return paths}var UNREADABLE=Symbol("worktree-changes-unreadable");class WorktreeChangesCollector{orch;bus;options;entries=new Map;stopped=!1;constructor(orch,bus,options={}){this.orch=orch;this.bus=bus;this.options=options}tick(){if(this.stopped)return;if(this.options.hasSubscribers&&!this.options.hasSubscribers())return;try{let tasks=this.orch.listTasks(),tracked=trackedWorktreePaths(tasks),pruned=!1;for(let path of this.entries.keys()){if(tracked.has(path))continue;if(this.entries.get(path)?.value)pruned=!0;this.entries.delete(path)}if(pruned)this.publish();let busy=this.busyPaths(tasks);for(let[path,baseRef]of tracked)this.maybeCollect(path,baseRef,busy.has(path))}catch(err){logDaemonError("worktree-changes",err)}}stop(){this.stopped=!0}busyPaths(tasks){let ids=this.options.activeTaskIds?.();if(!ids)return EMPTY_PATHS;let active=new Set(ids);if(active.size===0)return EMPTY_PATHS;let paths=new Set;for(let task of tasks)if(task.worktreePath&&active.has(task.id))paths.add(task.worktreePath);return paths}maybeCollect(worktreePath,baseRef,busy=!1){let entry=this.entries.get(worktreePath);if(!entry)entry={inFlight:!1,nextAllowedAt:0},this.entries.set(worktreePath,entry);let now=Date.now(),probe=(this.options.probe??worktreeFingerprint)(worktreePath,baseRef),quietUntil=entry.quietUntil??0;if(!busy&&probe!==null&&probe===entry.probe&&now<quietUntil)return;let quietIntervalMs=this.options.quietIntervalMs??WORKTREE_CHANGES_QUIET_INTERVAL_MS,cadence=this.options.cadence??{timeoutMs:WORKTREE_CHANGES_TIMEOUT_MS,slowRetryMs:WORKTREE_CHANGES_SLOW_RETRY_MS,minIntervalMs:WORKTREE_CHANGES_MIN_INTERVAL_MS},run=this.options.run??runGitStatus;maybeStartScheduledRun(entry,cadence,(signal)=>{return entry.probe=probe,entry.quietUntil=Date.now()+quietIntervalMs,run(worktreePath,signal,baseRef).catch(()=>UNREADABLE)},(value)=>{if(this.stopped)return;if(this.entries.get(worktreePath)!==entry)return;if(value===UNREADABLE&&entry.value!==void 0&&entry.value!==UNREADABLE)return;if(entry.value===value)return;if(entry.value&&entry.value!==UNREADABLE&&value!==UNREADABLE&&sameWorktreeChanges(entry.value,value))return;entry.value=value,this.publish()})}publish(){let changes={},unreadable=[];for(let[path,entry]of this.entries)if(entry.value===UNREADABLE)unreadable.push(path);else if(entry.value)changes[path]=entry.value;this.bus.publish("worktree.changes",unreadable.length>0?{changes,unreadable}:{changes})}}function startWorktreeChangesCollector(orch,runtime,bus,tickMs=DEFAULT_WORKTREE_CHANGES_TICK_MS,hasSubscribers,activeTaskIds){let collector=new WorktreeChangesCollector(orch,bus,{hasSubscribers,run:runtime.runWorktreeStatus,...activeTaskIds?{activeTaskIds}:{}});return startTicker({name:"worktree-changes-collector",tickMs,immediate:!0,run:()=>collector.tick(),onStop:()=>collector.stop()})}var DEFAULT_UPDATE_POLL_MS=21600000;function createProtocolUpgradeReporter(orch,runtime){return(taskId,tabId,evidence)=>{if(tabId!=="tab-1")return;let task=orch.getTask(taskId);if(!task)return;let upgrade=runtime.resolveProtocolUpgrade?.(task,evidence);if(!upgrade)return;orch.setCommand(taskId,upgrade.command,upgrade.vendor).then(()=>logDaemonInfo("protocol-sniff",`upgraded task ${taskId} to the ${upgrade.vendor} protocol from its live session`)).catch((err)=>logDaemonError("protocol-sniff",err))}}function startDaemonCollectors(orch,runtime,bus,hasSubscribersFor,options,quotaUsage,automations,activity,deferredSweep){let stopActivityObserver=activity?startActivityObserver(activity,{...createActivityObserverIo(options.homeDir,runtime,activity),onEngineEvidence:createProtocolUpgradeReporter(orch,runtime)},()=>hasSubscribersFor("engine-state")):()=>{},checkUpdate=options.checkUpdate??runtime.checkLatestVersion,updatePollMs=options.updatePollMs??DEFAULT_UPDATE_POLL_MS,pollUpdate=()=>{checkUpdate().then((info)=>bus.publish("update",{info})).catch((err)=>logDaemonError("update-poller",err))},updateTimer=null;if(updatePollMs>0)pollUpdate(),updateTimer=setInterval(pollUpdate,updatePollMs),updateTimer.unref?.();let stopAutoTitlePoller=startAutoTitlePoller(orch,runtime,options.autoTitlePollMs??DEFAULT_AUTO_TITLE_POLL_MS,()=>hasSubscribersFor("task.snapshot")),stopUiPrefsWatcher=startUiPrefsWatcher(bus,{statePath:defaultUiPrefsStatePath(options.homeDir),debounceMs:options.uiPrefsDebounceMs??DEFAULT_UI_PREFS_DEBOUNCE_MS}),stopKeybindingsWatcher=startKeybindingsWatcher(bus,{path:defaultKeybindingsPath(options.homeDir),debounceMs:options.keybindingsDebounceMs??DEFAULT_KEYBINDINGS_DEBOUNCE_MS}),stopWorktreeChangesCollector=startWorktreeChangesCollector(orch,runtime,bus,options.worktreeChangesTickMs??DEFAULT_WORKTREE_CHANGES_TICK_MS,()=>hasSubscribersFor("worktree.changes"),activity?()=>activity.currentNonIdle().map((e)=>e.taskId):void 0),stopTranscriptActivityCollector=startTranscriptActivityCollector(orch,runtime,bus,options.transcriptActivityTickMs??DEFAULT_TRANSCRIPT_ACTIVITY_TICK_MS,()=>hasSubscribersFor("transcript.activity")),stopContextUsageCollector=activity?startContextUsageCollector(activity,orch,bus,runtime,options.contextUsageTickMs??DEFAULT_CONTEXT_USAGE_TICK_MS,()=>hasSubscribersFor("usage.context")):()=>{},stopPrStatusPoller=startPrStatusPoller(orch,runtime,options.prStatusPollMs??DEFAULT_PR_STATUS_POLL_MS,()=>hasSubscribersFor("task.snapshot"),void 0,activity?()=>activity.currentNonIdle().some((e)=>e.state!=="idle"):void 0),stopQuotaResumeRunner=startQuotaResumeRunner(orch,runtime,options.quotaResumeTickMs??DEFAULT_QUOTA_RESUME_TICK_MS,void 0,automations?.plugins),stopAutomationRunner=automations?startAutomationRunner({store:automations.store,orch,runtime,link:automations.link,...automations.plugins?{plugins:automations.plugins}:{},...automations.deferred?{deferred:automations.deferred}:{},...automations.inbox?{inbox:automations.inbox}:{}},options.automationTickMs??DEFAULT_AUTOMATION_TICK_MS):()=>{},stopDeferredPromptSweep=deferredSweep?startDeferredPromptSweep(deferredSweep,options.deferredSweepTickMs??DEFAULT_DEFERRED_SWEEP_TICK_MS):()=>{},stopQuotaUsagePoller=quotaUsage?startQuotaUsagePoller(quotaUsage,()=>runtime.vendorsWithQuotaProbe(),()=>hasSubscribersFor("usage.snapshot"),options.quotaUsageTickMs):()=>{};return()=>{if(updateTimer)clearInterval(updateTimer);stopActivityObserver(),stopAutoTitlePoller(),stopPrStatusPoller(),stopQuotaResumeRunner(),stopAutomationRunner(),stopDeferredPromptSweep(),stopQuotaUsagePoller(),stopUiPrefsWatcher(),stopKeybindingsWatcher(),stopWorktreeChangesCollector(),stopTranscriptActivityCollector(),stopContextUsageCollector()}}var AGENT_TURN_HANDLERS=[{name:"agentTurn.list",handle(payload,ctx){let taskId=optionalString(payload,"taskId"),repo=optionalString(payload,"repoRoot"),since=optionalNumber(payload,"since"),limit=optionalNumber(payload,"limit");return{turns:ctx.agentTurns?.list({...taskId?{taskId}:{},...repo?{repo}:{},...since!==void 0?{since}:{},...limit!==void 0?{limit}:{}})??[]}}}];async function discardMatchingDeferredPrompt(ctx,taskId,tabId,at,reason){if(!tabId||!ctx.deferredPrompts)return;let deferredId=ctx.inbox.snapshot().find((item)=>item.taskId===taskId&&item.tabId===tabId&&item.state==="prompt_deferred"&&(at===void 0||item.at===at))?.detail?.deferredPrompt?.id;if(deferredId)await ctx.deferredPrompts.discard(deferredId,reason);return deferredId}var ATTENTION_HANDLERS=[{name:"attention.dismissRoutine",async handle(payload,ctx){return await ctx.inbox.deleteRoutineEpisode(requireString(payload,"automationId")),{deleted:!0}}},{name:"attention.dismiss",async handle(payload,ctx){let taskId=requireString(payload,"taskId"),tabId=optionalString(payload,"tabId")??null,at=payload.at===void 0?void 0:requireNumber(payload,"at"),deferredId=await discardMatchingDeferredPrompt(ctx,taskId,tabId,at,"Inbox item dismissed"),deleted=await ctx.inbox.deleteEpisode(taskId,tabId,at,deferredId?"prompt_deferred":void 0,deferredId);if(deleted)ctx.plugins?.handleUiReport({kind:"attention.handled",taskId,detail:{how:"dismissed",...tabId?{tabId}:{}}});return{deleted}}},{name:"attention.read",async handle(payload,ctx){let taskId=requireString(payload,"taskId"),tabId=optionalString(payload,"tabId")??null,at=requireNumber(payload,"at"),deferredId=await discardMatchingDeferredPrompt(ctx,taskId,tabId,at,"Inbox item read by legacy client"),updated=await ctx.inbox.markRead(taskId,tabId,at,deferredId?"prompt_deferred":void 0,deferredId);if(updated)ctx.plugins?.handleUiReport({kind:"attention.handled",taskId,detail:{how:"read",...tabId?{tabId}:{}}});return{updated}}}];import{execFile}from"child_process";import{stat}from"fs/promises";import{promisify}from"util";var execFileAsync=promisify(execFile);function isProbeable(repo){return!repo.startsWith("ssh://")}async function isGitWorkTree(repo){try{let{stdout}=await execFileAsync("git",["-C",repo,"rev-parse","--is-inside-work-tree"]);return stdout.trim()==="true"}catch{return!1}}async function assertRoutineRepo(repo){if(!isProbeable(repo))return;if(!(await stat(repo).catch(()=>null))?.isDirectory())throw Error(`repo does not exist: ${repo}`);if(!await isGitWorkTree(repo))throw Error(`not a git repository: ${repo}`)}async function assertRoutineBaseRef(repo,ref){if(!ref||!isProbeable(repo))return;if(!await isGitWorkTree(repo))return;try{await execFileAsync("git",["-C",repo,"rev-parse","--verify","--quiet",`${ref}^{commit}`])}catch{throw Error(`base branch does not resolve in ${repo}: ${ref}`)}}function requireSchedule(payload,key){let schedule=requireString(payload,key);if(!isValidCron(schedule))throw Error(`invalid cron expression: ${schedule}`);return schedule}function readPrecheck(payload){if(!("precheck"in payload))return;let raw=payload.precheck;if(raw===null)return null;if(!raw||typeof raw!=="object")throw Error("precheck must be an object or null");let command=requireString(raw,"command"),timeoutSeconds=optionalNumber(raw,"timeoutSeconds")??120;if(timeoutSeconds<=0)throw Error("precheck.timeoutSeconds must be greater than zero");return{command,timeoutSeconds}}function readGraceMinutes(payload){let value=optionalNumber(payload,"missedRunGraceMinutes");if(value!==void 0&&value<0)throw Error("missedRunGraceMinutes must be zero or more minutes");return value}var AUTOMATION_HANDLERS=[{name:"automation.list",async handle(_payload,ctx){let automations=ctx.automations.list(),lastRunStatus={};for(let automation of automations){let latest=ctx.automations.runsFor(automation.id,1)[0];if(latest)lastRunStatus[automation.id]=latest.status}return{automations,lastRunStatus,keepsDaemonAlive:ctx.automations.hasEnabled()}}},{name:"automation.create",async handle(payload,ctx){let precheck=readPrecheck(payload),repo=requireString(payload,"repo"),baseRef=optionalString(payload,"baseRef");if(await assertRoutineRepo(repo),baseRef)await assertRoutineBaseRef(repo,baseRef);let automation=await ctx.automations.create({name:requireString(payload,"name"),repo,prompt:requireString(payload,"prompt"),schedule:requireSchedule(payload,"schedule"),missedRunGraceMinutes:readGraceMinutes(payload)??60,...optionalVendor(payload,"vendor")?{vendor:optionalVendor(payload,"vendor")}:{},...precheck?{precheck}:{},...baseRef?{baseRef}:{},...optionalBoolean(payload,"persistentSession")===!0?{persistentSession:!0}:{},...optionalBoolean(payload,"enabled")!==void 0?{enabled:optionalBoolean(payload,"enabled")}:{}});return ctx.daemon.reevaluateIdle(),{automation}}},{name:"automation.update",async handle(payload,ctx){let id=requireString(payload,"id"),nextBaseRef="baseRef"in payload?optionalString(payload,"baseRef"):void 0,currentRepo=ctx.automations.get(id)?.repo;if(nextBaseRef&¤tRepo)await assertRoutineBaseRef(currentRepo,nextBaseRef);let patch={...optionalString(payload,"name")!==void 0?{name:optionalString(payload,"name")}:{},...optionalString(payload,"prompt")!==void 0?{prompt:optionalString(payload,"prompt")}:{},...optionalVendor(payload,"vendor")!==void 0?{vendor:optionalVendor(payload,"vendor")}:{},..."schedule"in payload?{schedule:requireSchedule(payload,"schedule")}:{},...readPrecheck(payload)!==void 0?{precheck:readPrecheck(payload)}:{},..."baseRef"in payload?{baseRef:optionalString(payload,"baseRef")??null}:{},...optionalBoolean(payload,"enabled")!==void 0?{enabled:optionalBoolean(payload,"enabled")}:{},...readGraceMinutes(payload)!==void 0?{missedRunGraceMinutes:readGraceMinutes(payload)}:{},...optionalBoolean(payload,"persistentSession")!==void 0?{persistentSession:optionalBoolean(payload,"persistentSession")}:{}},automation=await ctx.automations.update(id,patch);if(!automation)throw Error(`automation not found: ${id}`);return ctx.daemon.reevaluateIdle(),{automation}}},{name:"automation.delete",async handle(payload,ctx){let id=requireString(payload,"id"),deleted=await ctx.automations.delete(id);if(deleted)await ctx.inbox.deleteRoutineEpisode(id).catch(()=>{}),ctx.daemon.reevaluateIdle();return{deleted}}},{name:"automation.runs",async handle(payload,ctx){let automationId=requireString(payload,"id");if(!ctx.automations.get(automationId))throw Error(`automation not found: ${automationId}`);return{runs:ctx.automations.runsFor(automationId)}}},{name:"automation.runNow",blocking:!0,async handle(payload,ctx){let id=requireString(payload,"id"),automation=ctx.automations.get(id);if(!automation)throw Error(`automation not found: ${id}`);return{status:await runAutomationOnce({store:ctx.automations,orch:ctx.orch,runtime:ctx.runtime,link:ctx.selfLink,...ctx.plugins?{plugins:()=>ctx.plugins??null}:{},...ctx.deferredPrompts?{deferred:ctx.deferredPrompts}:{},inbox:ctx.inbox},automation,{scheduledFor:Date.now(),trigger:"manual"})}}}];function deferredPromptInput(payload){let taskId=requireString(payload,"taskId"),tabId=requireString(payload,"tabId"),prompt=requireString(payload,"prompt"),layer=requireString(payload,"layer");if(layer!=="recent-human-write"&&layer!=="composer-not-empty")throw Error('layer must be "recent-human-write" or "composer-not-empty"');let senderLabel=optionalString(payload,"senderLabel"),senderTaskId=optionalString(payload,"senderTaskId");return{taskId,tabId,prompt,layer,...senderLabel!==void 0?{senderLabel}:{},...senderTaskId!==void 0?{senderTaskId}:{},at:Date.now()}}async function fileWithInbox(input,ctx){if(!ctx.orch.getTask(input.taskId))throw Error(`task not found: ${input.taskId}`);if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let kind="filed",record;try{record=await ctx.deferredPrompts.file(input)}catch(error){if(!(error instanceof DeferredPromptPendingError))throw error;kind="occupied",record=error.existing}return await ctx.inbox.recordPromptDeferred(record.taskId,record.tabId,record.id,record.layer,record.at+DEFERRED_PROMPT_TTL_MS),{kind,record}}function errorText2(error){return error instanceof Error?error.message:String(error)}function publicRecord(record){return{id:record.id,taskId:record.taskId,tabId:record.tabId,prompt:record.prompt,layer:record.layer,at:new Date(record.at).toISOString(),expiresAt:new Date(record.at+DEFERRED_PROMPT_TTL_MS).toISOString(),...record.senderLabel!==void 0?{senderLabel:record.senderLabel}:{},...record.senderTaskId!==void 0?{senderTaskId:record.senderTaskId}:{}}}async function deleteDeferredInboxPointer(record,ctx){await ctx.inbox.deleteEpisode(record.taskId,record.tabId,void 0,"prompt_deferred",record.id)}async function cleanupDeliveredClaim(claim,ctx,report){if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let{record}=claim;try{await deleteDeferredInboxPointer(record,ctx),await ctx.deferredPrompts.completeClaim(claim),report.cleaned.push(record.id)}catch(error){await ctx.deferredPrompts.releaseClaim(claim),report.cleanupPending.push({id:record.id,taskId:record.taskId,tabId:record.tabId,error:errorText2(error)})}}async function deliverClaim(claim,ctx,report){if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let{record}=claim;if(record.deliveredAt||record.deliveryStartedAt){await cleanupDeliveredClaim(claim,ctx,report);return}let task=ctx.orch.getTask(record.taskId);if(!task?.worktreePath){await ctx.deferredPrompts.releaseClaim(claim),report.retained.push({id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"unavailable"});return}let deliveryStarted=!1;try{await ctx.deferredPrompts.beginDelivery(claim),deliveryStarted=!0;let outcome=await ctx.runtime.deliverPromptToLiveEngineTabDetailed({id:task.id,tabId:record.tabId,vendor:task.vendor,command:task.command,worktreePath:task.worktreePath},record.prompt);if(outcome.outcome==="delivered")await ctx.deferredPrompts.markDelivered(claim),report.delivered.push(record.id),await cleanupDeliveredClaim(claim,ctx,report);else await ctx.deferredPrompts.resetDelivery(claim),await ctx.deferredPrompts.releaseClaim(claim),report.retained.push(outcome.outcome==="busy"?{id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"busy",layer:outcome.layer}:{id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"unavailable"})}catch(error){if(await ctx.deferredPrompts.releaseClaim(claim).catch(()=>{}),deliveryStarted)report.cleanupPending.push({id:record.id,taskId:record.taskId,tabId:record.tabId,error:errorText2(error)});else report.retained.push({id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"error",error:errorText2(error)})}}async function flushDeferredPrompts(ctx){if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let report={delivered:[],cleaned:[],expired:[],retained:[],cleanupPending:[]},swept=await sweepExpiredDeferredPrompts({store:ctx.deferredPrompts,inbox:ctx.inbox});report.expired.push(...swept.expired),report.cleanupPending.push(...swept.cleanupPending);let listed=await ctx.deferredPrompts.list();for(let record of listed.records){if(record.deliveredAt||record.deliveryStartedAt){let claimed2=await ctx.deferredPrompts.claim(record.id);if(claimed2.kind==="claimed")await cleanupDeliveredClaim(claimed2.claim,ctx,report);continue}if(ctx.runtime.composerGateEnabled()){report.retained.push({id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"gate-enabled"});continue}let claimed=await ctx.deferredPrompts.claim(record.id);if(claimed.kind==="in-flight"){report.retained.push({id:record.id,taskId:record.taskId,tabId:record.tabId,reason:"in-flight"});continue}if(claimed.kind==="claimed")await deliverClaim(claimed.claim,ctx,report)}return report}var DEFERRED_PROMPT_HANDLERS=[{name:"deferredPrompt.file",async handle(payload,ctx){let result=await fileWithInbox(deferredPromptInput(payload),ctx);if(result.kind==="occupied")throw new DeferredPromptPendingError(result.record);return{id:result.record.id}}},{name:"deferredPrompt.fileIfVacant",async handle(payload,ctx){let result=await fileWithInbox(deferredPromptInput(payload),ctx);return{kind:result.kind,id:result.record.id,expiresAt:publicRecord(result.record).expiresAt,...result.kind==="occupied"?{layer:result.record.layer}:{}}}},{name:"deferredPrompt.get",async handle(){throw Error("legacy deferred prompt release is unsafe; restart Rove to update the client")}},{name:"deferredPrompt.list",async handle(_payload,ctx){if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");return{records:(await ctx.deferredPrompts.list()).records.map(publicRecord)}}},{name:"deferredPrompt.dismiss",async handle(payload,ctx){let id=requireString(payload,"id");if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let dropped=await ctx.deferredPrompts.discard(id,"Inbox item dismissed");if(!dropped)return{dismissed:!1};return await deleteDeferredInboxPointer(dropped,ctx),{dismissed:!0,record:publicRecord(dropped)}}},{name:"deferredPrompt.resolve",async handle(payload,ctx){let id=requireString(payload,"id");if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let claimed=await ctx.deferredPrompts.claim(id);if(claimed.kind!=="claimed")return{removed:!1,kind:claimed.kind};let report={delivered:[],cleaned:[],expired:[],retained:[],cleanupPending:[]};return await ctx.deferredPrompts.markDelivered(claimed.claim),await cleanupDeliveredClaim(claimed.claim,ctx,report),{removed:report.cleaned.includes(id),cleanupPending:report.cleanupPending}}},{name:"deferredPrompt.release",blocking:!0,async handle(payload,ctx){let id=requireString(payload,"id");if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let report={delivered:[],cleaned:[],expired:[],retained:[],cleanupPending:[]},claimed=await ctx.deferredPrompts.claim(id);if(claimed.kind==="claimed")await deliverClaim(claimed.claim,ctx,report);return{kind:claimed.kind,...report}}},{name:"deferredPrompt.discardTab",async handle(payload,ctx){let taskId=requireString(payload,"taskId"),tabId=requireString(payload,"tabId");if(!ctx.deferredPrompts)throw Error("deferred prompt store unavailable");let dropped=await ctx.deferredPrompts.discardTab(taskId,tabId,"tab closed");for(let record of dropped)await deleteDeferredInboxPointer(record,ctx);return{dropped:dropped.map((record)=>record.id)}}},{name:"deferredPrompt.flush",blocking:!0,async handle(_payload,ctx){return await flushDeferredPrompts(ctx)}}];async function ingestAgentTurns(store,runtime,orch,input){if(!input.transcriptPath)return{recorded:0,latest:void 0};let task=orch.getTask(input.taskId),vendor=input.vendor??task?.vendor;if(!vendor)return{recorded:0,latest:void 0};let turns=await runtime.readEngineTurns(vendor,input.transcriptPath);if(turns.length===0)return{recorded:0,latest:void 0};let records=turns.map((turn)=>({...turn,taskId:input.taskId,...input.tabId?{tabId:input.tabId}:{},vendor,...task?.repo?{repo:task.repo}:{}})),recorded=await store.record(records),last=records[records.length-1],latest=last?{id:last.id,model:last.model,usage:last.usage,startedAt:last.startedAt,endedAt:last.endedAt}:void 0;return{recorded,latest}}function ingestAgentTurnsBestEffort(store,runtime,orch,input,onDone){if(!store){onDone?.(void 0);return}ingestAgentTurns(store,runtime,orch,input).then(({latest})=>onDone?.(latest)).catch((err)=>{logDaemonError("agent-turns-ingest",err),onDone?.(void 0)})}import{createHash}from"crypto";import{existsSync as existsSync2}from"fs";import{homedir as homedir2}from"os";import path from"path";var KOBE_WORKTREE_ROOT_DIR="worktrees",REPO_LOCAL_ROVE_WORKTREE_ROOT_SUBPATH=".rove/worktrees",REPO_LOCAL_KOBE_WORKTREE_ROOT_SUBPATH=".kobe/worktrees",LEGACY_KOBE_WORKTREE_ROOT_SUBPATH=".claude/worktrees",REPO_LOCAL_KOBE_MANAGED_WORKTREE_ROOT_SUBPATHS=[REPO_LOCAL_ROVE_WORKTREE_ROOT_SUBPATH,REPO_LOCAL_KOBE_WORKTREE_ROOT_SUBPATH,LEGACY_KOBE_WORKTREE_ROOT_SUBPATH];function stateDir(basename4){return path.join(readRoveHomeDirEnv()??homedir2(),basename4)}function repoWorktreeDirName(repo){let safeBase=(path.basename(repo)||"repo").replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"repo",hash=createHash("sha1").update(path.resolve(repo)).digest("hex").slice(0,12);return`${safeBase}-${hash}`}function worktreeRootFor(repo){if(!path.isAbsolute(repo))throw Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);return path.join(stateDir(ROVE_STATE_DIR_BASENAME),KOBE_WORKTREE_ROOT_DIR,repoWorktreeDirName(repo))}function managedWorktreeRootsFor(repo){if(!path.isAbsolute(repo))throw Error(`managedWorktreeRootsFor: repo must be an absolute path, got: ${repo}`);return[worktreeRootFor(repo),path.join(stateDir(LEGACY_KOBE_STATE_DIR_BASENAME),KOBE_WORKTREE_ROOT_DIR,repoWorktreeDirName(repo)),...REPO_LOCAL_KOBE_MANAGED_WORKTREE_ROOT_SUBPATHS.map((subpath)=>path.join(repo,subpath))]}function normalize(p){return p.length>1&&p.endsWith("/")?p.slice(0,-1):p}function isAncestorOrSelf(wt,cwd){return cwd===wt||cwd.startsWith(`${wt}/`)}function crossesRepoBoundary(wt,cwd){for(let dir=cwd;dir.length>wt.length;dir=path.dirname(dir))if(existsSync2(path.join(dir,".git")))return!0;return!1}function matchTaskByCwd(tasks,cwd){let target=normalize(cwd),bestId,bestWt="",bestLen=-1;for(let t of tasks){if(!t.worktreePath)continue;let wt=normalize(t.worktreePath);if(isAncestorOrSelf(wt,target)&&wt.length>bestLen)bestLen=wt.length,bestWt=wt,bestId=t.id}if(bestId&&crossesRepoBoundary(bestWt,target))return;return bestId}function matchTaskByWorktreePath(tasks,worktreePath){let target=normalize(worktreePath);for(let t of tasks)if(t.worktreePath&&normalize(t.worktreePath)===target)return t.id;return}function findAdoptableWorktree(tasks,cwd){let target=normalize(cwd),repos=new Set,known=new Set;for(let t of tasks){if(t.repo&&path.isAbsolute(t.repo))repos.add(normalize(t.repo));if(t.worktreePath)known.add(normalize(t.worktreePath))}for(let repo of repos)for(let root of managedWorktreeRootsFor(repo).map(normalize)){let prefix=`${root}/`;if(!target.startsWith(prefix))continue;let name=target.slice(prefix.length).split("/")[0];if(!name)continue;let worktreePath=`${prefix}${name}`;if(known.has(worktreePath))return;return{repo,worktreePath}}return}var ENGINE_REPORT_HANDLER={name:"engine.reportEvent",async handle(payload,ctx){let kind=requireString(payload,"kind");if(!ctx.runtime.isEngineActivityKind(kind))throw Error(`unknown engine event kind: ${kind}`);let explicitId=optionalString(payload,"taskId"),cwd=optionalString(payload,"cwd");if(!explicitId&&cwd&&kind==="session-start"){let cand=findAdoptableWorktree(ctx.orch.listTasks(),cwd);if(cand)try{await ctx.orch.adoptWorktree({repo:cand.repo,worktreePath:cand.worktreePath,ifExists:"return"})}catch(err){logDaemonError("worktree-autosync",err)}}let taskId=explicitId??(cwd?matchTaskByCwd(ctx.orch.listTasks(),cwd):void 0);if(!taskId)return{};let detail=optionalActivityDetail(payload),tabId=optionalString(payload,"tabId"),sessionId=optionalString(payload,"sessionId"),transcriptPath=optionalString(payload,"transcriptPath"),session=sessionId?{id:sessionId,transcriptPath}:void 0,isStateKind=ctx.runtime.affectsActivityState(kind),vendor=optionalString(payload,"engine");if(isStateKind){if(ctx.activity.report(taskId,kind,detail,tabId,session,vendor),explicitId)await ctx.inbox.record(taskId,kind,detail,tabId??null).catch((err)=>logDaemonError("attention-inbox-record",err))}if(ctx.engineEvents?.append(taskId,{kind,...tabId?{tabId}:{},...vendor?{vendor}:{},...detail?{detail}:{},at:Date.now()}),kind==="pre-compact"||kind==="post-compact"||kind==="subagent-start"||kind==="subagent-stop")ctx.bus.publish("engine.lifecycle",{taskId,kind,...tabId?{tabId}:{},at:Date.now()});let pluginReport={kind,taskId,...detail?{detail}:{},...vendor?{vendor}:{},...tabId?{tabId}:{},...sessionId?{sessionId}:{}};if(kind!=="turn-complete")ctx.plugins?.handleEngineReport(pluginReport);if(kind==="turn-complete")ingestAgentTurnsBestEffort(ctx.agentTurns,ctx.runtime,ctx.orch,{taskId,...tabId?{tabId}:{},...vendor?{vendor}:{},...transcriptPath?{transcriptPath}:{}},(latest)=>ctx.plugins?.handleEngineReport(latest?{...pluginReport,detail:{...pluginReport.detail??{},turn:latest}}:pluginReport));if(kind==="turn-start"){if(ctx.runtime.maybeAutoStart(ctx.orch,taskId).then((result)=>{if(result==="moved")console.log(`[status-rules] task ${taskId} auto-moved backlog \u2192 in_progress`)}).catch((err)=>logDaemonError("status-rules",err)),ctx.orch.getTask(taskId)?.quotaResume)ctx.orch.setQuotaResume(taskId,null).catch((err)=>logDaemonError("quota-resume",err))}if(kind==="turn-failed"&&detail?.failure==="rate_limit")scheduleQuotaResume(ctx.orch,ctx.runtime,ctx.quotaUsage,taskId,void 0,()=>ctx.plugins??null).catch((err)=>logDaemonError("quota-resume",err));return{}}};var ISSUE_HANDLERS=[{name:"issue.list",async handle(payload,ctx){return ctx.issues.list(requireString(payload,"repoRoot"))}},{name:"issue.repos",async handle(_payload,ctx){return{repos:await ctx.issues.repos()}}},{name:"issue.mutate",async handle(payload,ctx){let repoRoot=requireString(payload,"repoRoot"),op=payload.op,opType=op&&typeof op==="object"?op.type:void 0;if(opType==="link"||opType==="update"){let taskId=op.taskId;if(typeof taskId==="string"&&taskId.length>0&&!ctx.orch.getTask(taskId))throw Error(`task not found: ${taskId}`)}let state=await ctx.issues.mutate(repoRoot,payload.op);return ctx.bus.publish("issue.snapshot",state),ctx.plugins?.handleUiReport({kind:"issue.changed",detail:{repo:repoRoot,...payload.op&&typeof payload.op==="object"?{op:payload.op}:{}}}),state}}];var MAX_FAILING_JOBS=3,MAX_TAIL_LINES=200,RUN_LOG_TIMEOUT_MS=30000;function stringField(value,key){if(typeof value!=="object"||value===null)return"";let field=value[key];return typeof field==="string"?field:""}function runIdFromDetailsUrl(url){return/\/actions\/runs\/(\d+)\b/.exec(url)?.[1]??null}function failingCheckTargets(rollup){let targets=[];for(let entry of rollup){let status=stringField(entry,"status").toUpperCase(),conclusion=stringField(entry,"conclusion").toUpperCase(),contextState=stringField(entry,"state").toUpperCase(),failedAs=null;if(status&&status!=="COMPLETED")failedAs=null;else if(conclusion)failedAs=conclusion==="SUCCESS"||conclusion==="NEUTRAL"||conclusion==="SKIPPED"?null:conclusion;else if(contextState==="FAILURE"||contextState==="ERROR")failedAs=contextState;if(failedAs===null)continue;let url=stringField(entry,"detailsUrl")||stringField(entry,"targetUrl");targets.push({jobName:stringField(entry,"name")||stringField(entry,"context")||"check",conclusion:failedAs,url,runId:runIdFromDetailsUrl(url)})}return targets}function parseFailedRunLog(text,maxLines=MAX_TAIL_LINES){let byJob=new Map;for(let raw of text.split(`
|
|
8
8
|
`)){let line=raw.replace(/\r$/,"");if(line.length===0)continue;let firstTab=line.indexOf("\t");if(firstTab<=0)continue;let job=line.slice(0,firstTab),secondTab=line.indexOf("\t",firstTab+1),body=(secondTab===-1?line.slice(firstTab+1):line.slice(secondTab+1)).replace(/^\uFEFF/,""),lines=byJob.get(job);if(!lines)lines=[],byJob.set(job,lines);if(lines.push(body),lines.length>maxLines)lines.shift()}return byJob}function joinFailingChecks(targets,logsByJob){return{checks:targets.slice(0,MAX_FAILING_JOBS).map((target)=>({jobName:target.jobName,conclusion:target.conclusion,url:target.url,tail:(logsByJob.get(target.jobName)??[]).join(`
|
|
9
9
|
`)})),totalFailing:targets.length}}async function ghText(args,cwd,timeoutMs){let controller=new AbortController,timer=setTimeout(()=>controller.abort(),timeoutMs);try{let res=await spawnGh(args,cwd,controller.signal);return res.status===0?res.stdout:""}finally{clearTimeout(timer)}}async function readFailingChecks(opts){try{let rollupJson=await ghText(["pr","view",String(opts.prNumber),"--json","statusCheckRollup"],opts.worktreePath,PR_VIEW_TIMEOUT_MS);if(!rollupJson)return{checks:[],totalFailing:0};let parsed=JSON.parse(rollupJson),rollup=Array.isArray(parsed.statusCheckRollup)?parsed.statusCheckRollup:[],targets=failingCheckTargets(rollup);if(targets.length===0)return{checks:[],totalFailing:0};let runIds=[];for(let target of targets.slice(0,MAX_FAILING_JOBS))if(target.runId&&!runIds.includes(target.runId))runIds.push(target.runId);let logsByJob=new Map;for(let runId of runIds){let text=await ghText(["run","view",runId,"--log-failed"],opts.worktreePath,RUN_LOG_TIMEOUT_MS);for(let[job,lines]of parseFailedRunLog(text))if(!logsByJob.has(job))logsByJob.set(job,lines)}return joinFailingChecks(targets,logsByJob)}catch(err){return logDaemonError("pr-failing-checks",err),{checks:[],totalFailing:0}}}var PR_HANDLERS=[{name:"pr.failingChecks",async handle(payload,ctx){let taskId=requireString(payload,"taskId"),task=ctx.orch?.getTask(taskId),prNumber=task?.prStatus?.number;if(!task?.worktreePath||typeof prNumber!=="number")return{checks:[],totalFailing:0};return await readFailingChecks({worktreePath:task.worktreePath,prNumber})}}];var SUBSYSTEM2="task-deletion-audit";function originText(origin,task){let parts=[];if(origin)parts.push(`client=${origin.clientId}`);if(origin?.requestedBy)parts.push(`by=${origin.requestedBy.taskId}::${origin.requestedBy.tabId}`);let dispatcher=task?.dispatcher;if(dispatcher)parts.push(`spawnedBy=${dispatcher.taskId}::${dispatcher.tabId}`);return parts.length>0?` (${parts.join(" ")})`:""}function subjectText(task,taskId){if(!task)return`task ${taskId}`;let bits=[`task ${taskId}`,`title=${JSON.stringify(task.title)}`,`kind=${task.kind}`];if(task.branch)bits.push(`branch=${task.branch}`);if(task.worktreePath)bits.push(`worktree=${task.worktreePath}`);return bits.join(" ")}function auditDeletionRequested(taskId,task,origin,opts={}){logDaemonInfo(SUBSYSTEM2,`requested ${subjectText(task,taskId)} force=${opts.force===!0} deleteBranch=${opts.deleteBranch===!0}${originText(origin,task)}`)}function auditDeletionRemoved(taskId,task){logDaemonInfo(SUBSYSTEM2,`removed ${subjectText(task,taskId)}`)}function auditDeletionFailed(taskId,task,err){let cause=err instanceof Error?err:Error(String(err)),context=`failed ${subjectText(task,taskId)} \u2014 session teardown and activity/inbox cleanup ALREADY ran; the worktree directory and task entry remain. Reason: `,line=Error(`${context}${cause.message}`);line.name=cause.name;let frames=cause.stack?.split(`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{writeOutdatedCache}from"./chunk-nm6adny4.js";import{PluginCliError,installPlugin}from"./chunk-
|
|
2
|
+
import{writeOutdatedCache}from"./chunk-nm6adny4.js";import{PluginCliError,installPlugin}from"./chunk-8sbevss4.js";import{loadPluginRegistry,pluginCheckoutDir,pluginConfigDir,pluginDataDir,pluginStateDir,removePluginEntry,savePluginRegistry}from"./chunk-jyv82gf0.js";import"./chunk-eq5f3z2f.js";import"./chunk-kwe61yx7.js";import"./chunk-6dn6h640.js";import{activeCliName}from"./chunk-ef11h2am.js";import"./chunk-8ddjfc2v.js";import"./chunk-cb2fpajx.js";import"./chunk-3bfm4hrs.js";import"./chunk-wqfp8d9w.js";import{execFileSync}from"child_process";import{existsSync,mkdirSync,readdirSync,renameSync}from"fs";import{join}from"path";function gitOut(args,cwd){try{return execFileSync("git",args,{cwd,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}}function checkEntry(entry){let repoSpec=entry.source.spec.split("/").slice(0,2).join("/"),localSha=gitOut(["rev-parse","HEAD"],pluginCheckoutDir(entry.id)),remote=gitOut(["ls-remote",`https://github.com/${repoSpec}.git`,"HEAD"]),remoteSha=remote?remote.split(/\s+/)[0]??null:null;return{id:entry.id,spec:entry.source.spec,version:entry.version,localSha,remoteSha,behind:Boolean(localSha&&remoteSha&&localSha!==remoteSha)}}function listOutdated(){let rows=loadPluginRegistry().plugins.filter((p)=>p.source.kind==="github").map(checkEntry);return writeOutdatedCache(rows.filter((r)=>r.behind).map((r)=>r.id)),rows}function printOutdated(){let rows=listOutdated();if(rows.length===0){console.log("no GitHub-installed plugins.");return}for(let r of rows){let status=r.behind?"update available":r.remoteSha===null?"remote unreachable":r.localSha===null?"local sha unreadable":"up to date";console.log(`${r.id} v${r.version} ${status}`)}}function migrateRenamedPlugin(oldId,newId){for(let dirOf of[pluginConfigDir,pluginStateDir]){let from=dirOf(oldId),to=dirOf(newId);if(!existsSync(from))continue;mkdirSync(to,{recursive:!0});for(let entry of readdirSync(from)){if(existsSync(join(to,entry)))continue;renameSync(join(from,entry),join(to,entry))}}savePluginRegistry(removePluginEntry(loadPluginRegistry(),oldId)),console.log(`renamed ${oldId} \u2192 ${newId}: settings and state carried over, old entry unregistered`),console.log(` leftover checkout (safe to remove): ${pluginDataDir(oldId)}`)}async function updatePlugins(ids,opts){let rows=listOutdated(),targets;if(opts.all){if(targets=rows.filter((r)=>r.behind),targets.length===0){console.log("all plugins up to date.");return}}else{if(ids.length===0)throw new PluginCliError("update takes plugin ids or --all");targets=ids.map((id)=>{let row=rows.find((r)=>r.id===id);if(!row)throw new PluginCliError(`\`${id}\` is not a GitHub-installed plugin; see \`${activeCliName()} plugin list\``);return row})}for(let target of targets){if(!target.behind&&!ids.includes(target.id))continue;console.log(`updating ${target.id} (${target.spec})\u2026`);let installedId=await installPlugin(target.spec,{yes:opts.yes});if(installedId!==target.id)migrateRenamedPlugin(target.id,installedId)}listOutdated()}export{updatePlugins,printOutdated};
|