@automagik/genie 4.260323.4 → 4.260324.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260323.4",
13
+ "version": "4.260324.1",
14
14
  "source": "./plugins/genie",
15
15
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
16
16
  }
package/dist/genie.js CHANGED
@@ -41,7 +41,7 @@ disable_paste_burst = true
41
41
 
42
42
  ${content}`;if(params.extraArgs){let fileIdx=params.extraArgs.indexOf("--append-system-prompt-file");if(fileIdx!==-1&&params.extraArgs[fileIdx+1])content=`${content}
43
43
 
44
- ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(fileIdx,2)}writeFileSync5(promptFile,content);let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg(promptFile))}else if(params.systemPromptFile){let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg(params.systemPromptFile))}}function buildClaudeCommand(params){preflightCheck("claude");let parts=["claude","--dangerously-skip-permissions"],env={};if(params.role)env.GENIE_AGENT_NAME=params.role;if(params.team)env.GENIE_TEAM=params.team;if(params.nativeTeam?.enabled)appendNativeTeamFlags(parts,env,params.nativeTeam,params);if(params.resume)parts.push("--resume",escapeShellArg(params.resume));else if(params.sessionId)parts.push("--session-id",escapeShellArg(params.sessionId));if(params.role)parts.push("--agent",escapeShellArg(params.role));if(params.model)parts.push("--model",escapeShellArg(params.model));if(params.name)parts.push("--name",escapeShellArg(params.name));if(appendSystemPromptFlags(parts,params),params.extraArgs)for(let arg of params.extraArgs)parts.push(escapeShellArg(arg));if(params.initialPrompt)parts.push(escapeShellArg(params.initialPrompt));return{command:parts.join(" "),provider:"claude",env:Object.keys(env).length>0?env:void 0,meta:{role:params.role,skill:params.skill}}}function buildCodexCommand(params){preflightCheck("codex");let parts=["codex"];if(parts.push("--yolo"),parts.push("--no-alt-screen"),params.extraArgs)for(let arg of params.extraArgs)parts.push(escapeShellArg(arg));let promptParts=[`Genie worker. Team: ${params.team}.`];if(params.role)promptParts.push(`Role: ${params.role}.`);if(params.skill)promptParts.push(`Execute the ${params.skill} skill instructions.`);let prompt2=promptParts.join(" ");return parts.push(escapeShellArg(prompt2)),{command:parts.join(" "),provider:"codex",meta:{role:params.role,skill:params.skill}}}function buildLaunchCommand(params){let validated=validateSpawnParams(params);switch(validated.provider){case"claude":return buildClaudeCommand(validated);case"codex":return buildCodexCommand(validated);default:throw Error(`Unknown provider "${validated.provider}". Valid providers: claude, codex`)}}var CLAUDE_TEAM_COLORS,spawnParamsSchema;var init_provider_adapters=__esm(()=>{init_zod();CLAUDE_TEAM_COLORS=["red","blue","green","yellow","purple","orange","pink","cyan"],spawnParamsSchema=exports_external.object({provider:exports_external.enum(["claude","codex"]),team:exports_external.string().min(1,"Team name is required"),role:exports_external.string().optional(),skill:exports_external.string().optional(),extraArgs:exports_external.array(exports_external.string()).optional(),nativeTeam:exports_external.object({enabled:exports_external.boolean(),parentSessionId:exports_external.string().optional(),color:exports_external.string().optional(),agentType:exports_external.string().optional(),planModeRequired:exports_external.boolean().optional(),permissionMode:exports_external.string().optional(),agentName:exports_external.string().optional()}).optional(),sessionId:exports_external.string().uuid().optional(),resume:exports_external.string().optional(),systemPromptFile:exports_external.string().optional(),systemPrompt:exports_external.string().optional(),promptMode:exports_external.enum(["system","append"]).optional(),model:exports_external.string().optional(),initialPrompt:exports_external.string().optional(),name:exports_external.string().optional()})});import{existsSync as existsSync10}from"fs";import{mkdir as mkdir3,readFile as readFile2,readdir,rm,stat as stat2,unlink as unlink3,writeFile as writeFile2}from"fs/promises";import{homedir as homedir10}from"os";import{join as join10}from"path";function claudeConfigDir(){return process.env.CLAUDE_CONFIG_DIR??join10(homedir10(),".claude")}function teamsBaseDir(){return join10(claudeConfigDir(),"teams")}function sanitizeTeamName(name){return name.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase()}function teamDir(teamName){return join10(teamsBaseDir(),sanitizeTeamName(teamName))}function configPath(teamName){return join10(teamDir(teamName),"config.json")}function inboxesDir(teamName){return join10(teamDir(teamName),"inboxes")}function inboxPath(teamName,agentName){return join10(inboxesDir(teamName),`${sanitizeTeamName(agentName)}.json`)}function lockPath(filePath){return`${filePath}.lock`}async function acquireLock2(path){let lock=lockPath(path),deadline=Date.now()+LOCK_TIMEOUT_MS2;while(Date.now()<deadline)try{await writeFile2(lock,String(process.pid),{flag:"wx"});return}catch{let jitter=Math.floor(Math.random()*LOCK_POLL_MS);await new Promise((r)=>setTimeout(r,LOCK_POLL_MS+jitter))}console.warn(`[claude-native-teams] Force-acquiring stale lock: ${lock}`),await writeFile2(lock,String(process.pid))}async function releaseLock(path){try{await unlink3(lockPath(path))}catch{}}async function loadConfig(teamName){try{let content=await readFile2(configPath(teamName),"utf-8");return JSON.parse(content)}catch(err){if(err instanceof Error&&"code"in err&&err.code==="ENOENT")return null;let message=err instanceof Error?err.message:String(err);return console.warn(`[claude-native-teams] Failed to load config for "${teamName}": ${message}`),null}}async function saveConfig(teamName,config){await writeFile2(configPath(teamName),JSON.stringify(config,null,2))}async function ensureNativeTeam(teamName,description,leadSessionId){let dir=teamDir(teamName),inboxDir=inboxesDir(teamName);await mkdir3(dir,{recursive:!0}),await mkdir3(inboxDir,{recursive:!0});let existing=await loadConfig(teamName);if(existing)return existing;let sanitized=sanitizeTeamName(teamName),config={name:sanitized,description,createdAt:Date.now(),leadAgentId:`team-lead@${sanitized}`,leadSessionId,members:[]};return await saveConfig(teamName,config),config}async function registerNativeMember(teamName,member){let config=await loadConfig(teamName);if(!config)throw Error(`Native team "${teamName}" not found`);let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(member.agentName)}@${sanitized}`;config.members=config.members.filter((m)=>m.agentId!==agentId),config.members.push({agentId,name:sanitizeTeamName(member.agentName),agentType:member.agentType??"general-purpose",joinedAt:Date.now(),tmuxPaneId:member.tmuxPaneId,cwd:member.cwd??process.cwd(),backendType:"tmux",color:member.color,planModeRequired:member.planModeRequired??!1,isActive:!0}),await saveConfig(teamName,config);let inbox=inboxPath(teamName,member.agentName);if(!existsSync10(inbox))await writeFile2(inbox,"[]")}async function unregisterNativeMember(teamName,agentName){let config=await loadConfig(teamName);if(!config)return;let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(agentName)}@${sanitized}`,member=config.members.find((m)=>m.agentId===agentId);if(member)member.isActive=!1;await saveConfig(teamName,config)}async function writeNativeInbox(teamName,agentName,message){let path=inboxPath(teamName,agentName);await mkdir3(inboxesDir(teamName),{recursive:!0}),await acquireLock2(path);try{let messages=[];try{let content=await readFile2(path,"utf-8");messages=JSON.parse(content)}catch{}messages.push(message),await writeFile2(path,JSON.stringify(messages,null,2))}finally{await releaseLock(path)}}async function assignColor(teamName){let config=await loadConfig(teamName);if(!config)return CLAUDE_TEAM_COLORS[0];let usedColors=new Set(config.members.map((m)=>m.color));for(let color of CLAUDE_TEAM_COLORS)if(!usedColors.has(color))return color;return CLAUDE_TEAM_COLORS[config.members.length%CLAUDE_TEAM_COLORS.length]}async function clearNativeInbox(teamName,agentName){let path=inboxPath(teamName,agentName);await acquireLock2(path);try{await writeFile2(path,"[]")}finally{await releaseLock(path)}}async function deleteNativeTeam(teamName){let dir=teamDir(teamName);if(!existsSync10(dir))return!1;return await rm(dir,{recursive:!0,force:!0}),!0}function sanitizePath(p){return p.replace(/[^a-zA-Z0-9]/g,"-")}async function discoverClaudeSessionId(cwd){let envSessionId=process.env.CLAUDE_CODE_SESSION_ID;if(envSessionId)return envSessionId;let projectDir=join10(claudeConfigDir(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let newest=null;for(let name of jsonls){let s=await stat2(join10(projectDir,name));if(!newest||s.mtimeMs>newest.mtime)newest={name,mtime:s.mtimeMs}}if(!newest)return null;return newest.name.replace(".jsonl","")}catch{return null}}function isInsideClaudeCode(){return process.env.CLAUDECODE==="1"}async function discoverTeamName(cwd){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let sessionId=await discoverClaudeSessionId(cwd);if(!sessionId)return null;let base=teamsBaseDir();try{let teams=await readdir(base);for(let name of teams){let cfgPath=join10(base,name,"config.json");try{let content=await readFile2(cfgPath,"utf-8"),config=JSON.parse(content);if(config.leadSessionId===sessionId)return config.name}catch{}}}catch{}return null}async function registerAsTeamLead(teamName,opts){let sessionId=await discoverClaudeSessionId(opts?.cwd);if(!sessionId)throw Error("Could not discover Claude Code session ID. Are you running inside Claude Code with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1?");let config=await ensureNativeTeam(teamName,`Genie team: ${teamName}`,sessionId);if(config.leadSessionId!==sessionId)config.leadSessionId=sessionId,await saveConfig(teamName,config);let leadAgentId=`team-lead@${sanitizeTeamName(teamName)}`,existingLead=config.members.find((m)=>m.agentId===leadAgentId),resolvedPaneId=opts?.tmuxPaneId??process.env.TMUX_PANE;if(!existingLead||!existingLead.isActive)await registerNativeMember(teamName,{agentName:"team-lead",agentType:"general-purpose",color:opts?.color??"blue",tmuxPaneId:resolvedPaneId,cwd:opts?.cwd??process.cwd()});else if(resolvedPaneId&&existingLead.tmuxPaneId!==resolvedPaneId)existingLead.tmuxPaneId=resolvedPaneId,await saveConfig(teamName,config);let inbox=inboxPath(teamName,"team-lead");if(!existsSync10(inbox))await writeFile2(inbox,"[]");let finalConfig=await loadConfig(teamName);if(!finalConfig)throw Error(`Failed to load config for team "${teamName}" after creation`);return{sessionId,config:finalConfig}}var LOCK_TIMEOUT_MS2=5000,LOCK_POLL_MS=50;var init_claude_native_teams=__esm(()=>{init_provider_adapters()});var exports_team_lead_command={};__export(exports_team_lead_command,{shellQuote:()=>shellQuote,sessionExists:()=>sessionExists,ccProjectDirName:()=>ccProjectDirName,buildTeamLeadCommand:()=>buildTeamLeadCommand});import{readFileSync as readFileSync6,readdirSync as readdirSync2}from"fs";import{basename,join as join11}from"path";function shellQuote(s){return`'${s.replace(/'/g,"'\\''")}'`}function buildTeamLeadCommand(teamName,options){let sanitized=sanitizeTeamName(teamName),qTeam=shellQuote(sanitized),folderName=basename(process.cwd()),parts=["CLAUDECODE=1","CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1",`GENIE_TEAM=${qTeam}`,`GENIE_AGENT_NAME=${shellQuote(folderName)}`,"claude",`--agent-id ${shellQuote(`team-lead@${sanitized}`)}`,"--agent-name team-lead",`--team-name ${qTeam}`,"--agent-type team-lead","--dangerously-skip-permissions"];if(parts.push(`--name ${shellQuote(sanitized)}`),options?.continueName)parts.push(`--resume ${shellQuote(options.continueName)}`);else if(options?.sessionId)parts.push(`--session-id ${shellQuote(options.sessionId)}`);if(options?.systemPromptFile){let promptFlag=(options?.promptMode??loadGenieConfigSync().promptMode)==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(`${promptFlag} ${shellQuote(options.systemPromptFile)}`)}return parts.join(" ")}function ccProjectDirName(dir){return dir.replace(/\//g,"-")}function fileHasSessionName(filePath,needle){try{let lines=readFileSync6(filePath,"utf-8").split(`
44
+ ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(fileIdx,2)}writeFileSync5(promptFile,content);let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg(promptFile))}else if(params.systemPromptFile){let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg(params.systemPromptFile))}}function buildClaudeCommand(params){preflightCheck("claude");let parts=["claude","--dangerously-skip-permissions"],env={};if(params.role)env.GENIE_AGENT_NAME=params.role;if(params.team)env.GENIE_TEAM=params.team;if(params.nativeTeam?.enabled)appendNativeTeamFlags(parts,env,params.nativeTeam,params);if(params.resume)parts.push("--resume",escapeShellArg(params.resume));else if(params.sessionId)parts.push("--session-id",escapeShellArg(params.sessionId));if(params.role)parts.push("--agent",escapeShellArg(params.role));if(params.model)parts.push("--model",escapeShellArg(params.model));if(params.name)parts.push("--name",escapeShellArg(params.name));if(appendSystemPromptFlags(parts,params),params.extraArgs)for(let arg of params.extraArgs)parts.push(escapeShellArg(arg));if(params.initialPrompt)parts.push(escapeShellArg(params.initialPrompt));return{command:parts.join(" "),provider:"claude",env:Object.keys(env).length>0?env:void 0,meta:{role:params.role,skill:params.skill}}}function buildCodexCommand(params){preflightCheck("codex");let parts=["codex"];if(parts.push("--yolo"),parts.push("--no-alt-screen"),params.extraArgs)for(let arg of params.extraArgs)parts.push(escapeShellArg(arg));let promptParts=[`Genie worker. Team: ${params.team}.`];if(params.role)promptParts.push(`Role: ${params.role}.`);if(params.skill)promptParts.push(`Execute the ${params.skill} skill instructions.`);let prompt2=promptParts.join(" ");return parts.push(escapeShellArg(prompt2)),{command:parts.join(" "),provider:"codex",meta:{role:params.role,skill:params.skill}}}function buildLaunchCommand(params){let validated=validateSpawnParams(params);switch(validated.provider){case"claude":return buildClaudeCommand(validated);case"codex":return buildCodexCommand(validated);default:throw Error(`Unknown provider "${validated.provider}". Valid providers: claude, codex`)}}var CLAUDE_TEAM_COLORS,spawnParamsSchema;var init_provider_adapters=__esm(()=>{init_zod();CLAUDE_TEAM_COLORS=["red","blue","green","yellow","purple","orange","pink","cyan"],spawnParamsSchema=exports_external.object({provider:exports_external.enum(["claude","codex"]),team:exports_external.string().min(1,"Team name is required"),role:exports_external.string().optional(),skill:exports_external.string().optional(),extraArgs:exports_external.array(exports_external.string()).optional(),nativeTeam:exports_external.object({enabled:exports_external.boolean(),parentSessionId:exports_external.string().optional(),color:exports_external.string().optional(),agentType:exports_external.string().optional(),planModeRequired:exports_external.boolean().optional(),permissionMode:exports_external.string().optional(),agentName:exports_external.string().optional()}).optional(),sessionId:exports_external.string().uuid().optional(),resume:exports_external.string().optional(),systemPromptFile:exports_external.string().optional(),systemPrompt:exports_external.string().optional(),promptMode:exports_external.enum(["system","append"]).optional(),model:exports_external.string().optional(),initialPrompt:exports_external.string().optional(),name:exports_external.string().optional()})});var exports_claude_native_teams={};__export(exports_claude_native_teams,{writeNativeInbox:()=>writeNativeInbox,unregisterNativeMember:()=>unregisterNativeMember,sanitizeTeamName:()=>sanitizeTeamName,registerNativeMember:()=>registerNativeMember,registerAsTeamLead:()=>registerAsTeamLead,loadConfig:()=>loadConfig,isInsideClaudeCode:()=>isInsideClaudeCode,ensureNativeTeam:()=>ensureNativeTeam,discoverTeamName:()=>discoverTeamName,discoverClaudeSessionId:()=>discoverClaudeSessionId,deleteNativeTeam:()=>deleteNativeTeam,clearNativeInbox:()=>clearNativeInbox,assignColor:()=>assignColor});import{existsSync as existsSync10}from"fs";import{mkdir as mkdir3,readFile as readFile2,readdir,rm,stat as stat2,unlink as unlink3,writeFile as writeFile2}from"fs/promises";import{homedir as homedir10}from"os";import{join as join10}from"path";function claudeConfigDir(){return process.env.CLAUDE_CONFIG_DIR??join10(homedir10(),".claude")}function teamsBaseDir(){return join10(claudeConfigDir(),"teams")}function sanitizeTeamName(name){return name.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase()}function teamDir(teamName){return join10(teamsBaseDir(),sanitizeTeamName(teamName))}function configPath(teamName){return join10(teamDir(teamName),"config.json")}function inboxesDir(teamName){return join10(teamDir(teamName),"inboxes")}function inboxPath(teamName,agentName){return join10(inboxesDir(teamName),`${sanitizeTeamName(agentName)}.json`)}function lockPath(filePath){return`${filePath}.lock`}async function acquireLock2(path){let lock=lockPath(path),deadline=Date.now()+LOCK_TIMEOUT_MS2;while(Date.now()<deadline)try{await writeFile2(lock,String(process.pid),{flag:"wx"});return}catch{let jitter=Math.floor(Math.random()*LOCK_POLL_MS);await new Promise((r)=>setTimeout(r,LOCK_POLL_MS+jitter))}console.warn(`[claude-native-teams] Force-acquiring stale lock: ${lock}`),await writeFile2(lock,String(process.pid))}async function releaseLock(path){try{await unlink3(lockPath(path))}catch{}}async function loadConfig(teamName){try{let content=await readFile2(configPath(teamName),"utf-8");return JSON.parse(content)}catch(err){if(err instanceof Error&&"code"in err&&err.code==="ENOENT")return null;let message=err instanceof Error?err.message:String(err);return console.warn(`[claude-native-teams] Failed to load config for "${teamName}": ${message}`),null}}async function saveConfig(teamName,config){await writeFile2(configPath(teamName),JSON.stringify(config,null,2))}async function ensureNativeTeam(teamName,description,leadSessionId){let dir=teamDir(teamName),inboxDir=inboxesDir(teamName);await mkdir3(dir,{recursive:!0}),await mkdir3(inboxDir,{recursive:!0});let existing=await loadConfig(teamName);if(existing)return existing;let sanitized=sanitizeTeamName(teamName),config={name:sanitized,description,createdAt:Date.now(),leadAgentId:`team-lead@${sanitized}`,leadSessionId,members:[]};return await saveConfig(teamName,config),config}async function registerNativeMember(teamName,member){let config=await loadConfig(teamName);if(!config)throw Error(`Native team "${teamName}" not found`);let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(member.agentName)}@${sanitized}`;config.members=config.members.filter((m)=>m.agentId!==agentId),config.members.push({agentId,name:sanitizeTeamName(member.agentName),agentType:member.agentType??"general-purpose",joinedAt:Date.now(),tmuxPaneId:member.tmuxPaneId,cwd:member.cwd??process.cwd(),backendType:"tmux",color:member.color,planModeRequired:member.planModeRequired??!1,isActive:!0}),await saveConfig(teamName,config);let inbox=inboxPath(teamName,member.agentName);if(!existsSync10(inbox))await writeFile2(inbox,"[]")}async function unregisterNativeMember(teamName,agentName){let config=await loadConfig(teamName);if(!config)return;let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(agentName)}@${sanitized}`,member=config.members.find((m)=>m.agentId===agentId);if(member)member.isActive=!1;await saveConfig(teamName,config)}async function writeNativeInbox(teamName,agentName,message){let path=inboxPath(teamName,agentName);await mkdir3(inboxesDir(teamName),{recursive:!0}),await acquireLock2(path);try{let messages=[];try{let content=await readFile2(path,"utf-8");messages=JSON.parse(content)}catch{}messages.push(message),await writeFile2(path,JSON.stringify(messages,null,2))}finally{await releaseLock(path)}}async function assignColor(teamName){let config=await loadConfig(teamName);if(!config)return CLAUDE_TEAM_COLORS[0];let usedColors=new Set(config.members.map((m)=>m.color));for(let color of CLAUDE_TEAM_COLORS)if(!usedColors.has(color))return color;return CLAUDE_TEAM_COLORS[config.members.length%CLAUDE_TEAM_COLORS.length]}async function clearNativeInbox(teamName,agentName){let path=inboxPath(teamName,agentName);await acquireLock2(path);try{await writeFile2(path,"[]")}finally{await releaseLock(path)}}async function deleteNativeTeam(teamName){let dir=teamDir(teamName);if(!existsSync10(dir))return!1;return await rm(dir,{recursive:!0,force:!0}),!0}function sanitizePath(p){return p.replace(/[^a-zA-Z0-9]/g,"-")}async function discoverClaudeSessionId(cwd){let envSessionId=process.env.CLAUDE_CODE_SESSION_ID;if(envSessionId)return envSessionId;let projectDir=join10(claudeConfigDir(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let newest=null;for(let name of jsonls){let s=await stat2(join10(projectDir,name));if(!newest||s.mtimeMs>newest.mtime)newest={name,mtime:s.mtimeMs}}if(!newest)return null;return newest.name.replace(".jsonl","")}catch{return null}}function isInsideClaudeCode(){return process.env.CLAUDECODE==="1"}async function discoverTeamName(cwd){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let sessionId=await discoverClaudeSessionId(cwd);if(!sessionId)return null;let base=teamsBaseDir();try{let teams=await readdir(base);for(let name of teams){let cfgPath=join10(base,name,"config.json");try{let content=await readFile2(cfgPath,"utf-8"),config=JSON.parse(content);if(config.leadSessionId===sessionId)return config.name}catch{}}}catch{}return null}async function registerAsTeamLead(teamName,opts){let sessionId=await discoverClaudeSessionId(opts?.cwd);if(!sessionId)throw Error("Could not discover Claude Code session ID. Are you running inside Claude Code with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1?");let config=await ensureNativeTeam(teamName,`Genie team: ${teamName}`,sessionId);if(config.leadSessionId!==sessionId)config.leadSessionId=sessionId,await saveConfig(teamName,config);let leadAgentId=`team-lead@${sanitizeTeamName(teamName)}`,existingLead=config.members.find((m)=>m.agentId===leadAgentId),resolvedPaneId=opts?.tmuxPaneId??process.env.TMUX_PANE;if(!existingLead||!existingLead.isActive)await registerNativeMember(teamName,{agentName:"team-lead",agentType:"general-purpose",color:opts?.color??"blue",tmuxPaneId:resolvedPaneId,cwd:opts?.cwd??process.cwd()});else if(resolvedPaneId&&existingLead.tmuxPaneId!==resolvedPaneId)existingLead.tmuxPaneId=resolvedPaneId,await saveConfig(teamName,config);let inbox=inboxPath(teamName,"team-lead");if(!existsSync10(inbox))await writeFile2(inbox,"[]");let finalConfig=await loadConfig(teamName);if(!finalConfig)throw Error(`Failed to load config for team "${teamName}" after creation`);return{sessionId,config:finalConfig}}var LOCK_TIMEOUT_MS2=5000,LOCK_POLL_MS=50;var init_claude_native_teams=__esm(()=>{init_provider_adapters()});var exports_team_lead_command={};__export(exports_team_lead_command,{shellQuote:()=>shellQuote,sessionExists:()=>sessionExists,ccProjectDirName:()=>ccProjectDirName,buildTeamLeadCommand:()=>buildTeamLeadCommand});import{readFileSync as readFileSync6,readdirSync as readdirSync2}from"fs";import{basename,join as join11}from"path";function shellQuote(s){return`'${s.replace(/'/g,"'\\''")}'`}function buildTeamLeadCommand(teamName,options){let sanitized=sanitizeTeamName(teamName),qTeam=shellQuote(sanitized),folderName=basename(process.cwd()),parts=["CLAUDECODE=1","CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1",`GENIE_TEAM=${qTeam}`,`GENIE_AGENT_NAME=${shellQuote(folderName)}`,"claude",`--agent-id ${shellQuote(`team-lead@${sanitized}`)}`,"--agent-name team-lead",`--team-name ${qTeam}`,"--agent-type team-lead","--dangerously-skip-permissions"];if(parts.push(`--name ${shellQuote(sanitized)}`),options?.continueName)parts.push(`--resume ${shellQuote(options.continueName)}`);else if(options?.sessionId)parts.push(`--session-id ${shellQuote(options.sessionId)}`);if(options?.systemPromptFile){let promptFlag=(options?.promptMode??loadGenieConfigSync().promptMode)==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(`${promptFlag} ${shellQuote(options.systemPromptFile)}`)}return parts.join(" ")}function ccProjectDirName(dir){return dir.replace(/\//g,"-")}function fileHasSessionName(filePath,needle){try{let lines=readFileSync6(filePath,"utf-8").split(`
45
45
  `).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=join11(home,".claude","projects",projectDir),files;try{files=readdirSync2(projectPath).filter((f)=>f.endsWith(".jsonl"))}catch{return!1}let needle=name.toLowerCase();return files.some((file)=>fileHasSessionName(join11(projectPath,file),needle))}catch{return!1}}var init_team_lead_command=__esm(()=>{init_claude_native_teams();init_genie_config2()});import{exec as execCallback}from"child_process";import{existsSync as existsSync11,mkdirSync as mkdirSync5}from"fs";import{homedir as homedir11}from"os";import{join as join12}from"path";import{promisify}from"util";function getLogDir(){let logDir=join12(homedir11(),".genie","logs","tmux");if(!existsSync11(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();let command=`tmux ${finalArgs.join(" ")}`,{stdout}=await exec(command,options);return stdout.trim()}var exec;var init_tmux_wrapper=__esm(()=>{exec=promisify(execCallback)});var exports_tmux={};__export(exports_tmux,{setWindowEnv:()=>setWindowEnv,listWindows:()=>listWindows,listSessions:()=>listSessions,listPanes:()=>listPanes,killSession:()=>killSession,isPaneAlive:()=>isPaneAlive,getWindowEnv:()=>getWindowEnv,getCurrentSessionName:()=>getCurrentSessionName,findWindowByName:()=>findWindowByName,findSessionByName:()=>findSessionByName,executeTmux:()=>executeTmux2,ensureTeamWindow:()=>ensureTeamWindow,createWindow:()=>createWindow,createSession:()=>createSession,capturePaneContent:()=>capturePaneContent,applyPaneColor:()=>applyPaneColor});async function executeTmux2(tmuxCommand){try{return await executeTmux(tmuxCommand)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);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(`
46
46
  `).map((line)=>{let[id,name,attached,windows]=line.split(":");return{id,name,attached:attached==="1",windows:Number.parseInt(windows,10)}})}catch(error2){if((error2 instanceof Error?error2.message:String(error2)).includes("no server running"))return[];throw error2}}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_active,1,0}'`);if(!output)return[];return output.split(`
47
47
  `).map((line)=>{let[id,name,active]=line.split(":");return{id,name,active:active==="1",sessionId}})}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);if(message.includes("no server running")||message.includes("session not found"))return[];throw error2}}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(`
@@ -390,8 +390,8 @@ process.on('SIGTERM', () => { server.close(); process.exit(0); });
390
390
  process.on('SIGINT', () => { server.close(); process.exit(0); });
391
391
  `,{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 generateWorkerId(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 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,claudeSessionId:ctx.claudeSessionId,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;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}),await writeNativeInbox(ctx.validated.team,"team-lead",{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.validated.skill)console.log(` Skill: ${ctx.validated.skill}`);if(workerEntry.claudeSessionId)console.log(` Session: ${workerEntry.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}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride??await getCurrentSessionName(team);if(!sessionName)sessionName=(await getTeam2(team))?.tmuxSessionName??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:execSync3}=__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=execSync3(`tmux capture-pane -t '${paneId}' -p`,{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync3(`tmux 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:execSync3}=__require("child_process");if(teamWindow?.created){let paneId=execSync3(`tmux list-panes -t '${teamWindow.windowId}' -F '#{pane_id}'`,{encoding:"utf-8"}).trim().split(`
392
392
  `)[0];if(ctx.cwd)execSync3(`tmux send-keys -t '${paneId}' 'cd ${ctx.cwd.replace(/'/g,"'\\''")}' Enter`,{encoding:"utf-8"});return execSync3(`tmux send-keys -t '${paneId}' '${ctx.fullCommand.replace(/'/g,"'\\''")}' Enter`,{encoding:"utf-8"}),paneId}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"",splitCmd=`tmux split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${ctx.fullCommand}`;return execSync3(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync3}=__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{execSync3(`tmux ${buildLayoutCommand(layoutTarget,ctx.layoutMode)}`,{stdio:"ignore"})}catch{}}async function launchTmuxSpawn(ctx){let teamWindow=ctx.spawnIntoCurrentWindow?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);if(await notifySpawnJoin(ctx,paneId),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 launchInlineSpawn(ctx){let nt=ctx.validated.nativeTeam,paneId="inline",workerEntry=await registerSpawnWorker(ctx,"inline");if(await notifySpawnJoin(ctx,"inline"),console.log(`Agent "${ctx.workerId}" starting inline...`),console.log(` Provider: ${ctx.launch.provider} | Team: ${ctx.validated.team} | Role: ${ctx.validated.role??"-"}`),nt?.enabled)console.log(` Native: enabled | AgentID: ${workerEntry.nativeAgentId}`);console.log("");let{spawnSync}=__require("child_process"),envVars={...process.env,...ctx.launch.env??{}},result=spawnSync("sh",["-c",ctx.launch.command],{env:envVars,stdio:"inherit"});if(await unregister(ctx.workerId),nt?.enabled&&ctx.agentName)await clearNativeInbox(ctx.validated.team,ctx.agentName).catch(()=>{}),await unregisterNativeMember(ctx.validated.team,ctx.agentName).catch(()=>{});console.log(`
393
- Agent "${ctx.workerId}" session ended.`),process.exit(result.status??0)}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}`}async function rejectDuplicateRole(team,role){let existing=await list();for(let w of existing)if(w.role===role&&w.team===team&&await isPaneAlive(w.paneId))console.error(`Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})
394
- Use a different --role name for a second worker, e.g.: --role ${role}-2`),process.exit(1)}async function resolveNativeTeam(team,_repoPath,options){let parentSessionId=(await getTeam2(team))?.nativeTeamParentSessionId;if(!parentSessionId)parentSessionId=await discoverClaudeSessionId()??`genie-${team}`;await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=options.color??await assignColor(team),nativeTeam;if(options.provider==="claude")nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:options.role??"general-purpose",planModeRequired:options.planMode,permissionMode:options.permissionMode,agentName:options.role};return{parentSessionId,spawnColor,nativeTeam}}async function resolveAgentForSpawn(name,options){let resolved=await resolve3(name);if(!resolved)console.error(`Error: Agent "${name}" not found in directory or built-ins.`),console.error(` Register with: genie dir add ${name} --dir <path>`),console.error(" Or use a built-in: engineer, reviewer, qa, fix, ..."),process.exit(1);let entry=resolved.entry,identityPath=null;if(resolved.builtin)identityPath=resolveBuiltinAgentPath(name);else if(entry.dir)identityPath=loadIdentity(entry);return{entry,repoPath:options.cwd??(entry.dir||void 0)??process.cwd(),identityPath,model:options.model??entry.model}}async function buildSpawnParams(name,team,options,agent){let params={provider:options.provider,team,role:name,skill:options.skill,extraArgs:options.extraArgs,model:agent.model,systemPromptFile:agent.identityPath??void 0,promptMode:agent.entry.promptMode,initialPrompt:options.initialPrompt},{parentSessionId,spawnColor,nativeTeam}=await resolveNativeTeam(team,agent.repoPath,{...options,role:name});if(nativeTeam)params.nativeTeam=nativeTeam;try{let{injectTeamHooks:injectTeamHooks2}=await Promise.resolve().then(() => (init_inject(),exports_inject));if(await injectTeamHooks2(team))console.log(` Hooks: injected genie hook dispatch into team "${team}"`)}catch(err){console.warn(`Warning: could not inject hooks for team "${team}": ${err instanceof Error?err.message:err}`)}if(params.provider==="claude")params.sessionId=crypto.randomUUID();return{params,parentSessionId,spawnColor}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),teamWasExplicit=Boolean(options.team),team=options.team||await discoverTeamName();if(!team)console.error("Error: --team is required (or set GENIE_TEAM, or run inside a genie session)"),process.exit(1);await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam2(team);if(teamConfig?.worktreePath)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams(effectiveRole,team,options,agent);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=await generateWorkerId(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,otelRelayActive=!1;if(!nt?.enabled&&validated.provider==="codex"&&insideTmux)ensureCodexOtelConfig(),otelRelayActive=await ensureOtelRelay(validated.team);let fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&insideTmux,sessionOverride:options.session,autoResume:options.autoResume};if(insideTmux)await launchTmuxSpawn(ctx);else await launchInlineSpawn(ctx)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync3}=__require("child_process"),currentPane=execSync3("tmux display-message -p '#{pane_id}'",{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync3(`tmux kill-pane -t ${w.paneId}`,{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join17}=__require("path"),{homedir:homedir15}=__require("os"),{unlinkSync:unlinkSync3}=__require("fs"),relayDir=join17(homedir15(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync3(join17(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie ls` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`)}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.id)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`)}else console.error(`Failed to stop agent "${w.id}".`),process.exit(1)}async function isResumeEligible(w){return(w.state==="suspended"||w.state==="error")&&Boolean(w.claudeSessionId)&&!await isPaneAlive(w.paneId)}async function resumeAllAgents(){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){if(options.all)return resumeAllAgents();if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w)}function buildResumeParams(agent,template){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??"genie";return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:agent.claudeSessionId,name:`${team}-${agentName}`}}async function resumeAgent(agent){let template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id));await update(agent.id,{resumeAttempts:0});let params=buildResumeParams(agent,template);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}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={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(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),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${agent.claudeSessionId}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.role||w.id;if(await isPaneAlive(w.paneId))statusMap.set(name,{state:w.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 handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),entries=[];for(let entry of dirEntries){let running=statusMap.get(entry.name);entries.push({name:entry.name,dir:entry.dir||"-",status:running?running.state:"offline",team:running?.team||"-",model:entry.model||"-",resumeAttempts:running?.resumeAttempts,maxResumeAttempts:running?.maxResumeAttempts,autoResume:running?.autoResume}),statusMap.delete(entry.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(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 init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_builtin_agents();init_claude_native_teams();init_codex_config();init_provider_adapters();init_team_manager();init_tmux();init_tmux()});function parseDuration(input){let match=input.trim().match(DURATION_RE);if(!match)throw Error(`Invalid duration: "${input}". Expected format: 10m, 2h, 24h, 1d`);let value=Number.parseFloat(match[1]),unit=match[2].toLowerCase(),ms=value*{s:1000,sec:1000,m:60000,min:60000,h:3600000,hr:3600000,d:86400000,day:86400000}[unit];if(ms<=0)throw Error(`Duration must be positive: "${input}"`);return ms}function expandRange(range,step,min,max){if(step===0)throw Error("Cron step value cannot be 0");if(range==="*"){let out=[];for(let i2=min;i2<=max;i2+=step)out.push(i2);return out}if(range.includes("-")){let[start,end]=range.split("-").map(Number),out=[];for(let i2=start;i2<=end;i2+=step)out.push(i2);return out}return[Number.parseInt(range,10)]}function parseCronField(field,min,max){let values=new Set;for(let part of field.split(",")){let stepMatch=part.match(/^(.+)\/(\d+)$/),step=stepMatch?Number.parseInt(stepMatch[2],10):1,range=stepMatch?stepMatch[1]:part;for(let v of expandRange(range,step,min,max))values.add(v)}return[...values].sort((a,b)=>a-b)}function getTimeParts(date,tz){if(!tz)return{month:date.getMonth()+1,dom:date.getDate(),dow:date.getDay(),hour:date.getHours(),minute:date.getMinutes()};let parts=new Intl.DateTimeFormat("en-US",{timeZone:tz,year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short",hour12:!1}).formatToParts(date),get3=(type2)=>Number(parts.find((p)=>p.type===type2)?.value??0),dayMap={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},weekday=parts.find((p)=>p.type==="weekday")?.value??"Sun";return{month:get3("month"),dom:get3("day"),dow:dayMap[weekday]??0,hour:get3("hour")===24?0:get3("hour"),minute:get3("minute")}}function parseOpts(afterOrOpts){if(afterOrOpts instanceof Date)return{after:afterOrOpts};if(afterOrOpts)return{after:afterOrOpts.after,timezone:afterOrOpts.timezone};return{}}function advanceToNextDay(candidate,tz){candidate.setTime(candidate.getTime()+86400000);let tp=getTimeParts(candidate,tz);candidate.setTime(candidate.getTime()-tp.hour*3600000-tp.minute*60000)}function parseCronExpr(cronExpr){let parts=cronExpr.trim().split(/\s+/);if(parts.length<5)throw Error(`Invalid cron expression: "${cronExpr}"`);let[minField,hourField,domField,monthField,dowField]=parts;return{minutes:parseCronField(minField,0,59),hours:parseCronField(hourField,0,23),doms:parseCronField(domField,1,31),months:parseCronField(monthField,1,12),dows:parseCronField(dowField,0,6),domRestricted:domField!=="*",dowRestricted:dowField!=="*"}}function computeNextCronDue(cronExpr,afterOrOpts){let{after,timezone}=parseOpts(afterOrOpts),cron=parseCronExpr(cronExpr),candidate=new Date((after??new Date).getTime());candidate.setSeconds(0,0),candidate.setTime(candidate.getTime()+60000);let limit=new Date(candidate.getTime()+31622400000);while(candidate<=limit){let tp=getTimeParts(candidate,timezone);if(!cron.months.includes(tp.month)){advanceToNextDay(candidate,timezone);continue}if(!(cron.domRestricted&&cron.dowRestricted?cron.doms.includes(tp.dom)||cron.dows.includes(tp.dow):cron.doms.includes(tp.dom)&&cron.dows.includes(tp.dow))){advanceToNextDay(candidate,timezone);continue}if(!cron.hours.includes(tp.hour)){candidate.setTime(candidate.getTime()+3600000-tp.minute*60000);continue}if(cron.minutes.includes(tp.minute))return candidate;candidate.setTime(candidate.getTime()+60000)}throw Error(`No next cron occurrence found for "${cronExpr}" within 366 days`)}var DURATION_RE;var init_cron=__esm(()=>{DURATION_RE=/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i});function validateRunSpec(input){if(!input.command||input.command.trim().length===0)throw Error("RunSpec.command is required and cannot be empty");if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms<1e4)throw Error(`RunSpec.lease_timeout_ms must be >= 10000ms, got ${input.lease_timeout_ms}`);if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms>3600000)throw Error(`RunSpec.lease_timeout_ms must be <= 3600000ms (1h), got ${input.lease_timeout_ms}`);if(input.provider&&!VALID_PROVIDERS.has(input.provider))throw Error(`RunSpec.provider must be 'claude' or 'codex', got '${input.provider}'`);if(input.ref_policy&&!VALID_REF_POLICIES.has(input.ref_policy))throw Error(`RunSpec.ref_policy must be 'current' or 'default', got '${input.ref_policy}'`);if(input.approval_policy&&!VALID_APPROVAL_POLICIES.has(input.approval_policy))throw Error(`RunSpec.approval_policy must be 'auto' or 'manual', got '${input.approval_policy}'`)}function resolveRunSpec(input){return validateRunSpec(input),{repo:input.repo??DEFAULTS.repo,ref_policy:input.ref_policy??DEFAULTS.ref_policy,provider:input.provider??DEFAULTS.provider,role:input.role??DEFAULTS.role,model:input.model??DEFAULTS.model,command:input.command.trim(),approval_policy:input.approval_policy??DEFAULTS.approval_policy,concurrency_class:input.concurrency_class??DEFAULTS.concurrency_class,lease_timeout_ms:input.lease_timeout_ms??DEFAULTS.lease_timeout_ms}}var DEFAULTS,VALID_PROVIDERS,VALID_REF_POLICIES,VALID_APPROVAL_POLICIES;var init_run_spec=__esm(()=>{DEFAULTS={repo:process.cwd(),ref_policy:"current",provider:"claude",role:"worker",model:"",approval_policy:"auto",concurrency_class:"default",lease_timeout_ms:300000},VALID_PROVIDERS=new Set(["claude","codex"]),VALID_REF_POLICIES=new Set(["current","default"]),VALID_APPROVAL_POLICIES=new Set(["auto","manual"])});import{readdirSync as readdirSync4}from"fs";import{join as join17}from"path";function getMigrationsDir(){return join17(import.meta.dir,"..","db","migrations")}async function loadMigrationFiles(){let candidates=[getMigrationsDir(),join17(process.cwd(),"src","db","migrations")];for(let dir of candidates)try{let files=readdirSync4(dir).filter((f)=>f.endsWith(".sql")).sort();if(files.length===0)continue;let migrations=[];for(let file of files){let content=await Bun.file(join17(dir,file)).text();migrations.push({name:file.replace(/\.sql$/,""),sql:content})}return migrations}catch{}return[]}async function ensureMigrationsTable(sql){await sql`
393
+ Agent "${ctx.workerId}" session ended.`),process.exit(result.status??0)}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}`}async function rejectDuplicateRole(team,role){let existing=await list();for(let w of existing)if(w.role===role&&w.team===team){if(await isPaneAlive(w.paneId))console.error(`Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})
394
+ Use a different --role name for a second worker, e.g.: --role ${role}-2`),process.exit(1);await unregister(w.id)}}async function resolveNativeTeam(team,_repoPath,options){let parentSessionId=(await getTeam2(team))?.nativeTeamParentSessionId;if(!parentSessionId)parentSessionId=await discoverClaudeSessionId()??`genie-${team}`;await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=options.color??await assignColor(team),nativeTeam;if(options.provider==="claude")nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:options.role??"general-purpose",planModeRequired:options.planMode,permissionMode:options.permissionMode,agentName:options.role};return{parentSessionId,spawnColor,nativeTeam}}async function resolveAgentForSpawn(name,options){let resolved=await resolve3(name);if(!resolved)console.error(`Error: Agent "${name}" not found in directory or built-ins.`),console.error(` Register with: genie dir add ${name} --dir <path>`),console.error(" Or use a built-in: engineer, reviewer, qa, fix, ..."),process.exit(1);let entry=resolved.entry,identityPath=null;if(resolved.builtin)identityPath=resolveBuiltinAgentPath(name);else if(entry.dir)identityPath=loadIdentity(entry);return{entry,repoPath:options.cwd??(entry.dir||void 0)??process.cwd(),identityPath,model:options.model??entry.model}}async function buildSpawnParams(name,team,options,agent){let params={provider:options.provider,team,role:name,skill:options.skill,extraArgs:options.extraArgs,model:agent.model,systemPromptFile:agent.identityPath??void 0,promptMode:agent.entry.promptMode,initialPrompt:options.initialPrompt},{parentSessionId,spawnColor,nativeTeam}=await resolveNativeTeam(team,agent.repoPath,{...options,role:name});if(nativeTeam)params.nativeTeam=nativeTeam;try{let{injectTeamHooks:injectTeamHooks2}=await Promise.resolve().then(() => (init_inject(),exports_inject));if(await injectTeamHooks2(team))console.log(` Hooks: injected genie hook dispatch into team "${team}"`)}catch(err){console.warn(`Warning: could not inject hooks for team "${team}": ${err instanceof Error?err.message:err}`)}if(params.provider==="claude")params.sessionId=crypto.randomUUID();return{params,parentSessionId,spawnColor}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),teamWasExplicit=Boolean(options.team),team=options.team||await discoverTeamName();if(!team)console.error("Error: --team is required (or set GENIE_TEAM, or run inside a genie session)"),process.exit(1);await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam2(team);if(teamConfig?.worktreePath)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams(effectiveRole,team,options,agent);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=await generateWorkerId(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,otelRelayActive=!1;if(!nt?.enabled&&validated.provider==="codex"&&insideTmux)ensureCodexOtelConfig(),otelRelayActive=await ensureOtelRelay(validated.team);let fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&insideTmux,sessionOverride:options.session,autoResume:options.autoResume};if(insideTmux)await launchTmuxSpawn(ctx);else await launchInlineSpawn(ctx)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync3}=__require("child_process"),currentPane=execSync3("tmux display-message -p '#{pane_id}'",{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync3(`tmux kill-pane -t ${w.paneId}`,{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join17}=__require("path"),{homedir:homedir15}=__require("os"),{unlinkSync:unlinkSync3}=__require("fs"),relayDir=join17(homedir15(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync3(join17(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie ls` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`)}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.id)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`)}else console.error(`Failed to stop agent "${w.id}".`),process.exit(1)}async function isResumeEligible(w){return(w.state==="suspended"||w.state==="error")&&Boolean(w.claudeSessionId)&&!await isPaneAlive(w.paneId)}async function resumeAllAgents(){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){if(options.all)return resumeAllAgents();if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w)}function buildResumeParams(agent,template){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??"genie";return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:agent.claudeSessionId,name:`${team}-${agentName}`}}async function resumeAgent(agent){let template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id));await update(agent.id,{resumeAttempts:0});let params=buildResumeParams(agent,template);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}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={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(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),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${agent.claudeSessionId}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.role||w.id;if(await isPaneAlive(w.paneId))statusMap.set(name,{state:w.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 handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),entries=[];for(let entry of dirEntries){let running=statusMap.get(entry.name);entries.push({name:entry.name,dir:entry.dir||"-",status:running?running.state:"offline",team:running?.team||"-",model:entry.model||"-",resumeAttempts:running?.resumeAttempts,maxResumeAttempts:running?.maxResumeAttempts,autoResume:running?.autoResume}),statusMap.delete(entry.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(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 init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_builtin_agents();init_claude_native_teams();init_codex_config();init_provider_adapters();init_team_manager();init_tmux();init_tmux()});function parseDuration(input){let match=input.trim().match(DURATION_RE);if(!match)throw Error(`Invalid duration: "${input}". Expected format: 10m, 2h, 24h, 1d`);let value=Number.parseFloat(match[1]),unit=match[2].toLowerCase(),ms=value*{s:1000,sec:1000,m:60000,min:60000,h:3600000,hr:3600000,d:86400000,day:86400000}[unit];if(ms<=0)throw Error(`Duration must be positive: "${input}"`);return ms}function expandRange(range,step,min,max){if(step===0)throw Error("Cron step value cannot be 0");if(range==="*"){let out=[];for(let i2=min;i2<=max;i2+=step)out.push(i2);return out}if(range.includes("-")){let[start,end]=range.split("-").map(Number),out=[];for(let i2=start;i2<=end;i2+=step)out.push(i2);return out}return[Number.parseInt(range,10)]}function parseCronField(field,min,max){let values=new Set;for(let part of field.split(",")){let stepMatch=part.match(/^(.+)\/(\d+)$/),step=stepMatch?Number.parseInt(stepMatch[2],10):1,range=stepMatch?stepMatch[1]:part;for(let v of expandRange(range,step,min,max))values.add(v)}return[...values].sort((a,b)=>a-b)}function getTimeParts(date,tz){if(!tz)return{month:date.getMonth()+1,dom:date.getDate(),dow:date.getDay(),hour:date.getHours(),minute:date.getMinutes()};let parts=new Intl.DateTimeFormat("en-US",{timeZone:tz,year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short",hour12:!1}).formatToParts(date),get3=(type2)=>Number(parts.find((p)=>p.type===type2)?.value??0),dayMap={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},weekday=parts.find((p)=>p.type==="weekday")?.value??"Sun";return{month:get3("month"),dom:get3("day"),dow:dayMap[weekday]??0,hour:get3("hour")===24?0:get3("hour"),minute:get3("minute")}}function parseOpts(afterOrOpts){if(afterOrOpts instanceof Date)return{after:afterOrOpts};if(afterOrOpts)return{after:afterOrOpts.after,timezone:afterOrOpts.timezone};return{}}function advanceToNextDay(candidate,tz){candidate.setTime(candidate.getTime()+86400000);let tp=getTimeParts(candidate,tz);candidate.setTime(candidate.getTime()-tp.hour*3600000-tp.minute*60000)}function parseCronExpr(cronExpr){let parts=cronExpr.trim().split(/\s+/);if(parts.length<5)throw Error(`Invalid cron expression: "${cronExpr}"`);let[minField,hourField,domField,monthField,dowField]=parts;return{minutes:parseCronField(minField,0,59),hours:parseCronField(hourField,0,23),doms:parseCronField(domField,1,31),months:parseCronField(monthField,1,12),dows:parseCronField(dowField,0,6),domRestricted:domField!=="*",dowRestricted:dowField!=="*"}}function computeNextCronDue(cronExpr,afterOrOpts){let{after,timezone}=parseOpts(afterOrOpts),cron=parseCronExpr(cronExpr),candidate=new Date((after??new Date).getTime());candidate.setSeconds(0,0),candidate.setTime(candidate.getTime()+60000);let limit=new Date(candidate.getTime()+31622400000);while(candidate<=limit){let tp=getTimeParts(candidate,timezone);if(!cron.months.includes(tp.month)){advanceToNextDay(candidate,timezone);continue}if(!(cron.domRestricted&&cron.dowRestricted?cron.doms.includes(tp.dom)||cron.dows.includes(tp.dow):cron.doms.includes(tp.dom)&&cron.dows.includes(tp.dow))){advanceToNextDay(candidate,timezone);continue}if(!cron.hours.includes(tp.hour)){candidate.setTime(candidate.getTime()+3600000-tp.minute*60000);continue}if(cron.minutes.includes(tp.minute))return candidate;candidate.setTime(candidate.getTime()+60000)}throw Error(`No next cron occurrence found for "${cronExpr}" within 366 days`)}var DURATION_RE;var init_cron=__esm(()=>{DURATION_RE=/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i});function validateRunSpec(input){if(!input.command||input.command.trim().length===0)throw Error("RunSpec.command is required and cannot be empty");if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms<1e4)throw Error(`RunSpec.lease_timeout_ms must be >= 10000ms, got ${input.lease_timeout_ms}`);if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms>3600000)throw Error(`RunSpec.lease_timeout_ms must be <= 3600000ms (1h), got ${input.lease_timeout_ms}`);if(input.provider&&!VALID_PROVIDERS.has(input.provider))throw Error(`RunSpec.provider must be 'claude' or 'codex', got '${input.provider}'`);if(input.ref_policy&&!VALID_REF_POLICIES.has(input.ref_policy))throw Error(`RunSpec.ref_policy must be 'current' or 'default', got '${input.ref_policy}'`);if(input.approval_policy&&!VALID_APPROVAL_POLICIES.has(input.approval_policy))throw Error(`RunSpec.approval_policy must be 'auto' or 'manual', got '${input.approval_policy}'`)}function resolveRunSpec(input){return validateRunSpec(input),{repo:input.repo??DEFAULTS.repo,ref_policy:input.ref_policy??DEFAULTS.ref_policy,provider:input.provider??DEFAULTS.provider,role:input.role??DEFAULTS.role,model:input.model??DEFAULTS.model,command:input.command.trim(),approval_policy:input.approval_policy??DEFAULTS.approval_policy,concurrency_class:input.concurrency_class??DEFAULTS.concurrency_class,lease_timeout_ms:input.lease_timeout_ms??DEFAULTS.lease_timeout_ms}}var DEFAULTS,VALID_PROVIDERS,VALID_REF_POLICIES,VALID_APPROVAL_POLICIES;var init_run_spec=__esm(()=>{DEFAULTS={repo:process.cwd(),ref_policy:"current",provider:"claude",role:"worker",model:"",approval_policy:"auto",concurrency_class:"default",lease_timeout_ms:300000},VALID_PROVIDERS=new Set(["claude","codex"]),VALID_REF_POLICIES=new Set(["current","default"]),VALID_APPROVAL_POLICIES=new Set(["auto","manual"])});import{readdirSync as readdirSync4}from"fs";import{join as join17}from"path";function getMigrationsDir(){return join17(import.meta.dir,"..","db","migrations")}async function loadMigrationFiles(){let candidates=[getMigrationsDir(),join17(process.cwd(),"src","db","migrations")];for(let dir of candidates)try{let files=readdirSync4(dir).filter((f)=>f.endsWith(".sql")).sort();if(files.length===0)continue;let migrations=[];for(let file of files){let content=await Bun.file(join17(dir,file)).text();migrations.push({name:file.replace(/\.sql$/,""),sql:content})}return migrations}catch{}return[]}async function ensureMigrationsTable(sql){await sql`
395
395
  CREATE TABLE IF NOT EXISTS _genie_migrations (
396
396
  id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
397
397
  name TEXT UNIQUE NOT NULL,
@@ -987,12 +987,12 @@ ${diff}
987
987
 
988
988
  When done, report your verdict:
989
989
  Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to team-lead`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_agent_registry();function formatTime(timestamp2){try{return new Date(timestamp2).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1})}catch{return"??:??"}}function truncate(str2,maxLen){if(str2.length<=maxLen)return str2;return`${str2.slice(0,maxLen-3)}...`}function formatPath(path3){let shortened=path3.replace(/^\/home\/\w+\/workspace\//,"~/").replace(/^\/home\/\w+\//,"~/");return truncate(shortened,50)}function flushPendingReads(ctx){if(ctx.pendingReads.length===0)return;ctx.events.push({timestamp:ctx.lastReadTime,type:"read",summary:ctx.pendingReads.length===1?`Read: ${formatPath(ctx.pendingReads[0])}`:`Read ${ctx.pendingReads.length} files`,details:ctx.pendingReads.length>1?ctx.pendingReads.map(formatPath):void 0}),ctx.pendingReads.length=0}function flushPendingEdits(ctx){if(ctx.pendingEdits.length===0)return;ctx.events.push({timestamp:ctx.lastEditTime,type:"edit",summary:ctx.pendingEdits.length===1?`Edit: ${formatPath(ctx.pendingEdits[0])}`:`Edit ${ctx.pendingEdits.length} files`,details:ctx.pendingEdits.length>1?ctx.pendingEdits.map(formatPath):void 0}),ctx.pendingEdits.length=0}function flushAll(ctx){flushPendingReads(ctx),flushPendingEdits(ctx)}function processToolCallEntry(entry,ctx){if(!entry.toolCall)return;let{name,input}=entry.toolCall,inputRecord=input,normalizedName=name==="shell"||name==="exec_command"?"Bash":name;switch(normalizedName){case"Read":if(ctx.lastReadTime=entry.timestamp,inputRecord.file_path)ctx.pendingReads.push(String(inputRecord.file_path));break;case"Edit":if(flushPendingReads(ctx),ctx.lastEditTime=entry.timestamp,inputRecord.file_path)ctx.pendingEdits.push(String(inputRecord.file_path));break;case"Write":flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"write",summary:`Write: ${formatPath(String(inputRecord.file_path||"unknown"))}`});break;case"Bash":{flushAll(ctx);let cmd=String(inputRecord.command||"").replace(/\n/g," ");ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`Bash: ${truncate(cmd,60)}`});break}case"AskUserQuestion":{flushAll(ctx);let questions=inputRecord.questions;ctx.events.push({timestamp:entry.timestamp,type:"question",summary:`Question: ${truncate(questions?.[0]?.question||"question",60)}`});break}default:flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`${normalizedName}: ${truncate(entry.text.replace(/\n/g," "),60)}`})}}function processTranscriptEntry(entry,ctx){if(entry.role==="user"){flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"prompt",summary:truncate(entry.text.replace(/\n/g," "),80)});return}if(entry.role==="tool_call"){processToolCallEntry(entry,ctx);return}if(entry.role==="assistant"&&entry.text.length>100)flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"response",summary:truncate(entry.text.replace(/\n/g," "),80)})}function extractEvents(entries){let ctx={events:[],pendingReads:[],pendingEdits:[],lastReadTime:"",lastEditTime:""};for(let entry of entries)processTranscriptEntry(entry,ctx);return flushAll(ctx),ctx.events}function detectStatus(entries){if(entries.length===0)return"unknown";let lastEntries=entries.slice(-10);for(let entry of lastEntries.reverse())if(entry.role==="tool_call"&&entry.toolCall?.name==="AskUserQuestion")return"question";let last=entries[entries.length-1];if(last.role==="user")return"working";if(last.role==="assistant")return"idle";return"unknown"}function formatEventsForDisplay(events,stats){let lines=[];lines.push(""),lines.push(`Session: ${stats.workerId} [${stats.provider}]${stats.branch?` (${stats.branch})`:""} | ${stats.duration} | ${stats.totalEntries} entries \u2192 ${stats.compressedLines} lines`),lines.push("");for(let event of events){let time=formatTime(event.timestamp),icon=getEventIcon(event.type);if(lines.push(`[${time}] ${icon} ${event.summary}`),event.details&&event.details.length>0){for(let detail of event.details.slice(0,5))lines.push(` ${detail}`);if(event.details.length>5)lines.push(` ... and ${event.details.length-5} more`)}if(event.result)lines.push(` \u2192 ${event.result}`)}return lines.push(""),lines.push(`Status: ${stats.status.toUpperCase()} | ${stats.exchanges} exchanges | ${stats.toolCalls} tool calls | ${stats.compressionRatio.toFixed(0)}x compression`),lines.push(""),lines.join(`
990
- `)}function getEventIcon(type2){switch(type2){case"prompt":return"\uD83D\uDCAC";case"read":return"\uD83D\uDCD6";case"edit":return"\u270F\uFE0F";case"write":return"\uD83D\uDCDD";case"bash":return"\u26A1";case"question":return"\u2753";case"answer":return"\u2705";case"permission":return"\uD83D\uDD10";case"thinking":return"\uD83E\uDD14";case"response":return"\uD83D\uDCAD";default:return"\u2022"}}function formatTranscriptEntryForDisplay(entry){let time=formatTime(entry.timestamp);if(entry.role==="user")return[`
991
- [${time}] USER:`,entry.text];if(entry.role==="tool_call"&&entry.toolCall){let input=entry.toolCall.input,detail;if(entry.toolCall.name==="Bash"||entry.toolCall.name==="shell")detail=` ${entry.toolCall.name}: ${input.command??""}`;else if(["Read","Edit","Write"].includes(entry.toolCall.name))detail=` ${entry.toolCall.name}: ${input.file_path??""}`;else detail=` ${entry.toolCall.name}`;return[`
992
- [${time}] TOOL:`,detail]}if(entry.role==="assistant"){let text=entry.text.slice(0,500)+(entry.text.length>500?"...":"");return[`
993
- [${time}] ASSISTANT:`,text]}if(entry.role==="tool_result"){let text=entry.text.slice(0,500)+(entry.text.length>500?"...":"");return[`
994
- [${time}] RESULT:`,` ${text}`]}if(entry.role==="system")return[`
995
- [${time}] SYSTEM:`,entry.text];return[]}function formatFullConversation(entries){return entries.flatMap(formatTranscriptEntryForDisplay).join(`
990
+ `)}function getEventIcon(type2){switch(type2){case"prompt":return"\uD83D\uDCAC";case"read":return"\uD83D\uDCD6";case"edit":return"\u270F\uFE0F";case"write":return"\uD83D\uDCDD";case"bash":return"\u26A1";case"question":return"\u2753";case"answer":return"\u2705";case"permission":return"\uD83D\uDD10";case"thinking":return"\uD83E\uDD14";case"response":return"\uD83D\uDCAD";default:return"\u2022"}}function formatToolDetail(toolCall){let input=toolCall.input;if(toolCall.name==="Bash"||toolCall.name==="shell")return` ${toolCall.name}: ${input.command??""}`;if(["Read","Edit","Write"].includes(toolCall.name))return` ${toolCall.name}: ${input.file_path??""}`;return` ${toolCall.name}`}function formatTranscriptEntryForDisplay(entry){let time=formatTime(entry.timestamp);switch(entry.role){case"user":return[`
991
+ [${time}] USER:`,entry.text];case"tool_call":return entry.toolCall?[`
992
+ [${time}] TOOL:`,formatToolDetail(entry.toolCall)]:[];case"assistant":return[`
993
+ [${time}] ASSISTANT:`,truncate(entry.text,500)];case"tool_result":return[`
994
+ [${time}] RESULT:`,` ${truncate(entry.text,500)}`];case"system":return[`
995
+ [${time}] SYSTEM:`,entry.text];default:return[]}}function formatFullConversation(entries){return entries.flatMap(formatTranscriptEntryForDisplay).join(`
996
996
  `)}async function findWorker(identifier){let worker=await get(identifier);if(worker)return worker;if(worker=await findByTask(identifier),worker)return worker;return(await list()).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function resolveContext(workerIdOrName,options){if(options.logFile)return{worker:{id:"direct",paneId:"",session:"",worktree:null,startedAt:new Date().toISOString(),state:"idle",lastStateChange:new Date().toISOString(),repoPath:process.cwd(),provider:"claude"},workerId:"direct",provider:"claude",duration:"N/A"};let worker=await findWorker(workerIdOrName);if(!worker)console.error(`Agent "${workerIdOrName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);let elapsed=getElapsedTime(worker);return{worker,workerId:worker.id,provider:worker.provider??"claude",branch:worker.worktree?`work/${worker.taskId}`:void 0,duration:elapsed.formatted}}function buildFilter(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.after)filter.since=options.after,hasFilter=!0;if(options.type)filter.roles=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function filterSinceExchanges(entries,since){let userCount=0;for(let i2=entries.length-1;i2>=0;i2--)if(entries[i2].role==="user"){if(userCount++,userCount>=since)return entries.slice(i2)}return entries}async function loadEntries(ctx,options){let{readTranscript:readTranscript2,getProvider:getProvider2}=await Promise.resolve().then(() => exports_transcript);if(options.logFile)return(await getProvider2(ctx.worker)).readEntries(options.logFile);return readTranscript2(ctx.worker)}function filterEntries(entries,options){let{applyFilter:applyFilter2}=__toCommonJS(exports_transcript),filtered=options.since&&options.since>0?filterSinceExchanges(entries,options.since):entries,transcriptFilter=buildFilter(options);if(transcriptFilter)filtered=applyFilter2(filtered,transcriptFilter);return filtered}function outputEntries(filtered,options){if(options.ndjson){for(let entry of filtered){let{raw:_raw,...rest}=entry;console.log(JSON.stringify(options.raw?entry:rest))}return!0}if(options.raw){for(let entry of filtered)console.log(JSON.stringify(entry.raw));return!0}if(options.full)return console.log(formatFullConversation(filtered)),!0;return!1}async function historyCommand(workerIdOrName,options){let ctx=await resolveContext(workerIdOrName,options),entries=await loadEntries(ctx,options);if(entries.length===0)console.error(`No transcript found for agent "${ctx.workerId}".`),process.exit(1);let filtered=filterEntries(entries,options);if(outputEntries(filtered,options))return;let events=extractEvents(filtered),toolCallCount=entries.filter((e)=>e.role==="tool_call").length,userMessageCount=entries.filter((e)=>e.role==="user").length,stats={workerId:ctx.workerId,taskId:ctx.workerId,branch:ctx.branch,provider:ctx.provider,duration:ctx.duration,totalEntries:entries.length,compressedLines:events.length,compressionRatio:entries.length/Math.max(events.length,1),exchanges:userMessageCount,toolCalls:toolCallCount,status:detectStatus(entries)};if(options.json){console.log(JSON.stringify({stats,events},null,2));return}console.log(formatEventsForDisplay(events,stats))}init_agent_registry();init_mailbox();import{appendFile as appendFile2,mkdir as mkdir9,readFile as readFile12}from"fs/promises";import{join as join27}from"path";init_file_lock();function chatDir(repoPath){return join27(repoPath,".genie","chat")}function chatFilePath(repoPath,teamName){let safeName=teamName.replace(/\//g,"--");return join27(chatDir(repoPath),`${safeName}.jsonl`)}async function readMessages(repoPath,teamName,since){let filePath=chatFilePath(repoPath,teamName),content;try{content=await readFile12(filePath,"utf-8")}catch{return[]}let lines=content.trim().split(`
997
997
  `).filter(Boolean),messages2=[];for(let line of lines)try{messages2.push(JSON.parse(line))}catch{}if(since){let sinceTime=new Date(since).getTime();messages2=messages2.filter((m)=>new Date(m.timestamp).getTime()>=sinceTime)}return messages2}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),team?readMessages(repoPath,team):Promise.resolve([])]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName)]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startNatsFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startNatsFollow(agents,repoPath,teamName,filter,onEvent)}async function startNatsFollow(_agents,_repoPath,_team,filter,onEvent){let nats=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client));if(!await nats.isAvailable())throw Error("NATS is not available. Install nats package and ensure NATS server is running.");let kindsFilter=filter?.kinds?new Set(filter.kinds):null,subs=[],seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,handleNatsEvent=(_subject,data)=>{let event=data;if(!event?.timestamp||!event?.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},allSub=await nats.subscribe("genie.>",handleNatsEvent);return subs.push(allSub),{mode:"nats",stop:async()=>{for(let sub of subs)sub.unsubscribe()}}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function formatTime2(timestamp2){try{return new Date(timestamp2).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})}catch{return"??:??:??"}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
998
998
  `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;case"shell":case"exec_command":return`$ ${(Array.isArray(input.command)?input.command.join(" "):String(input.command??"")).split(`
@@ -1010,7 +1010,7 @@ ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push
1010
1010
  `);else{if(lastFollowKind!==null&&!(lastFollowKind==="tool_call"&&event.kind==="tool_call"))process.stdout.write(`
1011
1011
  `);process.stdout.write(`${formatEventBlock(event)}
1012
1012
  `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);label=`team:${options.team} (${agents.length} agents)`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}import{readFile as readFile13}from"fs/promises";import{homedir as homedir19}from"os";import{join as join28}from"path";var _registry;async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}var _taskService;async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}var _teamManager;async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}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??join28(homedir19(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join28(configDir,"teams",sanitized,"config.json");try{let raw=await readFile13(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}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 resolveSenderTeams(teams,sender){let senderTeams=teams.filter((t)=>t.members.includes(sender));if(sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!senderTeams.some((t)=>t.name===leaderTeam.name))senderTeams=[...senderTeams,leaderTeam]}}return senderTeams}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||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"){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null}return null}function localActor(name){return{actorType:"local",actorId:name}}function padRight3(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function truncate2(str3,len){return str3.length<=len?str3:`${str3.slice(0,len-1)}\u2026`}function formatTime3(iso){return new Date(iso).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1})}async function resolveTeamName(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 ts=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts.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(ts,conv)}async function printConversationSummary(ts,conv){let messages2=await ts.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?formatTime3(lastMsg.createdAt):"";if(console.log(` ${padRight3(name,30)} ${padRight3(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts.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(` ${padRight3("ID",20)} ${padRight3("NAME",25)} ${padRight3("TYPE",8)} ${padRight3("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=formatTime3(c.updatedAt);console.log(` ${padRight3(c.id,20)} ${padRight3(name,25)} ${padRight3(c.type,8)} ${padRight3(linked,20)} ${updated}`)}console.log(`
1013
- ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts.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 ts=await getTaskService(),conv=await ts.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts.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=formatTime3(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}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-lead)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").action(async(body,options)=>{try{let ts=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),scopeError=await checkSendScope(repoPath,from,options.to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let senderActor=localActor(from),recipientActor=localActor(options.to),conv=await ts.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts.addMember(conv.id,senderActor),await ts.addMember(conv.id,recipientActor);let msg=await ts.sendMessage(conv.id,senderActor,body);console.log(`Message sent to "${options.to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),teamName=await resolveTeamName(options.team,repoPath,from),senderActor=localActor(from),conv=await ts.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts.addMember(conv.id,senderActor);let msg=await ts.sendMessage(conv.id,senderActor,body);console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("inbox [agent]").description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService2;async function getTaskService2(){if(!_taskService2)_taskService2=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService2}function padRight4(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function currentActor(){return{actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"}}async function handleNotifySet(options){let ts=await getTaskService2(),actor=currentActor(),pref=await ts.setPreference(actor,options.channel,{priorityThreshold:options.priority,isDefault:options.default}),defaultLabel=pref.isDefault?", default":"";console.log(`Notification preference set: ${pref.channel} (threshold: ${pref.priorityThreshold}${defaultLabel}).`)}function printPrefsTable(prefs){console.log(` ${padRight4("CHANNEL",15)} ${padRight4("THRESHOLD",12)} ${padRight4("DEFAULT",10)} ENABLED`),console.log(` ${"\u2500".repeat(45)}`);for(let p of prefs){let dflt=p.isDefault?"yes":"no",enabled=p.enabled?"yes":"no";console.log(` ${padRight4(p.channel,15)} ${padRight4(p.priorityThreshold,12)} ${padRight4(dflt,10)} ${enabled}`)}console.log(`
1013
+ ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts.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 ts=await getTaskService(),conv=await ts.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts.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=formatTime3(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}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-lead)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").action(async(body,options)=>{try{let ts=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),scopeError=await checkSendScope(repoPath,from,options.to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let senderActor=localActor(from),recipientActor=localActor(options.to),conv=await ts.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts.addMember(conv.id,senderActor),await ts.addMember(conv.id,recipientActor);let msg=await ts.sendMessage(conv.id,senderActor,body);try{let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),teamName=await nativeTeams.discoverTeamName().catch(()=>null);if(teamName){if((await nativeTeams.loadConfig(teamName).catch(()=>null))?.members?.some((m)=>m.name===options.to||m.agentId===`${options.to}@${teamName}`))await nativeTeams.writeNativeInbox(teamName,options.to,{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1})}}catch{}console.log(`Message sent to "${options.to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),teamName=await resolveTeamName(options.team,repoPath,from),senderActor=localActor(from),conv=await ts.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts.addMember(conv.id,senderActor);let msg=await ts.sendMessage(conv.id,senderActor,body);console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("inbox [agent]").description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService2;async function getTaskService2(){if(!_taskService2)_taskService2=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService2}function padRight4(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function currentActor(){return{actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"}}async function handleNotifySet(options){let ts=await getTaskService2(),actor=currentActor(),pref=await ts.setPreference(actor,options.channel,{priorityThreshold:options.priority,isDefault:options.default}),defaultLabel=pref.isDefault?", default":"";console.log(`Notification preference set: ${pref.channel} (threshold: ${pref.priorityThreshold}${defaultLabel}).`)}function printPrefsTable(prefs){console.log(` ${padRight4("CHANNEL",15)} ${padRight4("THRESHOLD",12)} ${padRight4("DEFAULT",10)} ENABLED`),console.log(` ${"\u2500".repeat(45)}`);for(let p of prefs){let dflt=p.isDefault?"yes":"no",enabled=p.enabled?"yes":"no";console.log(` ${padRight4(p.channel,15)} ${padRight4(p.priorityThreshold,12)} ${padRight4(dflt,10)} ${enabled}`)}console.log(`
1014
1014
  ${prefs.length} preference${prefs.length===1?"":"s"}`)}async function handleNotifyList(options){let ts=await getTaskService2(),actor=currentActor(),prefs=await ts.getPreferences(actor);if(options.json){console.log(JSON.stringify(prefs,null,2));return}if(prefs.length===0){console.log("No notification preferences configured.");return}printPrefsTable(prefs)}async function handleNotifyRemove(options){let ts=await getTaskService2(),actor=currentActor();if(await ts.deletePreference(actor,options.channel))console.log(`Removed notification preference for channel: ${options.channel}`);else console.log(`No preference found for channel: ${options.channel}`)}function registerNotifyCommands(program2){let notify=program2.command("notify").description("Notification preference management");notify.command("set").description("Set notification preference for a channel").requiredOption("--channel <channel>","Channel: whatsapp, telegram, email, slack, discord, tmux").option("--priority <priority>","Minimum priority threshold","normal").option("--default","Set as default channel").action(async(options)=>{try{await handleNotifySet(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("list").description("List notification preferences").option("--json","Output as JSON").action(async(options)=>{try{await handleNotifyList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("remove").description("Remove a notification preference").requiredOption("--channel <channel>","Channel to remove").action(async(options)=>{try{await handleNotifyRemove(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_tmux();function debug(msg){if(process.env.DEBUG)console.error(`[target-resolver] ${msg}`)}async function defaultTmuxLookup(sessionName,windowName){try{let tmux=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),session=await tmux.findSessionByName(sessionName);if(!session)return null;let windows=await tmux.listWindows(session.id);if(!windows||windows.length===0)return null;let targetWindow;if(windowName){if(targetWindow=windows.find((w)=>w.name===windowName),!targetWindow)return null}else targetWindow=windows.find((w)=>w.active)||windows[0];let panes=await tmux.listPanes(targetWindow.id);if(!panes||panes.length===0)return null;return{paneId:(panes.find((p)=>p.active)||panes[0]).id,session:sessionName}}catch{return null}}async function defaultIsPaneLive(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{pane_id}'`)).trim()===paneId}catch{return!1}}async function defaultCleanupDeadPane(workerId,paneId){try{await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).removeSubPane(workerId,paneId)}catch{}}async function defaultDeriveSession(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{session_name}'`)).trim()||null}catch{return null}}async function assertLive(paneId,isPaneLive,errorMsg,cleanup){if(!await isPaneLive(paneId)){if(cleanup)await cleanup();throw Error(errorMsg)}}async function resolveRawPane(target,opts){if(opts.checkLiveness)await assertLive(target,opts.isPaneLive,`Pane ${target} is dead or does not exist. Check with: tmux list-panes -a`);let session=await opts.deriveSession(target);return{paneId:target,session:session??void 0,resolvedVia:"raw"}}async function resolveWindowId(target,workers,opts){let matchingWorker=Object.values(workers).find((w)=>w.windowId===target);if(!matchingWorker)throw Error(`Window "${target}" not found in worker registry.
1015
1015
  Run 'genie ls' to list agents.`);if(opts.checkLiveness)await assertLive(matchingWorker.paneId,opts.isPaneLive,`Window ${target}: worker ${matchingWorker.id} pane ${matchingWorker.paneId} is dead. Run 'genie kill ${matchingWorker.id}' to clean up.`);return{paneId:matchingWorker.paneId,session:matchingWorker.session,workerId:matchingWorker.id,resolvedVia:"worker"}}function resolveWorkerSubPane(worker,leftSide,rightSide){let index=Number.parseInt(rightSide,10);if(Number.isNaN(index)||index<0)throw Error(`Invalid sub-pane index "${rightSide}" for worker "${leftSide}". Use a non-negative integer (0 = primary, 1+ = sub-panes).`);let paneId=getPaneByIndex(worker,index);if(!paneId){let maxIndex=worker.subPanes?worker.subPanes.length:0;throw Error(`Worker "${leftSide}" has no sub-pane index ${index}. Available: 0 (primary)${maxIndex>0?`, 1-${maxIndex} (sub-panes)`:""}. Sub-pane index ${index} does not exist.`)}return paneId}function pickUnique(target,candidates,label){if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 ${label}: ${ids}
1016
1016
  Use the full ID instead.`)}function resolveByRole(target,workers,currentTeam){if(!currentTeam)return null;let candidates=Object.entries(workers).filter(([,w])=>w.role===target&&w.team===currentTeam);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}" in team "${currentTeam}"`)}function resolveByCustomName(target,workers,currentTeam){if(currentTeam){let teamCandidates=Object.entries(workers).filter(([,w])=>w.customName===target&&w.team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(([,w])=>w.customName===target);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName "${target}"`)}function resolveByPartialId(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&id.endsWith(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260323.4",
5
+ "version": "4.260324.1",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260323.4",
3
+ "version": "4.260324.1",
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.260323.4",
3
+ "version": "4.260324.1",
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.260323.4",
3
+ "version": "4.260324.1",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -743,17 +743,22 @@ function prependEnvVars(command: string, env?: Record<string, string>): string {
743
743
 
744
744
  /**
745
745
  * Reject spawn if a live worker with the same role already exists in the team.
746
- * Dead/suspended workers (pane gone) are ignored — only live panes block.
746
+ * Dead/suspended workers (pane gone) are auto-cleaned from registry — only live panes block.
747
747
  */
748
748
  async function rejectDuplicateRole(team: string, role: string): Promise<void> {
749
749
  const existing = await registry.list();
750
750
  for (const w of existing) {
751
- if (w.role === role && w.team === team && (await isPaneAlive(w.paneId))) {
752
- console.error(
753
- `Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})\n` +
754
- `Use a different --role name for a second worker, e.g.: --role ${role}-2`,
755
- );
756
- process.exit(1);
751
+ if (w.role === role && w.team === team) {
752
+ const alive = await isPaneAlive(w.paneId);
753
+ if (alive) {
754
+ console.error(
755
+ `Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})\n` +
756
+ `Use a different --role name for a second worker, e.g.: --role ${role}-2`,
757
+ );
758
+ process.exit(1);
759
+ }
760
+ // Dead worker with same role — clean up stale registry entry so spawn can proceed
761
+ await registry.unregister(w.id);
757
762
  }
758
763
  }
759
764
  }
@@ -310,41 +310,34 @@ function getEventIcon(type: CompressedEvent['type']): string {
310
310
  }
311
311
  }
312
312
 
313
- function formatTranscriptEntryForDisplay(entry: TranscriptEntry): string[] {
314
- const time = formatTime(entry.timestamp);
315
-
316
- if (entry.role === 'user') {
317
- return [`\n[${time}] USER:`, entry.text];
318
- }
319
-
320
- if (entry.role === 'tool_call' && entry.toolCall) {
321
- const input = entry.toolCall.input as Record<string, unknown>;
322
- let detail: string;
323
- if (entry.toolCall.name === 'Bash' || entry.toolCall.name === 'shell') {
324
- detail = ` ${entry.toolCall.name}: ${input.command ?? ''}`;
325
- } else if (['Read', 'Edit', 'Write'].includes(entry.toolCall.name)) {
326
- detail = ` ${entry.toolCall.name}: ${input.file_path ?? ''}`;
327
- } else {
328
- detail = ` ${entry.toolCall.name}`;
329
- }
330
- return [`\n[${time}] TOOL:`, detail];
313
+ function formatToolDetail(toolCall: NonNullable<TranscriptEntry['toolCall']>): string {
314
+ const input = toolCall.input as Record<string, unknown>;
315
+ if (toolCall.name === 'Bash' || toolCall.name === 'shell') {
316
+ return ` ${toolCall.name}: ${input.command ?? ''}`;
331
317
  }
332
-
333
- if (entry.role === 'assistant') {
334
- const text = entry.text.slice(0, 500) + (entry.text.length > 500 ? '...' : '');
335
- return [`\n[${time}] ASSISTANT:`, text];
318
+ if (['Read', 'Edit', 'Write'].includes(toolCall.name)) {
319
+ return ` ${toolCall.name}: ${input.file_path ?? ''}`;
336
320
  }
321
+ return ` ${toolCall.name}`;
322
+ }
337
323
 
338
- if (entry.role === 'tool_result') {
339
- const text = entry.text.slice(0, 500) + (entry.text.length > 500 ? '...' : '');
340
- return [`\n[${time}] RESULT:`, ` ${text}`];
341
- }
324
+ function formatTranscriptEntryForDisplay(entry: TranscriptEntry): string[] {
325
+ const time = formatTime(entry.timestamp);
342
326
 
343
- if (entry.role === 'system') {
344
- return [`\n[${time}] SYSTEM:`, entry.text];
327
+ switch (entry.role) {
328
+ case 'user':
329
+ return [`\n[${time}] USER:`, entry.text];
330
+ case 'tool_call':
331
+ return entry.toolCall ? [`\n[${time}] TOOL:`, formatToolDetail(entry.toolCall)] : [];
332
+ case 'assistant':
333
+ return [`\n[${time}] ASSISTANT:`, truncate(entry.text, 500)];
334
+ case 'tool_result':
335
+ return [`\n[${time}] RESULT:`, ` ${truncate(entry.text, 500)}`];
336
+ case 'system':
337
+ return [`\n[${time}] SYSTEM:`, entry.text];
338
+ default:
339
+ return [];
345
340
  }
346
-
347
- return [];
348
341
  }
349
342
 
350
343
  function formatFullConversation(entries: TranscriptEntry[]): string {
@@ -399,6 +399,32 @@ export function registerSendInboxCommands(program: Command): void {
399
399
  await ts.addMember(conv.id, recipientActor);
400
400
 
401
401
  const msg = await ts.sendMessage(conv.id, senderActor, body);
402
+
403
+ // Bridge to CC native inbox so Claude Code agents receive in real-time
404
+ try {
405
+ const nativeTeams = await import('../lib/claude-native-teams.js');
406
+ const teamName = await nativeTeams.discoverTeamName().catch(() => null);
407
+ if (teamName) {
408
+ const config = await nativeTeams.loadConfig(teamName).catch(() => null);
409
+ const memberExists = config?.members?.some(
410
+ (m: { name?: string; agentId?: string }) =>
411
+ m.name === options.to || m.agentId === `${options.to}@${teamName}`,
412
+ );
413
+ if (memberExists) {
414
+ await nativeTeams.writeNativeInbox(teamName, options.to, {
415
+ from,
416
+ text: body,
417
+ summary: body.length > 50 ? `${body.substring(0, 50)}...` : body,
418
+ timestamp: new Date().toISOString(),
419
+ color: 'blue',
420
+ read: false,
421
+ });
422
+ }
423
+ }
424
+ } catch {
425
+ // Native inbox delivery is best-effort — PG message already persisted
426
+ }
427
+
402
428
  console.log(`Message sent to "${options.to}".`);
403
429
  console.log(` ID: ${msg.id}`);
404
430
  console.log(` Conversation: ${conv.id}`);