@automagik/genie 4.260421.6 → 4.260421.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js CHANGED
@@ -1260,7 +1260,7 @@ Your role adapts based on workspace maturity:
1260
1260
  </constraints>
1261
1261
  `,GENIE_SOUL_TEMPLATE,GENIE_HEARTBEAT_TEMPLATE;var init_templates=__esm(()=>{init_defaults();GENIE_SOUL_TEMPLATE=["# Genie Specialist \u2014 Soul","","You are the genie workspace specialist. You guide users through the genie workflow and orchestrate agents.","","## The Genie Pipeline","","Every idea follows this pipeline:","","```","brainstorm \u2192 wish \u2192 work \u2192 review \u2192 ship","```","","1. **Brainstorm** \u2014 Explore the idea. Use `/brainstorm` to think through scope, tradeoffs, and approach.","2. **Wish** \u2014 Structure the idea into an actionable plan with acceptance criteria, execution groups, and validation commands. Use `/wish` to create one.","3. **Work** \u2014 Execute the wish. Use `/work` to dispatch engineers per execution group.","4. **Review** \u2014 Validate the work against wish criteria. Use `/review` to check compliance.","5. **Ship** \u2014 Merge, release, deploy. The pipeline ensures quality before shipping.","","## Genie Commands Reference","","### Agent Management","```bash","genie spawn <name> # Start an agent","genie kill <name> # Force kill an agent","genie stop <name> # Stop (preserves session)","genie resume [name] # Resume a suspended agent","genie ls # List agents with status","genie log [agent] # Unified observability feed","genie read <name> # Read terminal output","genie history <name> # Compressed session history","genie answer <name> <choice> # Answer a prompt for an agent","```","","### Agent Communication","```bash","genie agent send '<msg>' --to <name> # Direct message","genie agent send '<msg>' --broadcast # Team broadcast","genie agent inbox # View inbox","genie agent brief --team <name> # Cold-start summary","```","","### Team Orchestration","```bash","genie team create <name> --repo <path> --wish <slug> # Launch autonomous team","genie team hire <name> --team <team> # Add to team","genie team fire <name> --team <team> # Remove from team","genie team list # List teams","genie team disband <name> # Disband team","```","","### Task & Wish Management","```bash","genie task create --title 'x' # Create task","genie task list # List tasks","genie task status <slug> # Wish group status","genie task done <ref> # Mark done","genie task board # Planning board","```","","### Workspace","```bash","genie init # Initialize workspace","genie init agent <name> # Scaffold new agent","genie serve # Start infrastructure","genie doctor # Diagnostic checks","```","","## Concierge \u2192 Orchestrator Transition","","Detect workspace maturity and adapt:","","**Concierge mode** activates when:","- Workspace has 0-1 agents (just the default genie agent)","- No wishes exist yet","- User appears new to genie","","In concierge mode:","- Explain concepts with examples","- Suggest creating a first agent or brainstorming a first wish","- Walk through the pipeline step by step","","**Orchestrator mode** activates when:","- Workspace has 2+ agents","- Wishes exist with execution groups","- User gives direct commands","","In orchestrator mode:","- Route work to the right agents","- Monitor progress across teams","- Summarize status concisely","- Suggest next pipeline steps based on current state","","## Agent Analysis Capability","","When invoked in a workspace with existing agents (from genie or other systems), analyze their setup:","","### Analysis Process","1. List all directories under `agents/` (and any discovered via tree scan)","2. For each agent directory, check:"," - Has `AGENTS.md`? (identity file with frontmatter)"," - Has `SOUL.md`? (personality and knowledge)"," - Has `HEARTBEAT.md`? (autonomous checklist)"," - Has `.claude/settings.local.json`? (Claude Code config)"," - Frontmatter fields present vs. missing","3. Compare against genie conventions:"," - Missing files \u2192 propose creation with templates"," - Incomplete frontmatter \u2192 propose mini-wizard"," - Non-standard structure \u2192 explain conventions, offer migration","4. Present proposals as a checklist \u2014 never auto-modify",""].join(`
1262
1262
  `),GENIE_HEARTBEAT_TEMPLATE=["# Heartbeat \u2014 Genie Specialist","","Run this checklist on every iteration. Exit early if nothing actionable.","","## Checklist","","### 1. Workspace State Check","Verify workspace health before doing anything else.","- Is `genie serve` running? If not, suggest starting it.","- Are there registered agents? List them with `genie ls`.","- Any agents in error/crashed state? Flag for user attention.","","### 2. Pending Agents Check","Look for agents waiting to be initialized.","- Check `.genie/pending-agents.json` for queued discoveries.","- If pending agents exist, notify the user and offer to initialize them.","- If new `AGENTS.md` files appeared outside `agents/`, flag for import.","","### 3. Wish Status Check","Review active work across the workspace.","- Check `genie task board` for in-progress wishes.","- For each active wish, check execution group progress.","- Flag blocked groups or stale tasks (no progress in 30+ minutes).","- Summarize: X wishes active, Y groups complete, Z blocked.","","### 4. Generate Suggestions","Based on workspace state, suggest the next most valuable action:",'- **Empty workspace** \u2192 "Start with /brainstorm to explore an idea"','- **Has brainstorm, no wish** \u2192 "Ready to structure this? Run /wish"','- **Has wish, no workers** \u2192 "Dispatch workers with /work"','- **Work complete** \u2192 "Time to review: /review"','- **Review passed** \u2192 "Ship it \u2014 merge the PR"','- **Agents from other systems** \u2192 "I can analyze your agents \u2014 want a compatibility report?"',"","### 5. Exit If Nothing Actionable","If workspace is healthy, no pending agents, no active wishes, and no suggestions \u2014 exit.","Don't create busywork. The user will invoke you when needed.",""].join(`
1263
- `)});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync2}from"child_process";import{createHash as createHash4,randomUUID as randomUUID7}from"crypto";import{existsSync as existsSync28}from"fs";import{basename as basename5,join as join31}from"path";function shortPathHash(p){return createHash4("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join31(process.cwd(),"AGENTS.md");if(existsSync28(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName,leaderName,sessionId){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName,leaderName,sessionId})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID7();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0,leaderName,freshId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync2(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync8,readFileSync:readFileSync19,writeFileSync:writeFileSync12}=await import("fs"),{join:join32}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join32(process.env.HOME??"/root",".claude","teams"),teams=readdirSync8(teamsDir);for(let team of teams)try{let configPath2=join32(teamsDir,team,"config.json"),raw=readFileSync19(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync12(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(shouldResume){let continueName=sanitizeTeamName(windowName);await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,continueName,leaderName,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else{let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0,leaderName,sessionId),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join31(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync29}from"fs";import{join as join32}from"path";function getSystemPromptFile(workingDir){let agentsPath=join32(workingDir,"AGENTS.md");if(existsSync29(agentsPath))return agentsPath;return null}async function ensureSession(teamName){let current=await getCurrentSessionName();if(current)return current;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}let sessionName=sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let sessionName=await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let currentSession=await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:currentSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),{sessionId,shouldResume}=await resolveOrMintLeadSessionId(teamName,workingDir);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:shouldResume});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({teamName,leaderName,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){let sanitizedTeam=sanitizeTeamName(opts.teamName),agentIdentity=await findOrCreateAgent(opts.leaderName,sanitizedTeam,opts.leaderName);await terminateActiveExecutor(agentIdentity.id);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);return deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir22}from"os";import{join as join33}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join33(homedir22(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join33(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
1263
+ `)});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync2}from"child_process";import{createHash as createHash4,randomUUID as randomUUID7}from"crypto";import{existsSync as existsSync28}from"fs";import{basename as basename5,join as join31}from"path";function shortPathHash(p){return createHash4("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join31(process.cwd(),"AGENTS.md");if(existsSync28(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName,leaderName,sessionId){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName,leaderName,sessionId})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID7();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0,leaderName,freshId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync2(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync8,readFileSync:readFileSync19,writeFileSync:writeFileSync12}=await import("fs"),{join:join32}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join32(process.env.HOME??"/root",".claude","teams"),teams=readdirSync8(teamsDir);for(let team of teams)try{let configPath2=join32(teamsDir,team,"config.json"),raw=readFileSync19(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync12(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(shouldResume){let continueName=sanitizeTeamName(windowName);await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,continueName,leaderName,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else{let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0,leaderName,sessionId),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join31(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync29}from"fs";import{join as join32}from"path";function getSystemPromptFile(workingDir){let agentsPath=join32(workingDir,"AGENTS.md");if(existsSync29(agentsPath))return agentsPath;return null}async function ensureSession(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),{sessionId,shouldResume}=await resolveOrMintLeadSessionId(teamName,workingDir);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:shouldResume});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({teamName,leaderName,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){let sanitizedTeam=sanitizeTeamName(opts.teamName),agentIdentity=await findOrCreateAgent(opts.leaderName,sanitizedTeam,opts.leaderName);await terminateActiveExecutor(agentIdentity.id);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);return deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir22}from"os";import{join as join33}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join33(homedir22(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join33(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
1264
1264
  ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),suggestion)console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`);else console.error("No reachable leader found \u2014 sender is not bound to any team.")}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
1265
1265
  Examples:
1266
1266
  genie send 'start task #3' --to engineer # Message a specific agent
@@ -2619,7 +2619,7 @@ ${label} <TODO>
2619
2619
  `;if(rows.length===0){let agentName=process.env.GENIE_AGENT_NAME;if(agentName){let fallback=await tx`
2620
2620
  SELECT id FROM executors
2621
2621
  WHERE agent_id = ${agentName}
2622
- ORDER BY started_at DESC
2622
+ ORDER BY started_at DESC, ctid DESC
2623
2623
  LIMIT 1
2624
2624
  FOR UPDATE
2625
2625
  `;if(fallback.length>0){effectiveId=fallback[0].id,rows=await tx`
@@ -4177,7 +4177,7 @@ Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s2
4177
4177
  No fixable violations to apply.`);process.exit(report.summary.total>0?1:0)}if(options.dryRun){if(options.json)console.log(JSON.stringify({...report,dryRun:!0,diff:renderDiff(markdown,fixed)}));else console.log(formatLintReport(report,{color:process.stdout.isTTY??!1,path:wishPath})),console.log(`
4178
4178
  --- Dry-run diff (${wishPath}) ---`),console.log(renderDiff(markdown,fixed)),console.log(`
4179
4179
  File not modified (--dry-run).`);process.exit(report.summary.total>0?1:0)}await writeFile9(wishPath,fixed,"utf-8");let docOrError2;try{docOrError2=parseWish(fixed)}catch(err){if(err instanceof WishParseError)docOrError2=err;else throw err}let report2=lintWish(docOrError2,fixed,lintOpts);report2={...report2,wish:slug,file:wishPath};let fixedCount=report.summary.fixable;if(options.json)console.log(JSON.stringify({...report2,fixedViolations:fixedCount}));else console.log(`\u2705 Applied ${fixedCount} fix(es) to ${wishPath}`),console.log(""),console.log(formatLintReport(report2,{color:process.stdout.isTTY??!1,path:wishPath}));let remainingErrors=report2.violations.filter((v)=>v.severity==="error").length;process.exit(remainingErrors>0?1:0)}if(options.json)console.log(JSON.stringify(report));else console.log(formatLintReport(report,{color:process.stdout.isTTY??!1,path:wishPath}));let errors3=report.violations.filter((v)=>v.severity==="error").length;process.exit(errors3>0?1:0)}async function wishParseCommand(slug,options){try{let doc=parseWishFile(slug),json2=options.json?JSON.stringify(doc):JSON.stringify(doc,null,2);console.log(json2)}catch(error2){if(error2 instanceof WishParseError){let payload={error:error2.message,rule:error2.rule,line:error2.line,column:error2.column??null,file:error2.file??null};if(options.json)console.log(JSON.stringify(payload));else{if(console.error(`\u274C Parse failed (${payload.rule}): ${payload.error}`),payload.file)console.error(` File: ${payload.file}`);if(payload.line)console.error(` Line: ${payload.line}`)}process.exit(1)}let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function isWishFile(wishPath){try{return(await stat6(wishPath)).isFile()}catch{return!1}}function parseWishSummary(slug){try{let doc=parseWishFile(slug);return{status:doc.metadata.status??"-",groupCount:String(doc.executionGroups.length)}}catch(error2){return{status:error2 instanceof WishParseError?"malformed":"error",groupCount:"-"}}}async function loadStateCounts(slug){try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(slug);if(!state)return{ready:0,inProgress:0,done:0};let ready=0,inProgress=0,done=0;for(let g of Object.values(state.groups))if(g.status==="ready")ready++;else if(g.status==="in_progress")inProgress++;else if(g.status==="done")done++;return{ready,inProgress,done}}catch{return{ready:0,inProgress:0,done:0}}}async function wishListCommand(){let wishesRoot=join64(process.cwd(),".genie","wishes");if(!existsSync54(wishesRoot))console.error(`\u274C Wishes directory not found: ${wishesRoot}`),process.exit(1);let entries=await readdir7(wishesRoot),rows=[];for(let entry2 of entries.sort()){if(entry2.startsWith("_")||entry2.startsWith("."))continue;if(!await isWishFile(join64(wishesRoot,entry2,"WISH.md")))continue;let{status,groupCount}=parseWishSummary(entry2),{ready,inProgress,done}=await loadStateCounts(entry2);rows.push({slug:entry2,status,groupCount,ready,inProgress,done})}if(rows.length===0){console.log("No wishes found under .genie/wishes/");return}let slugW=Math.max(4,...rows.map((r)=>r.slug.length)),statusW=Math.max(6,...rows.map((r)=>r.status.length)),pad=(s2,n)=>s2+" ".repeat(Math.max(0,n-s2.length));console.log(` ${pad("SLUG",slugW)} ${pad("STATUS",statusW)} GROUPS READY IN-PROG DONE`),console.log(` ${"\u2500".repeat(slugW+statusW+32)}`);for(let r of rows)console.log(` ${pad(r.slug,slugW)} ${pad(r.status,statusW)} ${pad(r.groupCount,6)} ${pad(String(r.ready),5)} ${pad(String(r.inProgress),7)} ${r.done}`);console.log(""),console.log(` Total: ${rows.length} wishes`)}function registerWishCommands(program2){let wish=program2.command("wish").description("Wish lifecycle management");wish.command("new <slug>").description("Scaffold a new WISH.md from templates/wish-template.md").option("--force","Overwrite an existing wish directory").action(async(slug,options)=>{await wishNewCommand(slug,options)}),wish.command("lint <slug>").description("Structural health check (stub \u2014 full implementation in Group 3)").option("--json","Emit machine-readable JSON output").option("--fix","Auto-repair deterministic violations").option("--dry-run","With --fix: print diff without writing").option("--allow-todo-placeholders","Pass <TODO> placeholders without emitting todo-placeholder-remaining").action(async(slug,options)=>{await wishLintCommand(slug,options)}),wish.command("parse <slug>").description("Parse WISH.md and emit the WishDocument as JSON").option("--json","One-line JSON output (default: pretty-printed)").action(async(slug,options)=>{await wishParseCommand(slug,options)}),wish.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),wish.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),wish.command("reset <ref>").option("-y, --yes","Skip confirmation prompt (required in non-interactive mode)").description("Reset wish state. <slug>#<group> resets one in-progress group; bare <slug> wipes the wish and recreates from current WISH.md").action(async(ref,options)=>{await resetAction(ref,options)}),wish.command("list").description("Enumerate all wishes with status, group counts, and progress").action(async()=>{await wishListCommand()})}var _T_BOOT=Date.now();try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}function parseNumericFlag2(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}if(process.env.GENIE_PROFILE_DB)console.error(`[profile] imports=${Date.now()-_T_BOOT}ms`);var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);program2.option("--no-interactive","Disable interactive prompts (exit 2 instead of prompting)");program2.configureHelp({sortSubcommands:!0,showGlobalOptions:!0});program2.configureOutput({outputError:(str5,write)=>{let cmd=program2.commands.find((c)=>process.argv.slice(2,6).includes(c.name())),prefix=cmd?`genie ${cmd.name()}`:"genie";write(`\x1B[31mError (${prefix}): ${str5}\x1B[0m
4180
- `)}});async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync6}=await import("child_process"),result2=spawnSync6("sh",["-c",cmd],{stdio:"inherit"});if(result2.status)process.exit(result2.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").option("--observability","Report partition health + GENIE_WIDE_EMIT flag state").option("--json","Emit JSON instead of human output (pairs with --observability)").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentCommands(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerDispatchGroupCommands(program2);registerWishCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBrainCommands(program2);registerBriefCommands(program2);registerApprovalCommands(program2);program2.command("done [ref]").description("Close the current turn (inside an agent session) or mark a wish group done (team-lead, <slug>#<group>)").action(async(ref)=>{let{doneAction:doneAction2}=await Promise.resolve().then(() => (init_done(),exports_done));await doneAction2(ref)});program2.command("blocked").description("Close the current turn with outcome=blocked").requiredOption("--reason <message>","Why the turn is blocked").action(async(options)=>{let{blockedAction:blockedAction2}=await Promise.resolve().then(() => (init_blocked(),exports_blocked));await blockedAction2(options)});program2.command("failed").description("Close the current turn with outcome=failed").requiredOption("--reason <message>","Why the turn failed").action(async(options)=>{let{failedAction:failedAction2}=await Promise.resolve().then(() => (init_failed(),exports_failed));await failedAction2(options)});program2.command("pane-trap").description("Internal: write clean_exit_unverified outcome for a dying pane/shell. Invoked by the tmux pane-died hook and the inline shell EXIT trap.").option("--pane-id <id>","tmux pane id (%N) \u2014 resolved to executor via executors.tmux_pane_id").option("--executor-id <id>","explicit executor UUID (preferred when available)").option("--reason <reason>","trap source: pane_died or shell_exit","pane_died").action(async(options)=>{let{paneTrapAction:paneTrapAction2}=await Promise.resolve().then(() => (init_pane_trap2(),exports_pane_trap));await paneTrapAction2(options)});installWorkspaceCheck(program2);var auditTimers=new Map,auditSpans=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{}),(async()=>{try{let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(!isWideEmitEnabled2())return;let{startSpan:startSpan2}=await Promise.resolve().then(() => (init_emit(),exports_emit)),{getAmbient:getAmbient2}=await Promise.resolve().then(() => (init_trace_context(),exports_trace_context)),handle=startSpan2("cli.command",{command:name,args:actionCommand.args??[],cwd:process.cwd()},{severity:"debug",source_subsystem:"cli",ctx:getAmbient2()??void 0,agent:getActor()});auditSpans.set(name,handle)}catch{}})()});program2.hook("postAction",async(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name);try{let{isConnected:isConnected2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!isConnected2())return;await recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs})}catch{}try{let handle=auditSpans.get(name);if(auditSpans.delete(name),!handle)return;let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(!isWideEmitEnabled2())return;let{endSpan:endSpan2,flushNow:flushNow2}=await Promise.resolve().then(() => (init_emit(),exports_emit));endSpan2(handle,{exit_code:0,duration_ms:durationMs??0},{severity:"debug",source_subsystem:"cli",agent:getActor()}),await flushNow2()}catch{}});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--stream","Stream SDK messages to stdout in real-time (claude-sdk provider)").option("--stream-format <format>","Streaming output format: text, json, ndjson (default: text)","text").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag2("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag2("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").option("--prompt <text>","Initial prompt to send as the first user message").addHelpText("after",`
4180
+ `)}});async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync6}=await import("child_process"),result2=spawnSync6("sh",["-c",cmd],{stdio:"inherit"});if(result2.status)process.exit(result2.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").option("--observability","Report partition health + GENIE_WIDE_EMIT flag state").option("--json","Emit JSON instead of human output (pairs with --observability)").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentCommands(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerDispatchGroupCommands(program2);registerWishCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBrainCommands(program2);registerBriefCommands(program2);registerApprovalCommands(program2);program2.command("done [ref]").description("Close the current turn (inside an agent session) or mark a wish group done (team-lead, <slug>#<group>)").action(async(ref)=>{let{doneAction:doneAction2}=await Promise.resolve().then(() => (init_done(),exports_done));await doneAction2(ref)});program2.command("blocked").description("Close the current turn with outcome=blocked").requiredOption("--reason <message>","Why the turn is blocked").action(async(options)=>{let{blockedAction:blockedAction2}=await Promise.resolve().then(() => (init_blocked(),exports_blocked));await blockedAction2(options)});program2.command("failed").description("Close the current turn with outcome=failed").requiredOption("--reason <message>","Why the turn failed").action(async(options)=>{let{failedAction:failedAction2}=await Promise.resolve().then(() => (init_failed(),exports_failed));await failedAction2(options)});program2.command("pane-trap").description("Internal: write clean_exit_unverified outcome for a dying pane/shell. Invoked by the tmux pane-died hook and the inline shell EXIT trap.").option("--pane-id <id>","tmux pane id (%N) \u2014 resolved to executor via executors.tmux_pane_id").option("--executor-id <id>","explicit executor UUID (preferred when available)").option("--reason <reason>","trap source: pane_died or shell_exit","pane_died").action(async(options)=>{let{paneTrapAction:paneTrapAction2}=await Promise.resolve().then(() => (init_pane_trap2(),exports_pane_trap));await paneTrapAction2(options)});installWorkspaceCheck(program2);var auditTimers=new Map,auditSpans=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{}),(async()=>{try{let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(!isWideEmitEnabled2())return;let{startSpan:startSpan2}=await Promise.resolve().then(() => (init_emit(),exports_emit)),{getAmbient:getAmbient2}=await Promise.resolve().then(() => (init_trace_context(),exports_trace_context)),handle=startSpan2("cli.command",{command:name,args:actionCommand.args??[],cwd:process.cwd()},{severity:"debug",source_subsystem:"cli",ctx:getAmbient2()??void 0,agent:getActor()});auditSpans.set(name,handle)}catch{}})()});program2.hook("postAction",async(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name);try{let{isConnected:isConnected2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!isConnected2())return;await recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs})}catch{}let handle=auditSpans.get(name);auditSpans.delete(name);try{if(handle){let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(isWideEmitEnabled2()){let{endSpan:endSpan2}=await Promise.resolve().then(() => (init_emit(),exports_emit));endSpan2(handle,{exit_code:0,duration_ms:durationMs??0},{severity:"debug",source_subsystem:"cli",agent:getActor()})}}}catch{}try{let{flushNow:flushNow2}=await Promise.resolve().then(() => (init_emit(),exports_emit));await flushNow2()}catch{}});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--stream","Stream SDK messages to stdout in real-time (claude-sdk provider)").option("--stream-format <format>","Streaming output format: text, json, ndjson (default: text)","text").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag2("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag2("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").option("--prompt <text>","Initial prompt to send as the first user message").addHelpText("after",`
4181
4181
  Examples:
4182
4182
  genie spawn engineer # Spawn built-in engineer role
4183
4183
  genie spawn researcher --model sonnet # Spawn with model override
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260421.6",
3
+ "version": "4.260421.7",
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.260421.6",
3
+ "version": "4.260421.7",
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.260421.6",
3
+ "version": "4.260421.7",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",