@automagik/genie 4.260427.3 → 4.260427.5

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/README.md CHANGED
@@ -25,7 +25,7 @@
25
25
  -->
26
26
 
27
27
  <!-- METRICS:START -->
28
- **🚀 121 commits** this week · **7 releases** · **+28.1K LoC** · **6 contributors**
28
+ **🚀 113 commits** this week · **3 releases** · **+12.5K LoC** · **5 contributors**
29
29
 
30
30
  ![Commits per day (30d, all branches)](.genie/assets/commits-30d.svg)
31
31
 
package/dist/genie.js CHANGED
@@ -236,15 +236,15 @@ ${readFileSync5(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(f
236
236
  `).slice(0,10);for(let line of lines){if(!line.includes("custom-title"))continue;let entry=JSON.parse(line);if(entry.type==="custom-title"&&entry.customTitle?.toLowerCase()===needle)return!0}}catch{}return!1}function sessionExists(name,cwd){try{let home=process.env.HOME??"/root",projectDir=ccProjectDirName(cwd??process.cwd()),projectPath=join9(home,".claude","projects",projectDir),files;try{files=readdirSync3(projectPath).filter((f)=>f.endsWith(".jsonl"))}catch{return!1}let needle=name.toLowerCase();return files.some((file)=>{let full=join9(projectPath,file);return fileHasSessionName(full,needle)||fileHasSessionName(full,`${needle}-${needle}`)})}catch{return!1}}var init_team_lead_command=__esm(()=>{init_claude_native_teams();init_genie_config2()});var exports_tmux_wrapper={};__export(exports_tmux_wrapper,{prependEnvVars:()=>prependEnvVars,genieTmuxPrefix:()=>genieTmuxPrefix,genieTmuxCmd:()=>genieTmuxCmd,executeTmux:()=>executeTmux});import{exec as execCallback}from"child_process";import{existsSync as existsSync7,mkdirSync as mkdirSync5}from"fs";import{homedir as homedir6}from"os";import{join as join10}from"path";import{promisify}from"util";function resolveGenieTmuxConf(){let home=homedir6(),genieHome3=process.env.GENIE_HOME??join10(home,".genie");return[join10(genieHome3,"tmux.conf"),join10(__dirname,"..","..","scripts","tmux","genie.tmux.conf"),join10(home,".bun","install","global","node_modules","@automagik","genie","scripts","tmux","genie.tmux.conf")].find((p)=>existsSync7(p))??"/dev/null"}function genieTmuxPrefix(){return["-L",GENIE_TMUX_SOCKET,"-f",resolveGenieTmuxConf()]}function genieTmuxCmd(subcommand){return`${tmuxBin()} ${genieTmuxPrefix().join(" ")} ${subcommand}`}function prependEnvVars(command,env){if(!env||Object.keys(env).length===0)return command;return`env ${Object.entries(env).map(([k,v])=>`${k}=${v}`).join(" ")} ${command}`}function getLogDir(){let logDir=join10(homedir6(),".genie","logs","tmux");if(!existsSync7(logDir))mkdirSync5(logDir,{recursive:!0});return logDir}function stripVerboseFlags(args){return args.filter((arg)=>!/^-v+$/.test(arg))}function isTmuxDebugEnabled(){return process.env.GENIE_TMUX_DEBUG==="1"}async function executeTmux(args){let argList=typeof args==="string"?args.split(/\s+/).filter(Boolean):args,finalArgs=stripVerboseFlags(argList),debugMode=isTmuxDebugEnabled(),options={};if(debugMode)finalArgs=["-v",...finalArgs],options.cwd=getLogDir();finalArgs=[...genieTmuxPrefix(),...finalArgs];let command=`${tmuxBin()} ${finalArgs.join(" ")}`,{stdout}=await exec(command,options);return stdout.trim()}var __dirname="/home/runner/_work/genie/genie/src/lib",exec,GENIE_TMUX_SOCKET;var init_tmux_wrapper=__esm(()=>{init_ensure_tmux();exec=promisify(execCallback),GENIE_TMUX_SOCKET=process.env.GENIE_TMUX_SOCKET||"genie"});var exports_tmux={};__export(exports_tmux,{setWindowEnv:()=>setWindowEnv,resolveRepoSession:()=>resolveRepoSession,listWindows:()=>listWindows,listPanes:()=>listPanes,killWindow:()=>killWindow,killSession:()=>killSession,isTmuxServerReachable:()=>isTmuxServerReachable,isPaneProcessRunning:()=>isPaneProcessRunning,isPaneAlive:()=>isPaneAlive,getWindowEnv:()=>getWindowEnv,getCurrentSessionName:()=>getCurrentSessionName,findWindowByName:()=>findWindowByName,findSessionByName:()=>findSessionByName,executeTmux:()=>executeTmux2,ensureTeamWindow:()=>ensureTeamWindow,createSession:()=>createSession,capturePaneContent:()=>capturePaneContent,applyPaneColor:()=>applyPaneColor,TmuxUnreachableError:()=>TmuxUnreachableError});import{existsSync as existsSync8}from"fs";import{basename as basename2,join as join11}from"path";async function executeTmux2(tmuxCommand){let{executeTmux:wrapperExec}=await Promise.resolve().then(() => (init_tmux_wrapper(),exports_tmux_wrapper));try{return await wrapperExec(tmuxCommand)}catch(error){let message=error instanceof Error?error.message:String(error);throw Error(`Failed to execute tmux command: ${message}`)}}async function getCurrentSessionName(hint){if(process.env.TMUX)try{return(await executeTmux2("display-message -p '#{session_name}'")).trim()||null}catch{return null}try{let sessions=await listSessions();if(sessions.length===0)return null;if(hint){let match=sessions.find((s)=>s.name.includes(hint));if(match)return match.name}return sessions[0].name}catch{return null}}async function listSessions(){try{let output=await executeTmux2("list-sessions -F '#{session_id}:#{session_name}:#{?session_attached,1,0}:#{session_windows}'");if(!output)return[];return output.split(`
237
237
  `).map((line)=>{let[id,name,attached,windows]=line.split(":");return{id,name,attached:attached==="1",windows:Number.parseInt(windows,10)}})}catch(error){if((error instanceof Error?error.message:String(error)).includes("no server running"))return[];throw error}}async function findSessionByName(name){try{return(await listSessions()).find((session)=>session.name===name)||null}catch(_error){return null}}async function getWindowEnv(target,varName){try{let output=await executeTmux2(`show-environment -t ${shellQuote(target)} ${shellQuote(varName)}`),prefix=`${varName}=`;if(output?.startsWith(prefix))return output.slice(prefix.length).trim();return null}catch{return null}}async function setWindowEnv(target,varName,value){await executeTmux2(`set-environment -t ${shellQuote(target)} ${shellQuote(varName)} ${shellQuote(value)}`)}async function killSession(sessionId){await executeTmux2(`kill-session -t '${sessionId}'`)}async function listWindows(sessionId){try{let output=await executeTmux2(`list-windows -t '=${sessionId}' -F '#{window_id}:#{window_name}:#{window_index}:#{?window_active,1,0}'`);if(!output)return[];return output.split(`
238
238
  `).map((line)=>{let[id,name,indexStr,active]=line.split(":");return{id,name,index:Number.parseInt(indexStr,10),active:active==="1",sessionId}})}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("session not found"))return[];throw error}}async function listPanes(windowId){try{let output=await executeTmux2(`list-panes -t '${windowId}' -F '#{pane_id}:#{pane_title}:#{?pane_active,1,0}'`);if(!output)return[];return output.split(`
239
- `).map((line)=>{let[id,title,active]=line.split(":");return{id,windowId,title,active:active==="1"}})}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("window not found"))return[];throw error}}async function capturePaneContent(paneId,lines=200,includeColors=!1){try{return await executeTmux2(`capture-pane -p ${includeColors?"-e":""} -t '${paneId}' -S -${lines} -E -`)}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("pane not found"))return"";throw error}}async function createSession(name){return await executeTmux2(`new-session -d -s "${name}" -e LC_ALL=C.UTF-8 -e LANG=C.UTF-8`),findSessionByName(name)}async function createWindow(sessionId,name,workingDir){let cdFlag=workingDir?` -c '${workingDir.replace(/'/g,"'\\''")}'`:"",output=await executeTmux2(`new-window -d -P -F '#{window_id}:#{window_index}' -t '${sessionId}:' -n '${name}'${cdFlag}`),[windowId,indexStr]=output.trim().split(":");if(!windowId)return null;try{await executeTmux2(`set-window-option -t '${windowId}' automatic-rename off`)}catch{}return{id:windowId,name,index:Number.parseInt(indexStr,10)||0,active:!1,sessionId}}async function findWindowByName(sessionId,name){return(await listWindows(sessionId)).find((w)=>w.name===name)||null}async function ensureMasterWindow(session,masterName){try{let windows=await listWindows(session);if(windows.length<2)return;let masterWindow=windows.find((w)=>w.name===masterName);if(!masterWindow)return;let minIndex=Math.min(...windows.map((w)=>w.index));if(masterWindow.index===minIndex)return;await executeTmux2(`swap-window -s '${session}:${masterWindow.index}' -t '${session}:${minIndex}'`)}catch{}}async function ensureSessionExists(name){try{await executeTmux2(`new-session -d -s "${name}" -e LC_ALL=C.UTF-8 -e LANG=C.UTF-8`)}catch(error){if((error instanceof Error?error.message:String(error)).includes("duplicate session"))return;throw error}}async function ensureTeamWindow(session,teamName,workingDir){for(let attempt=0;attempt<3;attempt++)try{return await ensureTeamWindowOnce(session,teamName,workingDir)}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("server exited")||message.includes("error connecting")){if(attempt<2){let delay=250*2**attempt;console.warn(`[genie-tmux] tmux server unreachable (attempt ${attempt+1}/3), retrying in ${delay}ms...`),await new Promise((resolve2)=>setTimeout(resolve2,delay));continue}}throw error}throw Error("Failed to ensure team window after 3 attempts")}async function ensureTeamWindowOnce(session,teamName,workingDir){await ensureSessionExists(session);let existing=await findWindowByName(session,teamName);if(existing){try{await executeTmux2(`set-window-option -t '${existing.id}' automatic-rename off`)}catch{}await rehydratePaneColorHook(existing.id);let panes2=await listPanes(existing.id),paneId2=panes2.length>0?panes2[0].id:`${session}:${teamName}.0`;return{windowId:existing.id,windowName:teamName,paneId:paneId2,created:!1}}let windowsBefore=await listWindows(session),masterBefore=windowsBefore.length>0?windowsBefore.reduce((a,b)=>a.index<=b.index?a:b):null,newWindow=await createWindow(session,teamName,workingDir);if(!newWindow)throw Error(`Failed to create team window "${teamName}" in session "${session}"`);if(masterBefore)await ensureMasterWindow(session,masterBefore.name);await rehydratePaneColorHook(newWindow.id);let panes=await listPanes(newWindow.id),paneId=panes.length>0?panes[0].id:`${session}:${teamName}.0`;return{windowId:newWindow.id,windowName:teamName,paneId,created:!0}}function ensurePaneColorScript(){let{existsSync:existsSync9,writeFileSync:writeFileSync6,mkdirSync:mkdirSync6,chmodSync:chmodSync2}=__require("fs"),{dirname:dirname4}=__require("path");if(existsSync9(PANE_COLOR_SCRIPT))return;mkdirSync6(dirname4(PANE_COLOR_SCRIPT),{recursive:!0});let bin=tmuxBin();writeFileSync6(PANE_COLOR_SCRIPT,`#!/bin/bash
239
+ `).map((line)=>{let[id,title,active]=line.split(":");return{id,windowId,title,active:active==="1"}})}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("window not found"))return[];throw error}}async function capturePaneContent(paneId,lines=200,includeColors=!1){try{return await executeTmux2(`capture-pane -p ${includeColors?"-e":""} -t '${paneId}' -S -${lines} -E -`)}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("pane not found"))return"";throw error}}async function createSession(name){return await executeTmux2(`new-session -d -s "${name}" -e LC_ALL=C.UTF-8 -e LANG=C.UTF-8`),findSessionByName(name)}async function createWindow(sessionId,name,workingDir){let cdFlag=workingDir?` -c '${workingDir.replace(/'/g,"'\\''")}'`:"",output=await executeTmux2(`new-window -d -P -F '#{window_id}:#{window_index}' -t '${sessionId}:' -n '${name}'${cdFlag}`),[windowId,indexStr]=output.trim().split(":");if(!windowId)return null;try{await executeTmux2(`set-window-option -t '${windowId}' automatic-rename off`)}catch{}return{id:windowId,name,index:Number.parseInt(indexStr,10)||0,active:!1,sessionId}}async function findWindowByName(sessionId,name){return(await listWindows(sessionId)).find((w)=>w.name===name)||null}async function ensureMasterWindow(session,masterName){try{let windows=await listWindows(session);if(windows.length<2)return;let masterWindow=windows.find((w)=>w.name===masterName);if(!masterWindow)return;let minIndex=Math.min(...windows.map((w)=>w.index));if(masterWindow.index===minIndex)return;await executeTmux2(`swap-window -s '${session}:${masterWindow.index}' -t '${session}:${minIndex}'`)}catch{}}async function ensureSessionExists(name){if(/^[@$]\d+$/.test(name))throw Error(`Refused to create tmux session with id-shaped name "${name}". Names matching /^[@$]\\d+$/ collide with tmux's window-id (@N) and session-id ($N) notation, producing ghost sessions that cannot be safely targeted. Pass a human-readable session name (e.g. the team or agent name) instead.`);try{await executeTmux2(`new-session -d -s "${name}" -e LC_ALL=C.UTF-8 -e LANG=C.UTF-8`)}catch(error){if((error instanceof Error?error.message:String(error)).includes("duplicate session"))return;throw error}}async function ensureTeamWindow(session,teamName,workingDir){for(let attempt=0;attempt<3;attempt++)try{return await ensureTeamWindowOnce(session,teamName,workingDir)}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("server exited")||message.includes("error connecting")){if(attempt<2){let delay=250*2**attempt;console.warn(`[genie-tmux] tmux server unreachable (attempt ${attempt+1}/3), retrying in ${delay}ms...`),await new Promise((resolve2)=>setTimeout(resolve2,delay));continue}}throw error}throw Error("Failed to ensure team window after 3 attempts")}async function ensureTeamWindowOnce(session,teamName,workingDir){await ensureSessionExists(session);let existing=await findWindowByName(session,teamName);if(existing){try{await executeTmux2(`set-window-option -t '${existing.id}' automatic-rename off`)}catch{}await rehydratePaneColorHook(existing.id);let panes2=await listPanes(existing.id),paneId2=panes2.length>0?panes2[0].id:`${session}:${teamName}.0`;return{windowId:existing.id,windowName:teamName,sessionName:session,paneId:paneId2,created:!1}}let windowsBefore=await listWindows(session),masterBefore=windowsBefore.length>0?windowsBefore.reduce((a,b)=>a.index<=b.index?a:b):null,newWindow=await createWindow(session,teamName,workingDir);if(!newWindow)throw Error(`Failed to create team window "${teamName}" in session "${session}"`);if(masterBefore)await ensureMasterWindow(session,masterBefore.name);await rehydratePaneColorHook(newWindow.id);let panes=await listPanes(newWindow.id),paneId=panes.length>0?panes[0].id:`${session}:${teamName}.0`;return{windowId:newWindow.id,windowName:teamName,sessionName:session,paneId,created:!0}}function ensurePaneColorScript(){let{existsSync:existsSync9,writeFileSync:writeFileSync6,mkdirSync:mkdirSync6,chmodSync:chmodSync2}=__require("fs"),{dirname:dirname4}=__require("path");if(existsSync9(PANE_COLOR_SCRIPT))return;mkdirSync6(dirname4(PANE_COLOR_SCRIPT),{recursive:!0});let bin=tmuxBin();writeFileSync6(PANE_COLOR_SCRIPT,`#!/bin/bash
240
240
  # Genie tmux pane color router \u2014 reads @genie_color pane option
241
241
  PANE_ID="$1"
242
242
  COLOR=$(${bin} display-message -p -t "$PANE_ID" '#{@genie_color}' 2>/dev/null)
243
243
  [ -z "$COLOR" ] && COLOR="default"
244
244
  ${bin} set-option -w pane-active-border-style "fg=$COLOR"
245
- `),chmodSync2(PANE_COLOR_SCRIPT,493)}async function applyPaneColor(paneId,color,windowId){let hex=TMUX_COLOR_MAP[color]??TMUX_COLOR_MAP.blue;try{ensurePaneColorScript(),await executeTmux2(`set-option -p -t '${paneId}' @genie_color '${hex}'`);try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));await(await getConnection2())`UPDATE executors SET pane_color = ${hex} WHERE tmux_pane_id = ${paneId}`}catch{}if(windowId)await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function rehydratePaneColorHook(windowId){try{ensurePaneColorScript(),await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function resolveRepoSession(repoPath){let derived=basename2(repoPath);try{let sessions=await listSessions(),exact=sessions.find((s)=>s.name===derived);if(exact)return exact.name;if(process.env.TMUX)try{let name=(await executeTmux2("display-message -p '#{session_name}'")).trim();if(name)return name}catch{}let partial=sessions.find((s)=>s.name.includes(derived));if(partial)return partial.name}catch{}return derived}function isTmuxSocketAlive(socketName){if(!socketName)return!1;let uid=process.getuid?.()??501;return existsSync8(join11(`/tmp/tmux-${uid}`,socketName))}async function isTmuxServerReachable(socketName){if(!socketName)return!1;if(!isTmuxSocketAlive(socketName))return!1;try{let{execSync:execSync2}=await import("child_process");return execSync2(`${tmuxBin()} -L ${shellQuote(socketName)} list-sessions -F ''`,{stdio:["ignore","ignore","ignore"],timeout:2000}),!0}catch{return!1}}async function isPaneAlive(paneId){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{return(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_dead}'`)).trim()==="0"}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("server exited")||message.includes("error connecting"))throw new TmuxUnreachableError(message);return!1}}async function isPaneProcessRunning(paneId,processName,execSyncFn){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{let panePid=(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_pid}'`)).trim();if(!panePid||!/^\d+$/.test(panePid))return!1;return(execSyncFn??(await import("child_process")).execSync)(`pgrep -la -P ${panePid} 2>/dev/null; for cpid in $(pgrep -P ${panePid} 2>/dev/null); do pgrep -la -P "$cpid" 2>/dev/null; done; true`,{encoding:"utf-8",timeout:5000}).toLowerCase().includes(processName.toLowerCase())}catch{return!1}}async function killWindow(sessionName,windowName){try{return await executeTmux2(`kill-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),!0}catch{return!1}}var TMUX_COLOR_NAMES,TMUX_COLOR_MAP,PANE_COLOR_SCRIPT,TmuxUnreachableError;var init_tmux=__esm(()=>{init_genie_tokens();init_ensure_tmux();init_team_lead_command();TMUX_COLOR_NAMES=["red","blue","green","yellow","purple","orange","pink","cyan"],TMUX_COLOR_MAP=Object.fromEntries(TMUX_COLOR_NAMES.map((name,i)=>[name,rotateHue(palette.accent,i*45)])),PANE_COLOR_SCRIPT=`${__require("os").homedir()}/.genie/tmux-pane-color.sh`;TmuxUnreachableError=class TmuxUnreachableError extends Error{constructor(message){super(message);this.name="TmuxUnreachableError"}}});var exports_agent_registry={};__export(exports_agent_registry,{update:()=>update,unregister:()=>unregister,setCurrentExecutor:()=>setCurrentExecutor,saveTemplate:()=>saveTemplate,removeSubPane:()=>removeSubPane,register:()=>register,reconcileStaleSpawns:()=>reconcileStaleSpawns,listTemplates:()=>listTemplates,listExhaustedZombies:()=>listExhaustedZombies,listAgents:()=>listAgents,list:()=>list,getTeamLeadEntry:()=>getTeamLeadEntry,getPane:()=>getPane,getElapsedTime:()=>getElapsedTime,getAgentEffectiveState:()=>getAgentEffectiveState,getAgentByName:()=>getAgentByName,getAgent:()=>getAgent,get:()=>get,findOrCreateAgent:()=>findOrCreateAgent,findByWindow:()=>findByWindow,findByTask:()=>findByTask,findByPane:()=>findByPane,filterBySession:()=>filterBySession,auditAgentKind:()=>auditAgentKind,archiveExhaustedZombies:()=>archiveExhaustedZombies,addSubPane:()=>addSubPane});import{createHash,randomUUID}from"crypto";function resolveWorkerSocketName(){return process.env.GENIE_TMUX_SOCKET||"genie"}function ts(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToAgent(r){let agent={id:r.id,paneId:r.pane_id,session:r.session,worktree:r.worktree??null,startedAt:ts(r.started_at),state:r.state,lastStateChange:ts(r.last_state_change),repoPath:r.repo_path};if(r.task_id!=null)agent.taskId=r.task_id;if(r.task_title!=null)agent.taskTitle=r.task_title;if(r.wish_slug!=null)agent.wishSlug=r.wish_slug;if(r.group_number!=null)agent.groupNumber=r.group_number;if(r.window_name!=null)agent.windowName=r.window_name;if(r.window_id!=null)agent.windowId=r.window_id;if(r.role!=null)agent.role=r.role;if(r.custom_name!=null)agent.customName=r.custom_name;if(r.sub_panes!=null){let sp=typeof r.sub_panes==="string"?JSON.parse(r.sub_panes):r.sub_panes;if(Array.isArray(sp)&&sp.length>0)agent.subPanes=sp}if(r.provider!=null)agent.provider=r.provider;if(r.transport!=null)agent.transport=r.transport;if(r.skill!=null)agent.skill=r.skill;if(r.team!=null)agent.team=r.team;if(r.tmux_window!=null)agent.window=r.tmux_window;if(r.native_agent_id!=null)agent.nativeAgentId=r.native_agent_id;if(r.native_color!=null)agent.nativeColor=r.native_color;if(r.native_team_enabled)agent.nativeTeamEnabled=r.native_team_enabled;if(r.parent_session_id!=null)agent.parentSessionId=r.parent_session_id;if(r.suspended_at!=null)agent.suspendedAt=ts(r.suspended_at);if(r.auto_resume!=null)agent.autoResume=r.auto_resume;if(r.resume_attempts!=null)agent.resumeAttempts=r.resume_attempts;if(r.last_resume_attempt!=null)agent.lastResumeAttempt=ts(r.last_resume_attempt);if(r.max_resume_attempts!=null)agent.maxResumeAttempts=r.max_resume_attempts;if(r.pane_color!=null)agent.paneColor=r.pane_color;if(agent.currentExecutorId=r.current_executor_id??null,agent.reportsTo=r.reports_to??null,agent.title=r.title??null,r.kind!=null)agent.kind=r.kind;return agent}function rowToTemplate(r){let tpl={id:r.id,provider:r.provider,team:r.team,cwd:r.cwd,lastSpawnedAt:ts(r.last_spawned_at)};if(r.role!=null)tpl.role=r.role;if(r.skill!=null)tpl.skill=r.skill;if(r.extra_args!=null){let ea=typeof r.extra_args==="string"?JSON.parse(r.extra_args):r.extra_args;if(Array.isArray(ea)&&ea.length>0)tpl.extraArgs=ea}if(r.native_team_enabled)tpl.nativeTeamEnabled=r.native_team_enabled;return tpl}function shortProjectHash(repoPath){return createHash("sha1").update(repoPath).digest("hex").slice(0,8)}function buildProjectTeamLeadEntryId(teamName,session,repoPath){return`team-lead:${session}:${shortProjectHash(repoPath)}:${teamName}`}function buildSessionTeamLeadEntryId(teamName,session){return`team-lead:${session}:${teamName}`}function buildLegacyTeamLeadEntryId(teamName){return`team-lead:${teamName}`}async function register(agent){let sql=await getConnection(),now=new Date().toISOString();if(agent.team){let existing=await sql`
245
+ `),chmodSync2(PANE_COLOR_SCRIPT,493)}async function applyPaneColor(paneId,color,windowId){let hex=TMUX_COLOR_MAP[color]??TMUX_COLOR_MAP.blue;try{ensurePaneColorScript(),await executeTmux2(`set-option -p -t '${paneId}' @genie_color '${hex}'`);try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));await(await getConnection2())`UPDATE executors SET pane_color = ${hex} WHERE tmux_pane_id = ${paneId}`}catch{}if(windowId)await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function rehydratePaneColorHook(windowId){try{ensurePaneColorScript(),await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function resolveRepoSession(repoPath){let derived=basename2(repoPath);try{let sessions=await listSessions(),exact=sessions.find((s)=>s.name===derived);if(exact)return exact.name;if(process.env.TMUX)try{let name=(await executeTmux2("display-message -p '#{session_name}'")).trim();if(name)return name}catch{}let partial=sessions.find((s)=>s.name.includes(derived));if(partial)return partial.name}catch{}return derived}function isTmuxSocketAlive(socketName){if(!socketName)return!1;let uid=process.getuid?.()??501;return existsSync8(join11(`/tmp/tmux-${uid}`,socketName))}async function isTmuxServerReachable(socketName){if(!socketName)return!1;if(!isTmuxSocketAlive(socketName))return!1;try{let{execSync:execSync2}=await import("child_process");return execSync2(`${tmuxBin()} -L ${shellQuote(socketName)} list-sessions -F ''`,{stdio:["ignore","ignore","ignore"],timeout:2000}),!0}catch{return!1}}async function isPaneAlive(paneId){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{return(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_dead}'`)).trim()==="0"}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("server exited")||message.includes("error connecting"))throw new TmuxUnreachableError(message);return!1}}async function isPaneProcessRunning(paneId,processName,execSyncFn){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{let panePid=(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_pid}'`)).trim();if(!panePid||!/^\d+$/.test(panePid))return!1;return(execSyncFn??(await import("child_process")).execSync)(`pgrep -la -P ${panePid} 2>/dev/null; for cpid in $(pgrep -P ${panePid} 2>/dev/null); do pgrep -la -P "$cpid" 2>/dev/null; done; true`,{encoding:"utf-8",timeout:5000}).toLowerCase().includes(processName.toLowerCase())}catch{return!1}}async function killWindow(sessionName,windowName){try{return await executeTmux2(`kill-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),!0}catch{return!1}}var TMUX_COLOR_NAMES,TMUX_COLOR_MAP,PANE_COLOR_SCRIPT,TmuxUnreachableError;var init_tmux=__esm(()=>{init_genie_tokens();init_ensure_tmux();init_team_lead_command();TMUX_COLOR_NAMES=["red","blue","green","yellow","purple","orange","pink","cyan"],TMUX_COLOR_MAP=Object.fromEntries(TMUX_COLOR_NAMES.map((name,i)=>[name,rotateHue(palette.accent,i*45)])),PANE_COLOR_SCRIPT=`${__require("os").homedir()}/.genie/tmux-pane-color.sh`;TmuxUnreachableError=class TmuxUnreachableError extends Error{constructor(message){super(message);this.name="TmuxUnreachableError"}}});var exports_agent_registry={};__export(exports_agent_registry,{update:()=>update,unregister:()=>unregister,setCurrentExecutor:()=>setCurrentExecutor,saveTemplate:()=>saveTemplate,removeSubPane:()=>removeSubPane,register:()=>register,reconcileStaleSpawns:()=>reconcileStaleSpawns,listTemplates:()=>listTemplates,listForRender:()=>listForRender,listExhaustedZombies:()=>listExhaustedZombies,listAgentsForRender:()=>listAgentsForRender,listAgents:()=>listAgents,list:()=>list,getTeamLeadEntry:()=>getTeamLeadEntry,getPane:()=>getPane,getElapsedTime:()=>getElapsedTime,getAgentEffectiveState:()=>getAgentEffectiveState,getAgentByName:()=>getAgentByName,getAgent:()=>getAgent,get:()=>get,findOrCreateAgent:()=>findOrCreateAgent,findByWindow:()=>findByWindow,findByTask:()=>findByTask,findByPane:()=>findByPane,filterBySession:()=>filterBySession,auditAgentKind:()=>auditAgentKind,archiveExhaustedZombies:()=>archiveExhaustedZombies,addSubPane:()=>addSubPane});import{createHash,randomUUID}from"crypto";function resolveWorkerSocketName(){return process.env.GENIE_TMUX_SOCKET||"genie"}function ts(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToAgent(r){let agent={id:r.id,paneId:r.pane_id,session:r.session,worktree:r.worktree??null,startedAt:ts(r.started_at),state:r.state,lastStateChange:ts(r.last_state_change),repoPath:r.repo_path};if(r.task_id!=null)agent.taskId=r.task_id;if(r.task_title!=null)agent.taskTitle=r.task_title;if(r.wish_slug!=null)agent.wishSlug=r.wish_slug;if(r.group_number!=null)agent.groupNumber=r.group_number;if(r.window_name!=null)agent.windowName=r.window_name;if(r.window_id!=null)agent.windowId=r.window_id;if(r.role!=null)agent.role=r.role;if(r.custom_name!=null)agent.customName=r.custom_name;if(r.sub_panes!=null){let sp=typeof r.sub_panes==="string"?JSON.parse(r.sub_panes):r.sub_panes;if(Array.isArray(sp)&&sp.length>0)agent.subPanes=sp}if(r.provider!=null)agent.provider=r.provider;if(r.transport!=null)agent.transport=r.transport;if(r.skill!=null)agent.skill=r.skill;if(r.team!=null)agent.team=r.team;if(r.tmux_window!=null)agent.window=r.tmux_window;if(r.native_agent_id!=null)agent.nativeAgentId=r.native_agent_id;if(r.native_color!=null)agent.nativeColor=r.native_color;if(r.native_team_enabled)agent.nativeTeamEnabled=r.native_team_enabled;if(r.parent_session_id!=null)agent.parentSessionId=r.parent_session_id;if(r.suspended_at!=null)agent.suspendedAt=ts(r.suspended_at);if(r.auto_resume!=null)agent.autoResume=r.auto_resume;if(r.resume_attempts!=null)agent.resumeAttempts=r.resume_attempts;if(r.last_resume_attempt!=null)agent.lastResumeAttempt=ts(r.last_resume_attempt);if(r.max_resume_attempts!=null)agent.maxResumeAttempts=r.max_resume_attempts;if(r.pane_color!=null)agent.paneColor=r.pane_color;if(agent.currentExecutorId=r.current_executor_id??null,agent.reportsTo=r.reports_to??null,agent.title=r.title??null,r.kind!=null)agent.kind=r.kind;return agent}function rowToTemplate(r){let tpl={id:r.id,provider:r.provider,team:r.team,cwd:r.cwd,lastSpawnedAt:ts(r.last_spawned_at)};if(r.role!=null)tpl.role=r.role;if(r.skill!=null)tpl.skill=r.skill;if(r.extra_args!=null){let ea=typeof r.extra_args==="string"?JSON.parse(r.extra_args):r.extra_args;if(Array.isArray(ea)&&ea.length>0)tpl.extraArgs=ea}if(r.native_team_enabled)tpl.nativeTeamEnabled=r.native_team_enabled;return tpl}function shortProjectHash(repoPath){return createHash("sha1").update(repoPath).digest("hex").slice(0,8)}function buildProjectTeamLeadEntryId(teamName,session,repoPath){return`team-lead:${session}:${shortProjectHash(repoPath)}:${teamName}`}function buildSessionTeamLeadEntryId(teamName,session){return`team-lead:${session}:${teamName}`}function buildLegacyTeamLeadEntryId(teamName){return`team-lead:${teamName}`}async function register(agent){let sql=await getConnection(),now=new Date().toISOString();if(agent.team){let existing=await sql`
246
246
  SELECT team FROM agents WHERE id = ${agent.id} LIMIT 1
247
- `;if(existing.length>0&&existing[0].team!==null&&existing[0].team!==agent.team)throw Error(`register: cross-team id collision \u2014 agent id "${agent.id}" already exists in team "${existing[0].team}", refusing to re-register under team "${agent.team}". Unregister the stale row first, or pick a different id/suffix for this spawn.`)}await sql`INSERT INTO agents (id, pane_id, session, worktree, task_id, task_title, wish_slug, group_number, started_at, state, last_state_change, repo_path, window_name, window_id, role, custom_name, sub_panes, provider, transport, skill, team, tmux_window, native_agent_id, native_color, native_team_enabled, parent_session_id, suspended_at, auto_resume, resume_attempts, last_resume_attempt, max_resume_attempts, pane_color) VALUES (${agent.id}, ${agent.paneId}, ${agent.session}, ${agent.worktree??null}, ${agent.taskId??null}, ${agent.taskTitle??null}, ${agent.wishSlug??null}, ${agent.groupNumber??null}, ${agent.startedAt??now}, ${agent.state??"spawning"}, ${agent.lastStateChange??now}, ${agent.repoPath}, ${agent.windowName??null}, ${agent.windowId??null}, ${agent.role??null}, ${agent.customName??null}, ${sql.json(agent.subPanes??[])}, ${agent.provider??null}, ${agent.transport??"tmux"}, ${agent.skill??null}, ${agent.team??null}, ${agent.window??null}, ${agent.nativeAgentId??null}, ${agent.nativeColor??null}, ${agent.nativeTeamEnabled??!1}, ${agent.parentSessionId??null}, ${agent.suspendedAt??null}, ${agent.autoResume??!0}, ${agent.resumeAttempts??0}, ${agent.lastResumeAttempt??null}, ${agent.maxResumeAttempts??3}, ${agent.paneColor??null}) ON CONFLICT (id) DO UPDATE SET pane_id = EXCLUDED.pane_id, session = EXCLUDED.session, state = EXCLUDED.state, last_state_change = EXCLUDED.last_state_change, team = COALESCE(agents.team, EXCLUDED.team), role = COALESCE(agents.role, EXCLUDED.role), custom_name = COALESCE(agents.custom_name, EXCLUDED.custom_name), updated_at = now()`}async function unregister(id){await(await getConnection())`DELETE FROM agents WHERE id = ${id}`}async function get(id){let rows=await(await getConnection())`SELECT * FROM agents WHERE id = ${id}`;return rows.length>0?rowToAgent(rows[0]):null}async function list(){return(await(await getConnection())`SELECT * FROM agents`).map(rowToAgent)}async function reconcileStaleSpawns(thresholdSeconds=60){try{let sql=await getConnection(),rows=await sql`
247
+ `;if(existing.length>0&&existing[0].team!==null&&existing[0].team!==agent.team)throw Error(`register: cross-team id collision \u2014 agent id "${agent.id}" already exists in team "${existing[0].team}", refusing to re-register under team "${agent.team}". Unregister the stale row first, or pick a different id/suffix for this spawn.`)}await sql`INSERT INTO agents (id, pane_id, session, worktree, task_id, task_title, wish_slug, group_number, started_at, state, last_state_change, repo_path, window_name, window_id, role, custom_name, sub_panes, provider, transport, skill, team, tmux_window, native_agent_id, native_color, native_team_enabled, parent_session_id, suspended_at, auto_resume, resume_attempts, last_resume_attempt, max_resume_attempts, pane_color) VALUES (${agent.id}, ${agent.paneId}, ${agent.session}, ${agent.worktree??null}, ${agent.taskId??null}, ${agent.taskTitle??null}, ${agent.wishSlug??null}, ${agent.groupNumber??null}, ${agent.startedAt??now}, ${agent.state??"spawning"}, ${agent.lastStateChange??now}, ${agent.repoPath}, ${agent.windowName??null}, ${agent.windowId??null}, ${agent.role??null}, ${agent.customName??null}, ${sql.json(agent.subPanes??[])}, ${agent.provider??null}, ${agent.transport??"tmux"}, ${agent.skill??null}, ${agent.team??null}, ${agent.window??null}, ${agent.nativeAgentId??null}, ${agent.nativeColor??null}, ${agent.nativeTeamEnabled??!1}, ${agent.parentSessionId??null}, ${agent.suspendedAt??null}, ${agent.autoResume??!0}, ${agent.resumeAttempts??0}, ${agent.lastResumeAttempt??null}, ${agent.maxResumeAttempts??3}, ${agent.paneColor??null}) ON CONFLICT (id) DO UPDATE SET pane_id = EXCLUDED.pane_id, session = EXCLUDED.session, state = EXCLUDED.state, last_state_change = EXCLUDED.last_state_change, team = COALESCE(agents.team, EXCLUDED.team), role = COALESCE(agents.role, EXCLUDED.role), custom_name = COALESCE(agents.custom_name, EXCLUDED.custom_name), updated_at = now()`}async function unregister(id){await(await getConnection())`DELETE FROM agents WHERE id = ${id}`}async function get(id){let rows=await(await getConnection())`SELECT * FROM agents WHERE id = ${id}`;return rows.length>0?rowToAgent(rows[0]):null}async function list(){return(await(await getConnection())`SELECT * FROM agents`).map(rowToAgent)}function dedupeShadowRows(rows,opts){let groups=new Map;for(let r of rows){let k=opts.keyFor(r),arr=groups.get(k);if(arr)arr.push(r);else groups.set(k,[r])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b)=>{let aExec=opts.hasExecutor(a)?1:0,bExec=opts.hasExecutor(b)?1:0;if(aExec!==bExec)return bExec-aExec;return opts.startedAt(b).localeCompare(opts.startedAt(a))}),out.push(arr[0])}return out}function shadowKey(customName,id,team){return`${customName??id}\x00${team??""}`}async function listForRender(){let all=await list();return dedupeShadowRows(all,{keyFor:(a)=>shadowKey(a.customName,a.id,a.team),hasExecutor:(a)=>a.currentExecutorId!=null,startedAt:(a)=>a.startedAt})}async function reconcileStaleSpawns(thresholdSeconds=60){try{let sql=await getConnection(),rows=await sql`
248
248
  UPDATE agents
249
249
  SET state = 'error', last_state_change = now()
250
250
  WHERE state = 'spawning'
@@ -358,7 +358,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
358
358
  native_team_enabled, parent_session_id, current_executor_id, reports_to, title, kind, created_at, updated_at
359
359
  FROM agents
360
360
  WHERE (${includeArchived} OR state IS DISTINCT FROM 'archived')
361
- `;return rows.map(rowToAgentIdentity)}async function auditAgentKind(){let rows=await(await getConnection())`
361
+ `;return rows.map(rowToAgentIdentity)}async function listAgentsForRender(filters){let all=await listAgents(filters);return dedupeShadowRows(all,{keyFor:(a)=>shadowKey(a.customName,a.id,a.team),hasExecutor:(a)=>a.currentExecutorId!=null,startedAt:(a)=>a.startedAt})}async function auditAgentKind(){let rows=await(await getConnection())`
362
362
  SELECT id, kind, reports_to FROM agents
363
363
  `,drifted=[];for(let row of rows){let expected=row.id.startsWith("dir:")||row.reports_to===null?"permanent":"task";if(row.kind!==expected)drifted.push({id:row.id,kind:row.kind,expected})}for(let drift of drifted)recordAuditEvent("worker",drift.id,"agents.kind.audit_drift","auditor",{stored:drift.kind,expected:drift.expected}).catch(()=>{});return{total:rows.length,drifted}}var DEAD_PANE_ZOMBIE_TTL_HOURS=24;var init_agent_registry=__esm(()=>{init_audit();init_db();init_tmux()});var exports_js_yaml={};__export(exports_js_yaml,{types:()=>types2,safeLoadAll:()=>safeLoadAll,safeLoad:()=>safeLoad,safeDump:()=>safeDump,loadAll:()=>loadAll,load:()=>load,dump:()=>dump,default:()=>jsYaml,YAMLException:()=>YAMLException,Type:()=>Type,Schema:()=>Schema,JSON_SCHEMA:()=>JSON_SCHEMA,FAILSAFE_SCHEMA:()=>FAILSAFE_SCHEMA,DEFAULT_SCHEMA:()=>DEFAULT_SCHEMA,CORE_SCHEMA:()=>CORE_SCHEMA});function isNothing(subject){return typeof subject>"u"||subject===null}function isObject(subject){return typeof subject==="object"&&subject!==null}function toArray(sequence){if(Array.isArray(sequence))return sequence;else if(isNothing(sequence))return[];return[sequence]}function extend(target,source){var index,length,key,sourceKeys;if(source){sourceKeys=Object.keys(source);for(index=0,length=sourceKeys.length;index<length;index+=1)key=sourceKeys[index],target[key]=source[key]}return target}function repeat(string,count){var result2="",cycle;for(cycle=0;cycle<count;cycle+=1)result2+=string;return result2}function isNegativeZero(number){return number===0&&Number.NEGATIVE_INFINITY===1/number}function formatError(exception,compact){var where="",message=exception.reason||"(unknown reason)";if(!exception.mark)return message;if(exception.mark.name)where+='in "'+exception.mark.name+'" ';if(where+="("+(exception.mark.line+1)+":"+(exception.mark.column+1)+")",!compact&&exception.mark.snippet)where+=`
364
364
 
@@ -1666,7 +1666,7 @@ ${otherList}
1666
1666
  `,await sql`
1667
1667
  DELETE FROM task_actors WHERE task_id = ANY(${ids}) AND role = 'assignee'
1668
1668
  `,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema,WishStateMismatchError;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()});WishStateMismatchError=class WishStateMismatchError extends Error{slug;added;removed;changed;constructor(slug,added,removed,changed){let lines=[`Wish "${slug}" group structure has changed since state was created.`];if(added.length>0)lines.push(` + added: ${added.join(", ")}`);if(removed.length>0)lines.push(` - removed: ${removed.join(", ")}`);if(changed.length>0)lines.push(` ~ changed deps: ${changed.join(", ")}`);lines.push(""),lines.push(`Run \`genie reset ${slug}\` to recreate state from the current WISH.md.`);super(lines.join(`
1669
- `));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps2});import{exec as exec2}from"child_process";import{readFile as readFile9}from"fs/promises";import{join as join40}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let leaderName=await resolveLeaderName(team),sanitized=sanitizeTeamName(team),leaderAgent=await getAgentByName(leaderName,sanitized).catch(()=>null);if(leaderAgent){let decision=await shouldResume(leaderAgent.id).catch(()=>null);if(decision?.sessionId)return decision.sessionId}return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps2.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),session=(await getTeam(team))?.tmuxSessionName??await resolveRepoSession(repoPath)??await getCurrentSessionName()??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude)await registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId);if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),await tryAutoBrain(workerId,repoPath),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await _deps2.findAnyGroupByAssignee(workerId,repoPath)??await _deps2.findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join40(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile9(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
1669
+ `));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps2});import{exec as exec2}from"child_process";import{readFile as readFile9}from"fs/promises";import{join as join40}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let leaderName=await resolveLeaderName(team),sanitized=sanitizeTeamName(team),leaderAgent=await getAgentByName(leaderName,sanitized).catch(()=>null);if(leaderAgent){let decision=await shouldResume(leaderAgent.id).catch(()=>null);if(decision?.sessionId)return decision.sessionId}return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps2.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),teamConfig=await getTeam(team),session=(Boolean(process.env.TMUX)?await getCurrentSessionName():null)??teamConfig?.tmuxSessionName??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude)await registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId);if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),await tryAutoBrain(workerId,repoPath),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await _deps2.findAnyGroupByAssignee(workerId,repoPath)??await _deps2.findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join40(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile9(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
1670
1670
  ${groupSection}`:"","",gitLog?`Last git log:
1671
1671
  ${gitLog}`:"","",gitStatus?`Uncommitted changes:
1672
1672
  ${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
@@ -1962,7 +1962,7 @@ process.on('SIGTERM', () => { server.close(); process.exit(0); });
1962
1962
  process.on('SIGINT', () => { server.close(); process.exit(0); });
1963
1963
  `,{mode:420});let{spawn:spawnChild}=__require("child_process");spawnChild("node",[scriptFile],{detached:!0,stdio:"ignore"}).unref();for(let i2=0;i2<30;i2++)if(await new Promise((r)=>setTimeout(r,100)),isRelayAlive(pidFile))return!0;return!1}catch{return!1}}async function generateWorkerId2(team,role){let base=role?`${team}-${role}`:team;if(!(await list()).some((w)=>w.id===base))return base;let suffix=crypto.randomUUID().slice(0,8);return`${base}-${suffix}`}async function capturePanePid2(paneId){if(paneId==="inline")return null;try{let{execSync:execSync10}=__require("child_process"),output=execSync10(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),pid=Number.parseInt(output,10);return pid>0?pid:null}catch{return null}}function resolveExecutorTransport2(provider,spawnTransport){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return spawnTransport==="inline"?"process":"tmux"}async function terminateActiveExecutorWithCleanup(agentIdentityId){try{let currentExec=await getCurrentExecutor(agentIdentityId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let provider=getProvider(currentExec.provider);if(provider)try{await provider.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentityId)}catch{}}async function createAndLinkExecutor2(agentIdentityId,provider,transport,opts){try{let executor=await createExecutor(agentIdentityId,provider,transport,opts);return await setCurrentExecutor(agentIdentityId,executor.id),executor.id}catch{return null}}async function registerSpawnWorker(ctx,paneId,windowInfo){let nt=ctx.validated.nativeTeam,workerEntry={id:ctx.workerId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role,skill:ctx.validated.skill,team:ctx.validated.team,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,nativeTeamEnabled:nt?.enabled??!1,nativeAgentId:`${ctx.agentName}@${ctx.validated.team}`,nativeColor:nt?.color??ctx.spawnColor,parentSessionId:nt?.parentSessionId??ctx.parentSessionId,window:windowInfo?.windowName,windowName:windowInfo?.windowName,windowId:windowInfo?.windowId,autoResume:ctx.autoResume===!1?!1:void 0,resumeAttempts:0};await register(workerEntry);let role=ctx.validated.role??ctx.agentName;if(role!=="council")try{await hireAgent(ctx.validated.team,role)}catch{}return workerEntry}async function notifySpawnJoin(ctx,paneId){let nt=ctx.validated.nativeTeam;if(!nt?.enabled)return;await registerNativeMember(ctx.validated.team,{agentName:ctx.agentName,agentType:nt.agentType??ctx.validated.role??"general-purpose",color:nt.color??ctx.spawnColor??"blue",tmuxPaneId:paneId,cwd:ctx.cwd,planModeRequired:nt.planModeRequired});let leaderName=await resolveTeamLeaderName(ctx.validated.team);await writeNativeInbox(ctx.validated.team,leaderName,{from:ctx.agentName,text:`Worker ${ctx.agentName} (${ctx.validated.provider}) joined team ${ctx.validated.team}. cwd: ${ctx.cwd}. Ready for tasks.`,summary:`${ctx.agentName} (${ctx.validated.provider}) joined`,timestamp:new Date().toISOString(),color:nt.color??ctx.spawnColor??"blue",read:!1})}function registerOtelRelayPane(workerId,paneId,agentName,spawnColor,repoPath){let{writeFileSync:wfs}=__require("fs"),{join:pjoin}=__require("path"),{homedir:hdir}=__require("os"),rd=pjoin(hdir(),".genie","relay");wfs(pjoin(rd,`${workerId}-pane`),paneId),wfs(pjoin(rd,`${workerId}-meta`),JSON.stringify({agent:agentName,color:spawnColor,repoPath}))}function printSpawnInfo(ctx,paneId,workerEntry){let nt=ctx.validated.nativeTeam;if(console.log(`Agent "${ctx.workerId}" spawned.`),console.log(` Provider: ${ctx.launch.provider}`),console.log(` Command: ${ctx.fullCommand}`),console.log(` Team: ${ctx.validated.team}`),console.log(` Pane: ${paneId}`),ctx.validated.role)console.log(` Role: ${ctx.validated.role}`);if(ctx.executorId)console.log(` Executor: ${ctx.executorId}`);if(ctx.validated.skill)console.log(` Skill: ${ctx.validated.skill}`);if(ctx.claudeSessionId)console.log(` Session: ${ctx.claudeSessionId}`);if(console.log(` Layout: ${ctx.layoutMode}`),nt?.enabled)console.log(" Native: enabled"),console.log(` AgentID: ${workerEntry.nativeAgentId}`),console.log(` Color: ${nt.color}`);if(ctx.otelRelayActive)console.log(` OTel: relay on port ${OTEL_RELAY_PORT}`)}function shellQuote2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function writeTmuxLaunchScript(workerId,fullCommand){let{chmodSync:chmodSync3,mkdirSync:mkdirSync14,writeFileSync:writeFileSync13}=__require("fs"),{join:join41}=__require("path"),{homedir:homedir27}=__require("os"),dir=join41(homedir27(),".genie","spawn-scripts");mkdirSync14(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join41(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync13(scriptPath,`#!/bin/sh
1964
1964
  exec ${fullCommand}
1965
- `,{mode:448}),chmodSync3(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync10}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync10(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync10(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync10}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(ctx.validated.windowTarget){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",cmd=`${tmuxPrefix}split-window -d -t ${shellQuote2(ctx.validated.windowTarget)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.windowId?.split(":")[0]??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync10(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync10(`${tmuxPrefix}new-session -d -s ${shellQuote2(session)} -n home${cwdFlag2}`,{stdio:"ignore"});let cmd=`${tmuxPrefix}new-window -a -d -t ${shellQuote2(`${session}:`)} -n claude${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync10(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync10(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let callerPane=process.env.TMUX_PANE;if(!teamWindow&&!callerPane)throw Error("createTmuxPane: refusing to split with no target \u2014 neither teamWindow nor TMUX_PANE is set. "+"This indicates a missing --team or --window flag, or a caller outside tmux. See ~/.genie/reports/trace-genie-spawn-wrong-window.md");let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:`-t '${callerPane}'`,cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"";if(useLaunchScript){let splitCmd2=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync10(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync10}=__require("child_process"),session=await getCurrentSessionName()??ctx.validated.team,layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{execSync10(genieTmuxCmd(buildLayoutCommand(layoutTarget,ctx.layoutMode)),{stdio:"ignore"})}catch{}}async function createTmuxExecutor(ctx,paneId,pid,teamWindow){if(!ctx.agentIdentityId||!ctx.executorId)return;await createAndLinkExecutor2(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport2(ctx.validated.provider,"tmux"),{id:ctx.executorId,pid,tmuxSession:ctx.validated.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd,paneColor:ctx.spawnColor})}async function finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry){if(ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(await saveTemplate({id:ctx.validated.role??ctx.workerId,provider:ctx.validated.provider,team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,extraArgs:ctx.extraArgs,nativeTeamEnabled:workerEntry.nativeTeamEnabled,lastSpawnedAt:new Date().toISOString()}),ctx.otelRelayActive&&paneId!=="%0")registerOtelRelayPane(ctx.workerId,paneId,ctx.agentName,ctx.spawnColor,ctx.cwd);if(teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`);printSpawnInfo(ctx,paneId,workerEntry)}async function awaitAgentReadiness(paneId){if(paneId==="inline")return;let result2=await waitForAgentReady(paneId);if(result2.ready)console.log(` \u2713 Agent ready (${(result2.elapsedMs/1000).toFixed(1)}s)`);else console.log(` \u26A0 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 proceeding anyway`)}async function launchTmuxSpawn(ctx){let isolatedSessionSpawn=ctx.validated.newWindow===!0&&Boolean(ctx.sessionOverride),teamWindow=ctx.spawnIntoCurrentWindow||isolatedSessionSpawn?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){return console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}let pid=await capturePanePid2(paneId);if(await createTmuxExecutor(ctx,paneId,pid,teamWindow),await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);if(await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry),await awaitAgentReadiness(paneId),ctx.executorId)await updateExecutorState(ctx.executorId,"running").catch(()=>{});return await update(ctx.workerId,{state:"idle"}).catch(()=>{}),paneId}async function runSdkQuery(ctx,permConfig,streamOpts,sdkConfig,runtimeExtraOptions){let{ClaudeSdkProvider:ClaudeSdkProvider2}=await Promise.resolve().then(() => (init_claude_sdk(),exports_claude_sdk)),{startSession:startSession2,recordTurn:recordTurn2,updateTurnCount:updateTurnCount2,endSession:endSession2}=await Promise.resolve().then(() => exports_sdk_session_capture),{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sdkProvider=new ClaudeSdkProvider2,spawnContext={agentId:ctx.agentIdentityId??ctx.workerId,executorId:ctx.executorId??crypto.randomUUID(),team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,model:ctx.validated.model,systemPrompt:ctx.validated.systemPrompt,systemPromptFile:ctx.validated.systemPromptFile,initialPrompt:ctx.validated.initialPrompt,name:ctx.validated.name},safePgCall=async(_op,fn,fallback)=>{try{let sql=await getConnection2();return await fn(sql)}catch{return fallback}},prompt2=ctx.validated.initialPrompt??`You are ${ctx.validated.role??"an agent"} on team "${ctx.validated.team}". Awaiting instructions.`,resumeSessionId=typeof runtimeExtraOptions?.resume==="string"?runtimeExtraOptions.resume:void 0,dbSessionId=null,turnIndex=0;if(resumeSessionId){let resolvedClaudeSessionId=resumeSessionId,byPgId=await safePgCall("resolve-session-resume",(sql)=>sql`
1965
+ `,{mode:448}),chmodSync3(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync10}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync10(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync10(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync10}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(ctx.validated.windowTarget){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",cmd=`${tmuxPrefix}split-window -d -t ${shellQuote2(ctx.validated.windowTarget)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.sessionName??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync10(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync10(`${tmuxPrefix}new-session -d -s ${shellQuote2(session)} -n home${cwdFlag2}`,{stdio:"ignore"});let cmd=`${tmuxPrefix}new-window -a -d -t ${shellQuote2(`${session}:`)} -n claude${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync10(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync10(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let callerPane=process.env.TMUX_PANE;if(!teamWindow&&!callerPane)throw Error("createTmuxPane: refusing to split with no target \u2014 neither teamWindow nor TMUX_PANE is set. "+"This indicates a missing --team or --window flag, or a caller outside tmux. See ~/.genie/reports/trace-genie-spawn-wrong-window.md");let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:`-t '${callerPane}'`,cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"";if(useLaunchScript){let splitCmd2=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync10(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync10(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync10}=__require("child_process"),session=await getCurrentSessionName()??ctx.validated.team,layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{execSync10(genieTmuxCmd(buildLayoutCommand(layoutTarget,ctx.layoutMode)),{stdio:"ignore"})}catch{}}async function createTmuxExecutor(ctx,paneId,pid,teamWindow){if(!ctx.agentIdentityId||!ctx.executorId)return;await createAndLinkExecutor2(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport2(ctx.validated.provider,"tmux"),{id:ctx.executorId,pid,tmuxSession:ctx.validated.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd,paneColor:ctx.spawnColor})}async function finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry){if(ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(await saveTemplate({id:ctx.validated.role??ctx.workerId,provider:ctx.validated.provider,team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,extraArgs:ctx.extraArgs,nativeTeamEnabled:workerEntry.nativeTeamEnabled,lastSpawnedAt:new Date().toISOString()}),ctx.otelRelayActive&&paneId!=="%0")registerOtelRelayPane(ctx.workerId,paneId,ctx.agentName,ctx.spawnColor,ctx.cwd);if(teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`);printSpawnInfo(ctx,paneId,workerEntry)}async function awaitAgentReadiness(paneId){if(paneId==="inline")return;let result2=await waitForAgentReady(paneId);if(result2.ready)console.log(` \u2713 Agent ready (${(result2.elapsedMs/1000).toFixed(1)}s)`);else console.log(` \u26A0 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 proceeding anyway`)}async function launchTmuxSpawn(ctx){let isolatedSessionSpawn=ctx.validated.newWindow===!0&&Boolean(ctx.sessionOverride),teamWindow=ctx.spawnIntoCurrentWindow||isolatedSessionSpawn?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){return console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}let pid=await capturePanePid2(paneId);if(await createTmuxExecutor(ctx,paneId,pid,teamWindow),await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);if(await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry),await awaitAgentReadiness(paneId),ctx.executorId)await updateExecutorState(ctx.executorId,"running").catch(()=>{});return await update(ctx.workerId,{state:"idle"}).catch(()=>{}),paneId}async function runSdkQuery(ctx,permConfig,streamOpts,sdkConfig,runtimeExtraOptions){let{ClaudeSdkProvider:ClaudeSdkProvider2}=await Promise.resolve().then(() => (init_claude_sdk(),exports_claude_sdk)),{startSession:startSession2,recordTurn:recordTurn2,updateTurnCount:updateTurnCount2,endSession:endSession2}=await Promise.resolve().then(() => exports_sdk_session_capture),{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sdkProvider=new ClaudeSdkProvider2,spawnContext={agentId:ctx.agentIdentityId??ctx.workerId,executorId:ctx.executorId??crypto.randomUUID(),team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,model:ctx.validated.model,systemPrompt:ctx.validated.systemPrompt,systemPromptFile:ctx.validated.systemPromptFile,initialPrompt:ctx.validated.initialPrompt,name:ctx.validated.name},safePgCall=async(_op,fn,fallback)=>{try{let sql=await getConnection2();return await fn(sql)}catch{return fallback}},prompt2=ctx.validated.initialPrompt??`You are ${ctx.validated.role??"an agent"} on team "${ctx.validated.team}". Awaiting instructions.`,resumeSessionId=typeof runtimeExtraOptions?.resume==="string"?runtimeExtraOptions.resume:void 0,dbSessionId=null,turnIndex=0;if(resumeSessionId){let resolvedClaudeSessionId=resumeSessionId,byPgId=await safePgCall("resolve-session-resume",(sql)=>sql`
1966
1966
  SELECT s.id, s.total_turns, COALESCE(s.claude_session_id, e.claude_session_id) as csid
1967
1967
  FROM sessions s
1968
1968
  LEFT JOIN executors e ON e.id = s.executor_id
@@ -2001,7 +2001,7 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
2001
2001
  LIMIT 1
2002
2002
  `;if(anchor.length>0)await relinkExecutorToAgent(anchor[0].id,agentId),createdAnchor=!0,decision=await shouldResume(agentId)}}}return await recordAuditEvent("agent",agentId,"recover.surgery_applied",getActor(),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}).catch(()=>{}),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}}async function jsonlHeadMatchesCustomName(filePath,customName){let{open:open4}=await import("fs/promises"),handle=null;try{handle=await open4(filePath,"r");let buffer2=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer2,0,buffer2.length,0),head=buffer2.toString("utf-8",0,bytesRead);for(let line of head.split(`
2003
2003
  `).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry2=JSON.parse(trimmed);if(typeof entry2.agentName==="string"&&entry2.agentName===customName)return!0}catch{}}return!1}catch{return!1}finally{await handle?.close().catch(()=>{})}}async function resolveClaudeProjectDir(cwd){let{join:join41}=await import("path"),{homedir:homedir27}=await import("os"),sanitize=(p)=>p.replace(/[^a-zA-Z0-9]/g,"-"),claudeConfigDir5=process.env.CLAUDE_CONFIG_DIR??join41(homedir27(),".claude");return join41(claudeConfigDir5,"projects",sanitize(cwd))}async function listJsonlsByMtime(projectDir){let{readdir:readdir6,stat:stat5}=await import("fs/promises"),{join:join41}=await import("path"),entries;try{entries=await readdir6(projectDir)}catch{return[]}let jsonls=entries.filter((e)=>e.endsWith(".jsonl")),candidates=[];for(let name of jsonls){let full=join41(projectDir,name);try{let s=await stat5(full);candidates.push({name,full,mtime:s.mtimeMs})}catch{}}return candidates.sort((a,b2)=>b2.mtime-a.mtime),candidates}async function scanJsonlByCustomName(cwd,customName){let projectDir=await resolveClaudeProjectDir(cwd),candidates=await listJsonlsByMtime(projectDir);for(let candidate of candidates)if(await jsonlHeadMatchesCustomName(candidate.full,customName))return candidate.name.replace(/\.jsonl$/,"");return null}async function confirmRecover(agentId){if(!process.stdin.isTTY)return console.error(`Refusing to recover "${agentId}" without --yes in a non-interactive shell. Re-run with \`--yes\` for unattended use.`),!1;let{createInterface:createInterface3}=await import("readline"),rl=createInterface3({input:process.stdin,output:process.stdout}),answer=await new Promise((resolve6)=>{rl.question(`About to recover agent "${agentId}" (flip auto_resume, terminate stale spawning executors, resume). Continue? [y/N] `,(a)=>{rl.close(),resolve6(a.trim().toLowerCase())})});return answer==="y"||answer==="yes"}async function handleWorkerRecover(name,options){let agent=await resolveAgentForRecover(name);if(!options.yes){if(!await confirmRecover(agent.id)){console.log("Recovery cancelled.");return}}console.log(`Recovering agent "${agent.id}"...`);let surgery=await recoverSurgery(agent.id);if(surgery.flippedAutoResume)console.log(" \u2713 auto_resume re-enabled");if(surgery.staleSpawningTerminated>0)console.log(` \u2713 terminated ${surgery.staleSpawningTerminated} stale spawning executor(s)`);if(surgery.createdAnchor)console.log(" \u2713 created executor anchor from JSONL on disk");if(surgery.sessionId)console.log(` \u2713 session UUID located: ${surgery.sessionId}`);else console.error(` \u2717 no recoverable session UUID for "${agent.id}". JSONL scan in ${agent.repoPath??"<no repo_path>"} did not match custom_name="${agent.customName??agent.role??"<none>"}".`),process.exit(1);let resumeName=agent.customName??agent.role??agent.id;await handleWorkerResume(resumeName,{all:!1,noResetAttempts:!1});let post=await get(agent.id);if(post?.paneId&&post.session)console.log(` pane: ${post.paneId}`),console.log(` attach: tmux attach -t ${post.session}`)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
2004
- `)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let params=await buildResumeParams(agent,template,decision.sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"])});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile10,readdir as readdir6}from"fs/promises";import{homedir as homedir27}from"os";import{join as join41}from"path";function getCodexDir(){return join41(homedir27(),".codex")}function getSessionsDir(){return join41(getCodexDir(),"sessions")}function getStateDbPath(){return join41(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join41(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join41(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join41(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join41(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile10(filePath,"utf-8"),nlIdx=content.indexOf(`
2004
+ `)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let params=await buildResumeParams(agent,template,decision.sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await listForRender(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"])});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile10,readdir as readdir6}from"fs/promises";import{homedir as homedir27}from"os";import{join as join41}from"path";function getCodexDir(){return join41(homedir27(),".codex")}function getSessionsDir(){return join41(getCodexDir(),"sessions")}function getStateDbPath(){return join41(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join41(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join41(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join41(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join41(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile10(filePath,"utf-8"),nlIdx=content.indexOf(`
2005
2005
  `),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry2=JSON.parse(firstLine);if(entry2.type==="session_meta"&&entry2.payload?.cwd)return{cwd:entry2.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query2=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query2.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query:query2}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile10(logPath,"utf-8")}catch{return[]}return content.split(`
2006
2006
  `).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result2=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result2=result2.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
2007
2007
  `).filter((line)=>!isTmuxMarkerOrNoise(line));while(filtered.length>0&&filtered[0].trim()==="")filtered.shift();while(filtered.length>0&&filtered[filtered.length-1].trim()==="")filtered.pop();return filtered.join(`
@@ -4441,7 +4441,7 @@ docs/incident-response/canisterworm.md for the legitimate --unsafe-unverified co
4441
4441
  ORDER BY sc.timestamp DESC
4442
4442
  LIMIT ${limit}
4443
4443
  `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log(`No results for "${query2}".`);return}for(let r of rows)console.log(`[${formatRelativeTimestamp(r.timestamp)}] ${r.agent_label??"orphaned"} / ${r.session_id.slice(0,12)}`),console.log(` ${r.role}${r.tool_name?` [${r.tool_name}]`:""}: ${r.headline}`);console.log(`
4444
- (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions2=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions2.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--source <name>","Filter by executor metadata source (e.g. omni)").option("--limit <n>","Max number of sessions to return (default: 50)").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions2.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions2.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query2,options)=>{await sessionsSearchCommand(query2,options)}),sessions2.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}init_state();init_observability_health();init_agent_registry();init_agent_registry();init_derived_signals();init_derived_signals();init_executor_registry();init_should_resume();init_term_format();var ANSI2={reset:"\x1B[0m",dim:"\x1B[2m",bold:"\x1B[1m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",magenta:"\x1B[35m"};function colorize(text,color2){if(process.env.NO_COLOR||!process.stdout.isTTY)return text;return`${ANSI2[color2]}${text}${ANSI2.reset}`}async function aggregateAgentDecisions(includeArchived){let agents=await listAgents({includeArchived}),results=Array(agents.length),cursor=0,cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agents.length)),workers=Array.from({length:cap},async()=>{while(cursor<agents.length){let i2=cursor++;if(i2>=agents.length)return;let a=agents[i2],decision=await shouldResume(a.id).catch(()=>({resume:!1,reason:"no_session_id",rehydrate:"lazy"})),name=a.customName??a.role??a.id,sessionPreview=decision.sessionId?decision.sessionId.slice(0,8):null,lastWriteAt=null;if(a.currentExecutorId){let exec3=await getExecutor(a.currentExecutorId).catch(()=>null);lastWriteAt=exec3?.updatedAt??exec3?.startedAt??null}results[i2]={agentId:a.id,name,kind:a.kind??null,decision,sessionPreview,lastWriteAt}}});return await Promise.all(workers),results}async function collectHealthChecks(){let report=await collectObservabilityHealth();return[{name:"partition",status:report.partition_health,message:report.next_rotation_at?`next rotation: ${report.next_rotation_at}`:void 0},{name:"watchdog",status:report.watchdog,message:report.watchdog_detail},{name:"spill journal",status:report.spill_journal==="pending"?"warn":report.spill_journal==="unknown"?"unknown":"ok",message:report.spill_path},{name:"watcher metrics",status:report.watcher_metrics,message:report.watcher_metrics==="ok"?"all six recently seen":"one or more meta-events missing"}]}function statusIcon(status){switch(status){case"ok":return colorize("\u2713","green");case"warn":return colorize("!","yellow");case"fail":return colorize("\u2717","red");default:return colorize("?","dim")}}function severityBadge(sev){if(sev==="critical")return colorize("[CRITICAL]","red");if(sev==="warn")return colorize("[WARN]","yellow");return colorize("[INFO]","dim")}function formatAgentLine(line){let kindTag=line.kind==="permanent"?colorize("p","magenta"):colorize("t","cyan"),session=line.sessionPreview?colorize(line.sessionPreview,"dim"):colorize("no-session","yellow"),lastWrite=line.lastWriteAt?formatRelativeTimestamp(line.lastWriteAt):"-",reason=line.decision.reason==="ok"?colorize("resume ready","green"):colorize(line.decision.reason,"yellow");return` [${kindTag}] ${line.name.padEnd(28).slice(0,28)} ${session.padEnd(8)} last:${lastWrite.padEnd(10)} ${reason}`}function renderResumableSection(lines){let resumable=lines.filter((l)=>l.decision.resume);if(resumable.length===0){console.log(colorize(" (no in-flight agents \u2014 every prior anchor is closed or paused)","dim"));return}for(let line of resumable)console.log(formatAgentLine(line))}function renderStuckSection(lines){let stuck=lines.filter((l)=>!l.decision.resume&&l.decision.reason!=="assignment_closed"&&l.decision.reason!=="unknown_agent");if(stuck.length===0)return;console.log(""),console.log(colorize("STUCK / NEEDS ATTENTION","bold")),console.log("-".repeat(60));for(let line of stuck)if(console.log(formatAgentLine(line)),line.decision.reason==="auto_resume_disabled")console.log(colorize(` \u2192 genie agent resume ${line.name}`,"dim"));else if(line.decision.reason==="no_session_id")console.log(colorize(` \u2192 genie agent show ${line.name} # inspect; consider archive`,"dim"))}function renderArchivedSection(lines){let done=lines.filter((l)=>l.decision.reason==="assignment_closed");if(done.length===0)return;console.log(""),console.log(colorize("DONE / ARCHIVED","bold")),console.log("-".repeat(60));for(let line of done)console.log(formatAgentLine(line))}function renderSignalsSection(signals2){if(signals2.length===0){console.log(colorize(" (no active alerts)","dim"));return}for(let sig of signals2){console.log(` ${severityBadge(sig.severity)} ${colorize(sig.type,"bold")} on ${sig.subject}`);let drilldown=SIGNAL_DRILLDOWN[sig.type];if(drilldown)console.log(colorize(` \u2192 ${drilldown}`,"dim"));if(sig.triggeredAt)console.log(colorize(` ${formatRelativeTimestamp(sig.triggeredAt)}`,"dim"))}}function renderHealthSection(checks){for(let check2 of checks){let detail=check2.message?colorize(` ${check2.message}`,"dim"):"";console.log(` ${statusIcon(check2.status)} ${check2.name.padEnd(18)} ${detail}`)}}async function renderDebugSection(){let audit=await auditAgentKind();if(console.log(""),console.log(colorize("DEBUG \u2014 kind audit","bold")),console.log("-".repeat(60)),console.log(` rows scanned: ${audit.total}`),console.log(` drift count : ${audit.drifted.length}`),audit.drifted.length>0)for(let d of audit.drifted.slice(0,10))console.log(colorize(` drift: ${d.id} stored=${d.kind??"null"} expected=${d.expected}`,"yellow"))}async function buildReport(opts){let includeArchived=opts.all===!0,[agents,signals2]=await Promise.all([aggregateAgentDecisions(includeArchived),listActiveDerivedSignals()]),partitionSignal=await detectPartitionMissing().catch(()=>null);if(partitionSignal)await recordDerivedSignal(partitionSignal).catch(()=>{}),signals2.unshift(partitionSignal);let report={agents,signals:signals2};if(opts.health)report.health=await collectHealthChecks();return report}async function statusCommand2(opts={}){let t0=Date.now(),report=await buildReport(opts);if(opts.json){console.log(JSON.stringify(report,null,2));return}if(console.log(""),console.log(colorize("IN-FLIGHT \u2014 should resume","bold")),console.log("-".repeat(60)),renderResumableSection(report.agents),renderStuckSection(report.agents),opts.all)renderArchivedSection(report.agents);if(console.log(""),console.log(colorize("ACTIVE SIGNALS","bold")),console.log("-".repeat(60)),renderSignalsSection(report.signals),report.health)console.log(""),console.log(colorize("HEALTH","bold")),console.log("-".repeat(60)),renderHealthSection(report.health);if(opts.debug)await renderDebugSection();console.log(""),console.log(colorize(` rendered in ${Date.now()-t0}ms \u2014 ${report.agents.length} agents, ${report.signals.length} signals`,"dim")),console.log("")}init_genie_tokens();init_term_format();var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService7()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
4444
+ (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions2=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions2.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--source <name>","Filter by executor metadata source (e.g. omni)").option("--limit <n>","Max number of sessions to return (default: 50)").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions2.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions2.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query2,options)=>{await sessionsSearchCommand(query2,options)}),sessions2.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}init_state();init_observability_health();init_agent_registry();init_agent_registry();init_derived_signals();init_derived_signals();init_executor_registry();init_should_resume();init_term_format();var ANSI2={reset:"\x1B[0m",dim:"\x1B[2m",bold:"\x1B[1m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",magenta:"\x1B[35m"};function colorize(text,color2){if(process.env.NO_COLOR||!process.stdout.isTTY)return text;return`${ANSI2[color2]}${text}${ANSI2.reset}`}async function aggregateAgentDecisions(includeArchived){let agents=await listAgentsForRender({includeArchived}),results=Array(agents.length),cursor=0,cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agents.length)),workers=Array.from({length:cap},async()=>{while(cursor<agents.length){let i2=cursor++;if(i2>=agents.length)return;let a=agents[i2],decision=await shouldResume(a.id).catch(()=>({resume:!1,reason:"no_session_id",rehydrate:"lazy"})),name=a.customName??a.role??a.id,sessionPreview=decision.sessionId?decision.sessionId.slice(0,8):null,lastWriteAt=null;if(a.currentExecutorId){let exec3=await getExecutor(a.currentExecutorId).catch(()=>null);lastWriteAt=exec3?.updatedAt??exec3?.startedAt??null}results[i2]={agentId:a.id,name,kind:a.kind??null,decision,sessionPreview,lastWriteAt}}});return await Promise.all(workers),results}async function collectHealthChecks(){let report=await collectObservabilityHealth();return[{name:"partition",status:report.partition_health,message:report.next_rotation_at?`next rotation: ${report.next_rotation_at}`:void 0},{name:"watchdog",status:report.watchdog,message:report.watchdog_detail},{name:"spill journal",status:report.spill_journal==="pending"?"warn":report.spill_journal==="unknown"?"unknown":"ok",message:report.spill_path},{name:"watcher metrics",status:report.watcher_metrics,message:report.watcher_metrics==="ok"?"all six recently seen":"one or more meta-events missing"}]}function statusIcon(status){switch(status){case"ok":return colorize("\u2713","green");case"warn":return colorize("!","yellow");case"fail":return colorize("\u2717","red");default:return colorize("?","dim")}}function severityBadge(sev){if(sev==="critical")return colorize("[CRITICAL]","red");if(sev==="warn")return colorize("[WARN]","yellow");return colorize("[INFO]","dim")}function formatAgentLine(line){let kindTag=line.kind==="permanent"?colorize("p","magenta"):colorize("t","cyan"),session=line.sessionPreview?colorize(line.sessionPreview,"dim"):colorize("no-session","yellow"),lastWrite=line.lastWriteAt?formatRelativeTimestamp(line.lastWriteAt):"-",reason=line.decision.reason==="ok"?colorize("resume ready","green"):colorize(line.decision.reason,"yellow");return` [${kindTag}] ${line.name.padEnd(28).slice(0,28)} ${session.padEnd(8)} last:${lastWrite.padEnd(10)} ${reason}`}function renderResumableSection(lines){let resumable=lines.filter((l)=>l.decision.resume);if(resumable.length===0){console.log(colorize(" (no in-flight agents \u2014 every prior anchor is closed or paused)","dim"));return}for(let line of resumable)console.log(formatAgentLine(line))}function renderStuckSection(lines){let stuck=lines.filter((l)=>!l.decision.resume&&l.decision.reason!=="assignment_closed"&&l.decision.reason!=="unknown_agent");if(stuck.length===0)return;console.log(""),console.log(colorize("STUCK / NEEDS ATTENTION","bold")),console.log("-".repeat(60));for(let line of stuck)if(console.log(formatAgentLine(line)),line.decision.reason==="auto_resume_disabled")console.log(colorize(` \u2192 genie agent resume ${line.name}`,"dim"));else if(line.decision.reason==="no_session_id")console.log(colorize(` \u2192 genie agent show ${line.name} # inspect; consider archive`,"dim"))}function renderArchivedSection(lines){let done=lines.filter((l)=>l.decision.reason==="assignment_closed");if(done.length===0)return;console.log(""),console.log(colorize("DONE / ARCHIVED","bold")),console.log("-".repeat(60));for(let line of done)console.log(formatAgentLine(line))}function renderSignalsSection(signals2){if(signals2.length===0){console.log(colorize(" (no active alerts)","dim"));return}for(let sig of signals2){console.log(` ${severityBadge(sig.severity)} ${colorize(sig.type,"bold")} on ${sig.subject}`);let drilldown=SIGNAL_DRILLDOWN[sig.type];if(drilldown)console.log(colorize(` \u2192 ${drilldown}`,"dim"));if(sig.triggeredAt)console.log(colorize(` ${formatRelativeTimestamp(sig.triggeredAt)}`,"dim"))}}function renderHealthSection(checks){for(let check2 of checks){let detail=check2.message?colorize(` ${check2.message}`,"dim"):"";console.log(` ${statusIcon(check2.status)} ${check2.name.padEnd(18)} ${detail}`)}}async function renderDebugSection(){let audit=await auditAgentKind();if(console.log(""),console.log(colorize("DEBUG \u2014 kind audit","bold")),console.log("-".repeat(60)),console.log(` rows scanned: ${audit.total}`),console.log(` drift count : ${audit.drifted.length}`),audit.drifted.length>0)for(let d of audit.drifted.slice(0,10))console.log(colorize(` drift: ${d.id} stored=${d.kind??"null"} expected=${d.expected}`,"yellow"))}async function buildReport(opts){let includeArchived=opts.all===!0,[agents,signals2]=await Promise.all([aggregateAgentDecisions(includeArchived),listActiveDerivedSignals()]),partitionSignal=await detectPartitionMissing().catch(()=>null);if(partitionSignal)await recordDerivedSignal(partitionSignal).catch(()=>{}),signals2.unshift(partitionSignal);let report={agents,signals:signals2};if(opts.health)report.health=await collectHealthChecks();return report}async function statusCommand2(opts={}){let t0=Date.now(),report=await buildReport(opts);if(opts.json){console.log(JSON.stringify(report,null,2));return}if(console.log(""),console.log(colorize("IN-FLIGHT \u2014 should resume","bold")),console.log("-".repeat(60)),renderResumableSection(report.agents),renderStuckSection(report.agents),opts.all)renderArchivedSection(report.agents);if(console.log(""),console.log(colorize("ACTIVE SIGNALS","bold")),console.log("-".repeat(60)),renderSignalsSection(report.signals),report.health)console.log(""),console.log(colorize("HEALTH","bold")),console.log("-".repeat(60)),renderHealthSection(report.health);if(opts.debug)await renderDebugSection();console.log(""),console.log(colorize(` rendered in ${Date.now()-t0}ms \u2014 ${report.agents.length} agents, ${report.signals.length} signals`,"dim")),console.log("")}init_genie_tokens();init_term_format();var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService7()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
4445
4445
  ${tags.length} tag${tags.length===1?"":"s"}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),tag.command("create <name>").description("Create a custom tag").option("--color <hex>","Tag color (hex)",palette.textDim).option("--type <typeId>","Associate with a task type").action(async(name,options)=>{try{let ts3=await getTaskService7(),id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createTag({id,name,color:options.color,typeId:options.type});console.log(`Created tag "${t.name}" (${t.id}) with color ${t.color}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_term_format();var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}var _boardService2;async function getBoardService2(){if(!_boardService2)_boardService2=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService2}var _closeMergedService;async function getCloseMergedService(){if(!_closeMergedService)_closeMergedService=await Promise.resolve().then(() => (init_task_close_merged(),exports_task_close_merged));return _closeMergedService}function localActor2(name){return{actorType:"local",actorId:name}}function currentActor3(){let name=process.env.GENIE_AGENT_NAME??"cli";return localActor2(name)}function getRunId(){return process.env.GENIE_RUN_ID??`run-${Date.now()}`}var PRIORITY_COLORS={urgent:"\x1B[31m",high:"\x1B[33m",normal:"\x1B[0m",low:"\x1B[90m"},RESET2="\x1B[0m";async function resolveDefaultBoardId(){try{let{execSync:execSync17}=await import("child_process"),repoRoot=execSync17("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),{join:join69}=await import("path"),configPath2=join69(repoRoot,".genie","config.json"),{existsSync:existsSync57,readFileSync:readFileSync36}=await import("fs");if(existsSync57(configPath2)){let config=JSON.parse(readFileSync36(configPath2,"utf-8"));if(config.activeBoard)return config.activeBoard}}catch{}return null}async function handleInvalidStageError(taskId,message){try{let task=await(await getTaskService8()).getTask(taskId);if(!task?.boardId)return;let board=await(await getBoardService2()).getBoard(task.boardId);if(!board)return;let validCols=board.columns.sort((a,b2)=>a.position-b2.position).map((c)=>c.name).join(" \u2192 ");console.error(`Error: ${message}
4446
4446
  Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}async function resolveBoardOption(boardName){if(boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);return board.id}return await resolveDefaultBoardId()??void 0}function getProjectName(repoPath){let parts=repoPath.split("/");return parts[parts.length-1]||repoPath}function formatTaskRow(t,showProject,hasExternal){let seq2=showProject?`${getProjectName(t.repoPath)}#${t.seq}`:`#${t.seq}`,title=truncate2(t.title,38),color2=t.status==="archived"?"\x1B[90m":PRIORITY_COLORS[t.priority]??"",due=formatDate(t.dueDate),proj=showProject?`${padRight(getProjectName(t.repoPath),16)} `:"",ext=hasExternal?`${padRight(truncate2(t.externalId??"",25),27)} `:"",statusLabel=t.status==="archived"?"\x1B[90m[archived]\x1B[0m":t.status;return` ${padRight(seq2,showProject?22:6)} ${proj}${padRight(title,40)} ${ext}${padRight(t.stage,12)} ${padRight(statusLabel,12)} ${color2}${padRight(t.priority,10)}${RESET2} ${padRight(due,12)}`}function printTaskList(tasks,showProject=!1){if(tasks.length===0){console.log("No tasks found.");return}let hasExternal=tasks.some((t)=>t.externalId),extCol=hasExternal?`${padRight("EXTERNAL",27)} `:"",projCol=showProject?`${padRight("PROJECT",16)} `:"",header=` ${padRight("#",6)} ${projCol}${padRight("TITLE",40)} ${extCol}${padRight("STAGE",12)} ${padRight("STATUS",12)} ${padRight("PRIORITY",10)} ${padRight("DUE",12)}`,lineLen=(showProject?108:92)+(hasExternal?28:0);console.log(header),console.log(` ${"\u2500".repeat(lineLen)}`);for(let t of tasks)console.log(formatTaskRow(t,showProject,hasExternal));console.log(`
4447
4447
  ${tasks.length} task${tasks.length===1?"":"s"}`)}function printTaskFields(task){console.log(""),console.log(`Task #${task.seq}: ${task.title}`),console.log("\u2500".repeat(60)),console.log(` ID: ${task.id}`),console.log(` Type: ${task.typeId}`),console.log(` Stage: ${task.stage}`),console.log(` Status: ${task.status}`),console.log(` Priority: ${task.priority}`);let optionalFields=[["Description",task.description],["Criteria",task.acceptanceCriteria],["Effort",task.estimatedEffort],["Start",task.startDate?formatDate(task.startDate):null],["Due",task.dueDate?formatDate(task.dueDate):null],["Blocked",task.blockedReason],["Parent",task.parentId],["Release",task.releaseId],["Wish",task.wishFile],["External",task.externalId],["Ext URL",task.externalUrl]];for(let[label,value]of optionalFields)if(value)console.log(` ${padRight(`${label}:`,12)} ${value}`);if(task.checkoutRunId)console.log(` Checkout: ${task.checkoutRunId} (since ${formatTimestamp(task.executionLockedAt)})`);if(console.log(` Created: ${formatTimestamp(task.createdAt)}`),task.startedAt)console.log(` Started: ${formatTimestamp(task.startedAt)}`);if(task.endedAt)console.log(` Ended: ${formatTimestamp(task.endedAt)}`)}async function printTaskRelations(task){let ts3=await getTaskService8(),actors=await ts3.getTaskActors(task.id,task.repoPath);if(actors.length>0){console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260427.3",
3
+ "version": "4.260427.5",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260427.3",
3
+ "version": "4.260427.5",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260427.3",
3
+ "version": "4.260427.5",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -0,0 +1,161 @@
1
+ -- 053_master_backfill_and_shadow_cleanup.sql
2
+ --
3
+ -- master-aware-spawn wish, Wave 2, Group 14 (sub-deliverables 14a + 14b).
4
+ --
5
+ -- The 2026-04-25 power-outage post-mortem surfaced that masters today fall
6
+ -- into two shadow patterns in PG (twin's analysis at
7
+ -- /tmp/genie-recover/group-1-shadow-analysis.json):
8
+ --
9
+ -- Type A — `dir:<name>` + bare-name pair (only `email` today). Both rows
10
+ -- exist; `findLiveWorkerFuzzy(name)` returns the bare row first,
11
+ -- so Group 1's `worker?.id ?? \`dir:\${recipientId}\`` chokepoint
12
+ -- fallback never fires. Result: master `email` re-spawns fresh
13
+ -- every time the live worker dies.
14
+ --
15
+ -- Type B — UUID + bare-name pair, NO `dir:<name>` row (`felipe`, `genie`,
16
+ -- `genie-pgserve`). The bare row's `custom_name=''` blocks the
17
+ -- jsonl-scan fallback (Group 7), and there is no `dir:` row for
18
+ -- Group 1's chokepoint to anchor on. Result: post-`unregister`
19
+ -- recovery is impossible without manual surgery.
20
+ --
21
+ -- This migration closes both gaps in two passes:
22
+ --
23
+ -- 1. **14b — master backfill:** for every agent row with
24
+ -- `kind='permanent' AND repo_path != ''` whose canonical name (custom_name
25
+ -- or role fallback) lacks a `dir:<name>` peer, create the missing
26
+ -- directory row using the bare row's identity columns. Brings
27
+ -- `dir:felipe`, `dir:genie`, `dir:genie-pgserve` into existence.
28
+ --
29
+ -- 2. **14a — bare-name shadow cleanup:** for every `dir:<name>` row that
30
+ -- pairs with a non-UUID, non-dir bare-name row whose
31
+ -- `current_executor_id IS NULL`, archive (state='archived',
32
+ -- auto_resume=false) the bare row. **Heal-not-wipe** — never DELETE.
33
+ -- The Group 3 guardrail in `src/lib/agent-directory.ts:rm()` blocks
34
+ -- DELETE on `kind='permanent' AND repo_path != ''` regardless, but
35
+ -- a SQL-side UPDATE bypasses that lock by design.
36
+ --
37
+ -- 14b runs before 14a so the dir-rows we just created can pair with their
38
+ -- bare shadows in the same migration. After this migration runs:
39
+ --
40
+ -- - `dir:email`, `dir:felipe`, `dir:genie`, `dir:genie-pgserve` all exist
41
+ -- and carry `repo_path`. Group 1's chokepoint extension covers all four.
42
+ -- - bare `email`, `felipe`, `genie`, `genie-pgserve` rows with
43
+ -- `current_executor_id IS NULL` are archived. registry.get(name) now
44
+ -- returns either nothing (so worker is null → Group 1 fallback fires) or
45
+ -- the dir:<name> row directly.
46
+ --
47
+ -- Idempotent: each pass gates on a NOT-EXISTS / DISTINCT-FROM-archived
48
+ -- predicate. Re-running the migration affects zero additional rows.
49
+ --
50
+ -- Audit: every backfilled row emits `directory.master_backfilled`; every
51
+ -- archived bare shadow emits `state_changed` with reason
52
+ -- `bare_name_shadow_archived`.
53
+
54
+ -- ---------------------------------------------------------------------------
55
+ -- Pass 1 (14b): backfill dir:<name> rows for masters that lack one.
56
+ -- ---------------------------------------------------------------------------
57
+ WITH
58
+ -- Pick one canonical source row per "name" equivalence class. The bare
59
+ -- row of a Type-B pair (custom_name='' but role + repo_path set) is the
60
+ -- only source carrying repo_path, so we filter on repo_path-non-empty
61
+ -- candidates here. Prefer rows with non-empty custom_name when both
62
+ -- candidates exist (NULLIF + ORDER BY in DISTINCT ON).
63
+ backfill_targets AS (
64
+ SELECT DISTINCT ON (COALESCE(NULLIF(a.custom_name, ''), a.role))
65
+ COALESCE(NULLIF(a.custom_name, ''), a.role) AS name,
66
+ a.role,
67
+ a.team,
68
+ a.repo_path
69
+ FROM agents a
70
+ WHERE a.kind = 'permanent'
71
+ AND a.id NOT LIKE 'dir:%'
72
+ AND a.repo_path IS NOT NULL AND a.repo_path <> ''
73
+ AND COALESCE(NULLIF(a.custom_name, ''), a.role) IS NOT NULL
74
+ AND a.auto_resume = true
75
+ AND a.state IS DISTINCT FROM 'archived'
76
+ AND NOT EXISTS (
77
+ SELECT 1 FROM agents d
78
+ WHERE d.id = 'dir:' || COALESCE(NULLIF(a.custom_name, ''), a.role)
79
+ )
80
+ ORDER BY
81
+ COALESCE(NULLIF(a.custom_name, ''), a.role),
82
+ -- Prefer rows that already populate custom_name (UUID peer in Type B),
83
+ -- so role/team fields come from the canonical identity row.
84
+ (CASE WHEN a.custom_name IS NOT NULL AND a.custom_name <> '' THEN 0 ELSE 1 END),
85
+ a.id
86
+ ),
87
+ inserted AS (
88
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, metadata)
89
+ SELECT
90
+ 'dir:' || c.name,
91
+ c.role,
92
+ -- The unique partial index `idx_agents_custom_name_team` requires
93
+ -- (custom_name, team) to be unique when both are non-null. If a peer
94
+ -- already owns that slot (Type B production: UUID peer with
95
+ -- custom_name=name, team=team), set custom_name=NULL on the dir row
96
+ -- so the new row sits outside the unique index. Session-sync's
97
+ -- `getAgentByName(name, team)` lookup will route to the UUID peer
98
+ -- while it lives; once the peer is unregistered, an UPDATE
99
+ -- backfill (separate migration if the slot ever frees) can repopulate.
100
+ CASE
101
+ WHEN c.team IS NOT NULL AND EXISTS (
102
+ SELECT 1 FROM agents x
103
+ WHERE x.custom_name = c.name AND x.team = c.team
104
+ AND x.id <> 'dir:' || c.name
105
+ ) THEN NULL
106
+ ELSE c.name
107
+ END,
108
+ c.team,
109
+ c.repo_path,
110
+ now(),
111
+ NULL,
112
+ '{}'::jsonb
113
+ FROM backfill_targets c
114
+ ON CONFLICT (id) DO NOTHING
115
+ RETURNING id
116
+ )
117
+ INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
118
+ SELECT 'agent', i.id, 'directory.master_backfilled',
119
+ 'migration:053_master_backfill_and_shadow_cleanup',
120
+ jsonb_build_object('reason', 'master_backfill',
121
+ 'wish', 'master-aware-spawn',
122
+ 'group', '14b')
123
+ FROM inserted i;
124
+
125
+ -- ---------------------------------------------------------------------------
126
+ -- Pass 2 (14a): archive bare-name shadows whose dir:<name> peer now exists.
127
+ -- Heal-not-wipe — never DELETE. Idempotent via state IS DISTINCT FROM.
128
+ -- ---------------------------------------------------------------------------
129
+ WITH archived AS (
130
+ UPDATE agents bare
131
+ SET state = 'archived',
132
+ auto_resume = false,
133
+ last_state_change = now()
134
+ FROM agents dir
135
+ WHERE dir.id = 'dir:' || bare.id
136
+ AND bare.id NOT LIKE 'dir:%'
137
+ -- Exclude UUID-shaped ids (4 hyphens). Same heuristic as migration 050.
138
+ AND bare.id NOT LIKE '%-%-%-%-%'
139
+ AND bare.current_executor_id IS NULL
140
+ AND bare.state IS DISTINCT FROM 'archived'
141
+ AND bare.repo_path IS NOT NULL
142
+ AND bare.repo_path <> ''
143
+ -- Belt-and-suspenders identity match: the bare row's role or id must
144
+ -- agree with the dir's custom_name. Prevents archiving an unrelated
145
+ -- bare row whose id happens to suffix a dir: id.
146
+ AND (
147
+ bare.role = dir.custom_name
148
+ OR bare.id = dir.custom_name
149
+ OR bare.custom_name = dir.custom_name
150
+ )
151
+ RETURNING bare.id
152
+ )
153
+ INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
154
+ SELECT 'worker', a.id, 'state_changed',
155
+ 'migration:053_master_backfill_and_shadow_cleanup',
156
+ jsonb_build_object('reason', 'bare_name_shadow_archived',
157
+ 'state', 'archived',
158
+ 'auto_resume', false,
159
+ 'wish', 'master-aware-spawn',
160
+ 'group', '14a')
161
+ FROM archived a;
@@ -187,6 +187,11 @@ describe.skipIf(!DB_AVAILABLE)('migration 049 — agents.kind GENERATED column',
187
187
  `--glob '!src/db/migrations/049_agents_kind_generated.sql' ` +
188
188
  `--glob '!src/db/migrations/046_dir_agents_state_null.sql' ` +
189
189
  `--glob '!src/db/migrations/agents-kind.test.ts' ` +
190
+ // Migration 053's test file (master-aware-spawn Group 14a+b)
191
+ // legitimately queries `id LIKE 'dir:%'` to verify dir-row
192
+ // backfill + bare-shadow archive shape post-migration. Distinguishing
193
+ // dir-shape vs bare-shape is the whole point — not permanence inference.
194
+ `--glob '!src/db/migrations/master-backfill-and-shadow-cleanup.test.ts' ` +
190
195
  // The state-machine invariants test file legitimately references
191
196
  // the pattern in test descriptions, comments, and the rg pattern
192
197
  // it itself constructs.
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Integration tests for migration 053 — master_backfill_and_shadow_cleanup.
3
+ *
4
+ * Covers Group 14 sub-deliverables 14a (bare-name shadow archival) and 14b
5
+ * (master backfill of dir:<name> rows).
6
+ */
7
+
8
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
9
+ import { readFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { getConnection } from '../../lib/db.js';
12
+ import { DB_AVAILABLE, setupTestDatabase } from '../../lib/test-db.js';
13
+
14
+ const MIGRATION_PATH = join(import.meta.dir, '053_master_backfill_and_shadow_cleanup.sql');
15
+
16
+ async function applyMigrationManually(): Promise<void> {
17
+ const sql = await getConnection();
18
+ const body = await readFile(MIGRATION_PATH, 'utf-8');
19
+ await sql.unsafe(body);
20
+ }
21
+
22
+ describe.skipIf(!DB_AVAILABLE)('migration 053 — master_backfill_and_shadow_cleanup', () => {
23
+ let cleanup: () => Promise<void>;
24
+
25
+ beforeAll(async () => {
26
+ cleanup = await setupTestDatabase();
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await cleanup();
31
+ });
32
+
33
+ beforeEach(async () => {
34
+ const sql = await getConnection();
35
+ await sql`DELETE FROM audit_events WHERE actor = 'migration:053_master_backfill_and_shadow_cleanup'`;
36
+ await sql`DELETE FROM assignments`;
37
+ await sql`DELETE FROM executors`;
38
+ await sql`DELETE FROM agents`;
39
+ });
40
+
41
+ // ==========================================================================
42
+ // 14b — master backfill: dir:<name> created from existing canonical fields
43
+ // ==========================================================================
44
+
45
+ test('14b: backfills dir:felipe from a bare felipe row (Type-B shape)', async () => {
46
+ const sql = await getConnection();
47
+ // Twin's analysis: the bare row is the only candidate carrying
48
+ // repo_path. `custom_name` is empty/null in production (the partial
49
+ // unique index `idx_agents_custom_name_team` requires it to be unique
50
+ // when populated alongside team — bare rows skirt that by leaving it
51
+ // null); role is the canonical identity.
52
+ await sql`
53
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
54
+ VALUES ('felipe', 'felipe', NULL, 'felipe', '/home/genie/workspace/agents/felipe', now(), NULL, true, NULL)
55
+ `;
56
+
57
+ await applyMigrationManually();
58
+
59
+ const dirRow = await sql<
60
+ { id: string; role: string; custom_name: string | null; team: string; repo_path: string }[]
61
+ >`
62
+ SELECT id, role, custom_name, team, repo_path
63
+ FROM agents WHERE id = 'dir:felipe'
64
+ `;
65
+ expect(dirRow.length).toBe(1);
66
+ expect(dirRow[0].role).toBe('felipe');
67
+ expect(dirRow[0].custom_name).toBe('felipe');
68
+ expect(dirRow[0].team).toBe('felipe');
69
+ expect(dirRow[0].repo_path).toBe('/home/genie/workspace/agents/felipe');
70
+
71
+ const audit = await sql<{ details: { reason: string; group: string } }[]>`
72
+ SELECT details FROM audit_events
73
+ WHERE actor = 'migration:053_master_backfill_and_shadow_cleanup'
74
+ AND entity_id = 'dir:felipe'
75
+ AND event_type = 'directory.master_backfilled'
76
+ `;
77
+ expect(audit.length).toBe(1);
78
+ expect(audit[0].details.reason).toBe('master_backfill');
79
+ expect(audit[0].details.group).toBe('14b');
80
+ });
81
+
82
+ test('14b: backfills dir:genie and dir:genie-pgserve from twin-shape masters', async () => {
83
+ const sql = await getConnection();
84
+ await sql`
85
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
86
+ VALUES
87
+ ('genie', 'genie', NULL, 'genie', '/home/genie/workspace/agents/genie', now(), 'idle', true, NULL),
88
+ ('genie-pgserve', 'genie-pgserve', NULL, 'genie', '/home/genie/workspace/agents/genie-pgserve', now(), NULL, true, NULL)
89
+ `;
90
+
91
+ await applyMigrationManually();
92
+
93
+ const created = await sql<{ id: string }[]>`
94
+ SELECT id FROM agents WHERE id LIKE 'dir:%' ORDER BY id
95
+ `;
96
+ expect(created.map((r: { id: string }) => r.id)).toEqual(['dir:genie', 'dir:genie-pgserve']);
97
+
98
+ const repoPaths = await sql<{ id: string; repo_path: string }[]>`
99
+ SELECT id, repo_path FROM agents WHERE id LIKE 'dir:%' ORDER BY id
100
+ `;
101
+ const byId = new Map(repoPaths.map((r: { id: string; repo_path: string }) => [r.id, r.repo_path]));
102
+ expect(byId.get('dir:genie')).toBe('/home/genie/workspace/agents/genie');
103
+ expect(byId.get('dir:genie-pgserve')).toBe('/home/genie/workspace/agents/genie-pgserve');
104
+ });
105
+
106
+ test('14b: NULLs custom_name when (name, team) slot is held by another live peer', async () => {
107
+ const sql = await getConnection();
108
+ // Production Type-B: UUID peer holds (custom_name='felipe', team='felipe')
109
+ // in the unique partial index. The bare row lacks dir:<name>. Backfilling
110
+ // dir:felipe with custom_name='felipe' team='felipe' would conflict with
111
+ // the index, so the migration must NULL custom_name on the new row.
112
+ await sql`
113
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
114
+ VALUES
115
+ ('00000000-0000-0000-0000-feedfacefeed', 'felipe', 'felipe', 'felipe', '/some/uuid/path', now(), 'idle', true, NULL),
116
+ ('felipe', 'felipe', NULL, 'felipe', '/home/genie/workspace/agents/felipe', now(), 'idle', true, NULL)
117
+ `;
118
+
119
+ await applyMigrationManually();
120
+
121
+ const dirRow = await sql<{ custom_name: string | null }[]>`
122
+ SELECT custom_name FROM agents WHERE id = 'dir:felipe'
123
+ `;
124
+ expect(dirRow.length).toBe(1);
125
+ expect(dirRow[0].custom_name).toBeNull();
126
+ });
127
+
128
+ test('14b: skips agents that already have a dir:<name> peer (no duplicate insert)', async () => {
129
+ const sql = await getConnection();
130
+ await sql`
131
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
132
+ VALUES ('dir:email', 'email', 'email', 'felipe', '/home/genie/workspace/agents/email', now(), NULL, true, NULL)
133
+ `;
134
+ await sql`
135
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
136
+ VALUES ('email', 'email', NULL, 'felipe', '/home/genie/workspace/agents/email', now(), NULL, true, NULL)
137
+ `;
138
+
139
+ await applyMigrationManually();
140
+
141
+ const dirRows = await sql<{ id: string }[]>`
142
+ SELECT id FROM agents WHERE id LIKE 'dir:%' ORDER BY id
143
+ `;
144
+ expect(dirRows.length).toBe(1);
145
+ expect(dirRows[0].id).toBe('dir:email');
146
+ });
147
+
148
+ test('14b: skips bare task-shaped rows (kind=task / archived / no repo_path)', async () => {
149
+ const sql = await getConnection();
150
+ await sql`
151
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
152
+ VALUES
153
+ ('engineer-w2g3', 'engineer', 'engineer', 'master-aware-spawn', '/some/path', now(), 'idle', true, 'team-lead-uuid'),
154
+ ('archived-master', 'archived', '', 'archived', '/some/path', now(), 'archived', true, NULL),
155
+ ('no-repo-master', 'foo', '', 'foo', NULL, now(), NULL, true, NULL)
156
+ `;
157
+
158
+ await applyMigrationManually();
159
+
160
+ const dirRows = await sql<{ id: string }[]>`SELECT id FROM agents WHERE id LIKE 'dir:%'`;
161
+ expect(dirRows.length).toBe(0);
162
+ });
163
+
164
+ // ==========================================================================
165
+ // 14a — bare-name shadow cleanup (heal-not-wipe)
166
+ // ==========================================================================
167
+
168
+ test('14a: archives bare-name shadow when dir:<name> peer exists (no executor)', async () => {
169
+ const sql = await getConnection();
170
+ await sql`
171
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
172
+ VALUES
173
+ ('dir:email', 'email', 'email', 'felipe', '/home/genie/workspace/agents/email', now(), NULL, false, NULL),
174
+ ('email', 'email', NULL, 'felipe', '/home/genie/workspace/agents/email', now(), NULL, true, NULL)
175
+ `;
176
+
177
+ await applyMigrationManually();
178
+
179
+ const bare = await sql<{ state: string | null; auto_resume: boolean }[]>`
180
+ SELECT state, auto_resume FROM agents WHERE id = 'email'
181
+ `;
182
+ expect(bare[0].state).toBe('archived');
183
+ expect(bare[0].auto_resume).toBe(false);
184
+
185
+ // dir: row left intact.
186
+ const dir = await sql<{ state: string | null; repo_path: string }[]>`
187
+ SELECT state, repo_path FROM agents WHERE id = 'dir:email'
188
+ `;
189
+ expect(dir[0].state).toBeNull();
190
+ expect(dir[0].repo_path).toBe('/home/genie/workspace/agents/email');
191
+
192
+ const audit = await sql<{ details: { reason: string; group: string } }[]>`
193
+ SELECT details FROM audit_events
194
+ WHERE actor = 'migration:053_master_backfill_and_shadow_cleanup'
195
+ AND entity_id = 'email'
196
+ AND event_type = 'state_changed'
197
+ `;
198
+ expect(audit.length).toBe(1);
199
+ expect(audit[0].details.reason).toBe('bare_name_shadow_archived');
200
+ expect(audit[0].details.group).toBe('14a');
201
+ });
202
+
203
+ test('14a: NEVER deletes bare-name shadow rows (heal-not-wipe contract)', async () => {
204
+ const sql = await getConnection();
205
+ await sql`
206
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
207
+ VALUES
208
+ ('dir:email', 'email', 'email', 'felipe', '/home/genie/workspace/agents/email', now(), NULL, false, NULL),
209
+ ('email', 'email', NULL, 'felipe', '/home/genie/workspace/agents/email', now(), NULL, true, NULL)
210
+ `;
211
+
212
+ await applyMigrationManually();
213
+
214
+ // Row still exists (just archived). Wholesale deletion is the failure
215
+ // mode the master-aware-spawn wish was born from.
216
+ const stillThere = await sql<{ id: string; state: string | null }[]>`
217
+ SELECT id, state FROM agents WHERE id = 'email'
218
+ `;
219
+ expect(stillThere.length).toBe(1);
220
+ expect(stillThere[0].state).toBe('archived');
221
+ });
222
+
223
+ test('14a: leaves bare-name shadow alone when current_executor_id is set (live peer)', async () => {
224
+ const sql = await getConnection();
225
+ await sql`
226
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
227
+ VALUES
228
+ ('dir:email', 'email', 'email', 'felipe', '/some/path', now(), NULL, false, NULL),
229
+ ('email', 'email', NULL, 'felipe', '/some/path', now(), 'idle', true, NULL)
230
+ `;
231
+ // Attach a live executor to the bare row.
232
+ await sql`
233
+ INSERT INTO executors (id, agent_id, provider, transport, state)
234
+ VALUES ('exec-live', 'email', 'claude', 'tmux', 'running')
235
+ `;
236
+ await sql`UPDATE agents SET current_executor_id = 'exec-live' WHERE id = 'email'`;
237
+
238
+ await applyMigrationManually();
239
+
240
+ const bare = await sql<{ state: string | null }[]>`
241
+ SELECT state FROM agents WHERE id = 'email'
242
+ `;
243
+ expect(bare[0].state).toBe('idle');
244
+ });
245
+
246
+ test('14a: never archives UUID-shaped rows even if dir:<uuid> exists', async () => {
247
+ const sql = await getConnection();
248
+ const uuid = '11111111-2222-3333-4444-555555555555';
249
+ await sql`
250
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
251
+ VALUES
252
+ (${`dir:${uuid}`}, 'weird', 'weird', 'team-dir', '/p', now(), NULL, false, NULL),
253
+ (${uuid}, 'weird', 'weird', 'team-uuid', '/p', now(), 'idle', true, NULL)
254
+ `;
255
+
256
+ await applyMigrationManually();
257
+
258
+ const uuidRow = await sql<{ state: string | null }[]>`
259
+ SELECT state FROM agents WHERE id = ${uuid}
260
+ `;
261
+ expect(uuidRow[0].state).toBe('idle');
262
+ });
263
+
264
+ // ==========================================================================
265
+ // Composite + idempotency
266
+ // ==========================================================================
267
+
268
+ test('14a + 14b run in one pass: bare felipe gets archived AFTER dir:felipe is backfilled', async () => {
269
+ const sql = await getConnection();
270
+ // Only the bare row exists; the migration must first create dir:felipe
271
+ // (14b) and only THEN archive the bare felipe shadow (14a).
272
+ await sql`
273
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
274
+ VALUES ('felipe', 'felipe', NULL, 'felipe', '/home/genie/workspace/agents/felipe', now(), 'idle', true, NULL)
275
+ `;
276
+
277
+ await applyMigrationManually();
278
+
279
+ const dir = await sql<{ id: string }[]>`SELECT id FROM agents WHERE id = 'dir:felipe'`;
280
+ expect(dir.length).toBe(1);
281
+
282
+ const bare = await sql<{ state: string | null; auto_resume: boolean }[]>`
283
+ SELECT state, auto_resume FROM agents WHERE id = 'felipe'
284
+ `;
285
+ expect(bare[0].state).toBe('archived');
286
+ expect(bare[0].auto_resume).toBe(false);
287
+ });
288
+
289
+ test('idempotent: re-running the migration touches zero new rows', async () => {
290
+ const sql = await getConnection();
291
+ await sql`
292
+ INSERT INTO agents (id, role, custom_name, team, repo_path, started_at, state, auto_resume, reports_to)
293
+ VALUES ('felipe', 'felipe', NULL, 'felipe', '/home/genie/workspace/agents/felipe', now(), 'idle', true, NULL)
294
+ `;
295
+
296
+ await applyMigrationManually();
297
+ const firstAuditCount = await sql<{ cnt: number }[]>`
298
+ SELECT count(*)::int AS cnt FROM audit_events
299
+ WHERE actor = 'migration:053_master_backfill_and_shadow_cleanup'
300
+ `;
301
+
302
+ await applyMigrationManually();
303
+ const secondAuditCount = await sql<{ cnt: number }[]>`
304
+ SELECT count(*)::int AS cnt FROM audit_events
305
+ WHERE actor = 'migration:053_master_backfill_and_shadow_cleanup'
306
+ `;
307
+
308
+ expect(secondAuditCount[0].cnt).toBe(firstAuditCount[0].cnt);
309
+ });
310
+ });