@automagik/genie 4.260506.3 → 4.260507.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js
CHANGED
|
@@ -476,12 +476,12 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
476
476
|
`).count>0,disbanded)await tx`
|
|
477
477
|
UPDATE agents SET state = 'archived', updated_at = now()
|
|
478
478
|
WHERE team = ${teamName} AND state IS DISTINCT FROM 'archived'
|
|
479
|
-
`}),!disbanded)return!1;return recordAuditEvent("team",teamName,"disbanded",getActor(),{repo:config.repo}).catch(()=>{}),await pruneStaleWorktrees(config.repo),!0}async function pruneStaleWorktrees(_repoPath){let sql=await getConnection(),rows=await sql`SELECT name, worktree_path FROM teams`;for(let row of rows)if(row.worktree_path&&!existsSync8(row.worktree_path)){try{await deleteNativeTeam(row.name)}catch{}await sql`DELETE FROM teams WHERE name = ${row.name}`}}async function updateTeamConfig(name,config){let sql=await getConnection();await sql`
|
|
479
|
+
`}),!disbanded)return!1;return recordAuditEvent("team",teamName,"disbanded",getActor(),{repo:config.repo}).catch(()=>{}),await pruneStaleWorktrees(config.repo),!0}async function pruneStaleWorktrees(_repoPath){let sql=await getConnection(),rows=await sql`SELECT name, worktree_path FROM teams`;for(let row of rows)if(row.worktree_path&&!existsSync8(row.worktree_path)){try{await deleteNativeTeam(row.name)}catch{}await sql`DELETE FROM teams WHERE name = ${row.name}`}}function sanitizeLeaderForWrite(teamName,leader){if(leader==null||leader==="")return null;if(LEADER_ID_RE.test(leader)||leader.startsWith("dir:"))return leader;return recordAuditEvent("team",teamName,"leader_sanitized",getActor(),{dropped:leader,reason:"fk_teams_leader_check"}).catch(()=>{}),null}async function updateTeamConfig(name,config){let sql=await getConnection(),safeLeader=sanitizeLeaderForWrite(name,config.leader);await sql`
|
|
480
480
|
UPDATE teams SET
|
|
481
481
|
repo = ${config.repo},
|
|
482
482
|
base_branch = ${config.baseBranch},
|
|
483
483
|
worktree_path = ${config.worktreePath},
|
|
484
|
-
leader = ${
|
|
484
|
+
leader = ${safeLeader},
|
|
485
485
|
members = ${sql.json(config.members)},
|
|
486
486
|
status = ${config.status},
|
|
487
487
|
native_team_parent_session_id = ${config.nativeTeamParentSessionId??null},
|
|
@@ -495,7 +495,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
495
495
|
`}async function getTeam(name){try{let rows=await(await getConnection())`SELECT * FROM teams WHERE name = ${name}`;if(rows.length===0)return null;return rowToTeamConfig(rows[0])}catch{return null}}async function listTeams(includeArchived=!1){try{let sql=await getConnection();if(includeArchived)return(await sql`SELECT * FROM teams ORDER BY created_at DESC`).map(rowToTeamConfig);return(await sql`SELECT * FROM teams WHERE status != 'archived' ORDER BY created_at DESC`).map(rowToTeamConfig)}catch{return[]}}async function listMembers(teamName){let config=await getTeam(teamName);if(!config)return null;return config.members}async function killTeamMembers(teamName){let config=await getTeam(teamName);if(!config)return;for(let member of config.members)try{await killWorkersByName(member,teamName)}catch{}}async function resolveLeaderName(teamName){try{let config=await getTeam(teamName);if(config?.leader&&config.leader!=="team-lead")return config.leader}catch{}return teamName}async function setTeamStatus(teamName,status){if(status==="done"||status==="archived"){let config=await getTeam(teamName);if(config)await Promise.allSettled(config.members.map((member)=>killWorkersByName(member,teamName))),await cleanupTeamTmuxSession(config.tmuxSessionName??teamName,teamName),await cleanupTeamGitArtifacts(config)}if((await(await getConnection())`
|
|
496
496
|
UPDATE teams SET status = ${status}
|
|
497
497
|
WHERE name = ${teamName}
|
|
498
|
-
`).count===0)throw Error(`Team "${teamName}" not found.`)}var init_team_manager=__esm(()=>{init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_db();init_executor_registry();init_genie_config2();init_tmux()});var exports_claude_native_teams={};__export(exports_claude_native_teams,{writeNativeInbox:()=>writeNativeInbox,unregisterNativeMember:()=>unregisterNativeMember,sanitizeTeamName:()=>sanitizeTeamName,resolveNativeMemberName:()=>resolveNativeMemberName,registerNativeMember:()=>registerNativeMember,registerAsTeamLead:()=>registerAsTeamLead,loadNativeTeamConfig:()=>loadNativeTeamConfig,loadConfig:()=>loadConfig,loadAllNativeTeamConfigs:()=>loadAllNativeTeamConfigs,listTeamsWithUnreadInbox:()=>listTeamsWithUnreadInbox,listTeams:()=>listTeams2,isInsideClaudeCode:()=>isInsideClaudeCode,findTeamsContainingAgent:()=>findTeamsContainingAgent,ensureNativeTeamWithSessionId:()=>ensureNativeTeamWithSessionId,ensureNativeTeam:()=>ensureNativeTeam,discoverTeamName:()=>discoverTeamName,discoverClaudeParentSessionId:()=>discoverClaudeParentSessionId,deleteNativeTeam:()=>deleteNativeTeam,clearNativeInbox:()=>clearNativeInbox,assignColor:()=>assignColor});import{existsSync as existsSync9}from"fs";import{mkdir as mkdir3,open as open2,readFile as readFile3,readdir as readdir2,rm,stat as stat2,writeFile as writeFile3}from"fs/promises";import{homedir as homedir8}from"os";import{join as join12}from"path";function claudeConfigDir2(){return process.env.CLAUDE_CONFIG_DIR??join12(homedir8(),".claude")}function teamsBaseDir(){return join12(claudeConfigDir2(),"teams")}function sanitizeTeamName(name){return name.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase()}async function listTeams2(){try{return(await readdir2(teamsBaseDir())).filter((e)=>!e.startsWith("."))}catch{return[]}}function teamDir(teamName){return join12(teamsBaseDir(),sanitizeTeamName(teamName))}function configPath(teamName){return join12(teamDir(teamName),"config.json")}function inboxesDir(teamName){return join12(teamDir(teamName),"inboxes")}function inboxPath(teamName,agentName){return join12(inboxesDir(teamName),`${sanitizeTeamName(agentName)}.json`)}async function loadConfig(teamName){try{let content=await readFile3(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 loadNativeTeamConfig(teamName){return loadConfig(teamName)}async function loadAllNativeTeamConfigs(){let teamNames=await listTeams2(),configs=[];for(let name of teamNames){let cfg=await loadConfig(name);if(cfg)configs.push(cfg)}return configs}async function findTeamsContainingAgent(agentName){let teams=await listTeams2(),matches=[];for(let teamName of teams){let config=await loadConfig(teamName);if(!config)continue;if(config.members.some((m)=>m.name===agentName||m.agentType===agentName))matches.push(teamName)}return matches}async function saveConfig(teamName,config){await writeFile3(configPath(teamName),JSON.stringify(config,null,2))}async function countLeadSessionRefs(){let counts=new Map,teams=await listTeams2();for(let team of teams){let leadSessionId=(await loadConfig(team))?.leadSessionId;if(!leadSessionId)continue;counts.set(leadSessionId,(counts.get(leadSessionId)??0)+1)}return counts}async function ensureNativeTeam(teamName,description,leadSessionId,leaderName){let dir=teamDir(teamName),inboxDir=inboxesDir(teamName);await mkdir3(dir,{recursive:!0}),await mkdir3(inboxDir,{recursive:!0}),ensureClaudeSettingsSafe();let existing=await loadConfig(teamName);if(existing)return await backfillTeamRow(sanitizeTeamName(teamName),existing),existing;let sanitized=sanitizeTeamName(teamName),resolvedLeader=sanitizeTeamName(leaderName??teamName),config={name:sanitized,description,createdAt:Date.now(),leadAgentId:`${resolvedLeader}@${sanitized}`,leadSessionId,members:[]};return await saveConfig(teamName,config),await backfillTeamRow(sanitized,config),config}async function backfillTeamRow(name,nativeConfig){try{let{ensureTeamRow:ensureTeamRow2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));await ensureTeamRow2(name,{nativeConfig})}catch{}}function isHealthyLeadSessionId(id){if(typeof id!=="string"||id.length===0)return!1;return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)}async function ensureNativeTeamWithSessionId(teamName,description,sessionId,leaderName){let config=await ensureNativeTeam(teamName,description,sessionId,leaderName);if(config.leadSessionId===sessionId)return config;if(isHealthyLeadSessionId(config.leadSessionId))return config;return config.leadSessionId=sessionId,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(!existsSync9(inbox))await writeFile3(inbox,"[]")}async function unregisterNativeMember(teamName,agentName){let config=await loadConfig(teamName);if(!config)return;let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(agentName)}@${sanitized}`,before=config.members.length;if(config.members=config.members.filter((m)=>m.agentId!==agentId),config.members.length===before)return;await saveConfig(teamName,config)}async function writeNativeInbox(teamName,agentName,message){let path2=inboxPath(teamName,agentName);await mkdir3(inboxesDir(teamName),{recursive:!0}),await acquireLock(path2);try{let messages=[];try{let content=await readFile3(path2,"utf-8");messages=JSON.parse(content)}catch{}messages.push(message),await writeFile3(path2,JSON.stringify(messages,null,2))}finally{await releaseLock(path2)}}async function resolveNativeMemberName(teamName,genieWorkerId){let config=await loadConfig(teamName);if(!config||config.members.length===0)return null;let sanitizedId=sanitizeTeamName(genieWorkerId),sanitizedTeam=sanitizeTeamName(teamName),exactMatch=config.members.find((m)=>m.name===sanitizedId&&m.isActive);if(exactMatch)return exactMatch.name;let agentIdMatch=config.members.find((m)=>m.agentId===`${sanitizedId}@${sanitizedTeam}`&&m.isActive);if(agentIdMatch)return agentIdMatch.name;let teamPrefix=`${sanitizedTeam}-`;if(sanitizedId.startsWith(teamPrefix)){let stripped=sanitizedId.slice(teamPrefix.length),prefixMatch=config.members.find((m)=>m.name===stripped&&m.isActive);if(prefixMatch)return prefixMatch.name}let inactiveMatch=config.members.find((m)=>m.name===sanitizedId);if(inactiveMatch)return inactiveMatch.name;return null}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 path2=inboxPath(teamName,agentName);await acquireLock(path2);try{await writeFile3(path2,"[]")}finally{await releaseLock(path2)}}async function deleteNativeTeam(teamName){let dir=teamDir(teamName);if(!existsSync9(dir))return!1;return await rm(dir,{recursive:!0,force:!0}),!0}function extractLeaderInboxName(config,teamName){if(!config?.leadAgentId)return teamName??"unknown";let atIdx=config.leadAgentId.indexOf("@");return atIdx>0?config.leadAgentId.slice(0,atIdx):teamName??"unknown"}function resolveLeadWorkingDir(config,leaderInboxName){let leadMember=config.members.find((m)=>m.agentId===config.leadAgentId||m.name===leaderInboxName);if(leadMember?.cwd)return leadMember.cwd;if(config.worktreePath)return config.worktreePath;if(config.repo)return config.repo;return config.members.find((m)=>m.cwd)?.cwd??null}async function scanTeamInbox(base,name){let config=null;try{let cfgContent=await readFile3(join12(base,name,"config.json"),"utf-8");config=JSON.parse(cfgContent)}catch{}let leaderInboxName=extractLeaderInboxName(config,name),inboxFile=join12(base,name,"inboxes",`${leaderInboxName}.json`),messages;try{let content=await readFile3(inboxFile,"utf-8");messages=JSON.parse(content)}catch{return null}if(!Array.isArray(messages))return null;let unread=messages.filter((m)=>m.read===!1);if(unread.length===0)return null;let workingDir=config?resolveLeadWorkingDir(config,leaderInboxName):null;return{teamName:name,unreadCount:unread.length,workingDir,firstUnreadText:unread[0]?.text??null}}async function listTeamsWithUnreadInbox(){let base=teamsBaseDir(),teamDirs;try{teamDirs=await readdir2(base)}catch{return[]}let results=[];for(let name of teamDirs){let entry=await scanTeamInbox(base,name);if(entry)results.push(entry)}return results}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=join12(claudeConfigDir2(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir2(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let newest=null;for(let name of jsonls){let s=await stat2(join12(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}}async function readSessionMetadata(filePath){let handle=null;try{handle=await open2(filePath,"r");let buffer=Buffer.alloc(8192),{bytesRead}=await handle.read(buffer,0,buffer.length,0),head=buffer.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
498
|
+
`).count===0)throw Error(`Team "${teamName}" not found.`)}var LEADER_ID_RE;var init_team_manager=__esm(()=>{init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_db();init_executor_registry();init_genie_config2();init_tmux();LEADER_ID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i});var exports_claude_native_teams={};__export(exports_claude_native_teams,{writeNativeInbox:()=>writeNativeInbox,unregisterNativeMember:()=>unregisterNativeMember,sanitizeTeamName:()=>sanitizeTeamName,resolveNativeMemberName:()=>resolveNativeMemberName,registerNativeMember:()=>registerNativeMember,registerAsTeamLead:()=>registerAsTeamLead,loadNativeTeamConfig:()=>loadNativeTeamConfig,loadConfig:()=>loadConfig,loadAllNativeTeamConfigs:()=>loadAllNativeTeamConfigs,listTeamsWithUnreadInbox:()=>listTeamsWithUnreadInbox,listTeams:()=>listTeams2,isInsideClaudeCode:()=>isInsideClaudeCode,findTeamsContainingAgent:()=>findTeamsContainingAgent,ensureNativeTeamWithSessionId:()=>ensureNativeTeamWithSessionId,ensureNativeTeam:()=>ensureNativeTeam,discoverTeamName:()=>discoverTeamName,discoverClaudeParentSessionId:()=>discoverClaudeParentSessionId,deleteNativeTeam:()=>deleteNativeTeam,clearNativeInbox:()=>clearNativeInbox,assignColor:()=>assignColor});import{existsSync as existsSync9}from"fs";import{mkdir as mkdir3,open as open2,readFile as readFile3,readdir as readdir2,rm,stat as stat2,writeFile as writeFile3}from"fs/promises";import{homedir as homedir8}from"os";import{join as join12}from"path";function claudeConfigDir2(){return process.env.CLAUDE_CONFIG_DIR??join12(homedir8(),".claude")}function teamsBaseDir(){return join12(claudeConfigDir2(),"teams")}function sanitizeTeamName(name){return name.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase()}async function listTeams2(){try{return(await readdir2(teamsBaseDir())).filter((e)=>!e.startsWith("."))}catch{return[]}}function teamDir(teamName){return join12(teamsBaseDir(),sanitizeTeamName(teamName))}function configPath(teamName){return join12(teamDir(teamName),"config.json")}function inboxesDir(teamName){return join12(teamDir(teamName),"inboxes")}function inboxPath(teamName,agentName){return join12(inboxesDir(teamName),`${sanitizeTeamName(agentName)}.json`)}async function loadConfig(teamName){try{let content=await readFile3(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 loadNativeTeamConfig(teamName){return loadConfig(teamName)}async function loadAllNativeTeamConfigs(){let teamNames=await listTeams2(),configs=[];for(let name of teamNames){let cfg=await loadConfig(name);if(cfg)configs.push(cfg)}return configs}async function findTeamsContainingAgent(agentName){let teams=await listTeams2(),matches=[];for(let teamName of teams){let config=await loadConfig(teamName);if(!config)continue;if(config.members.some((m)=>m.name===agentName||m.agentType===agentName))matches.push(teamName)}return matches}async function saveConfig(teamName,config){await writeFile3(configPath(teamName),JSON.stringify(config,null,2))}async function countLeadSessionRefs(){let counts=new Map,teams=await listTeams2();for(let team of teams){let leadSessionId=(await loadConfig(team))?.leadSessionId;if(!leadSessionId)continue;counts.set(leadSessionId,(counts.get(leadSessionId)??0)+1)}return counts}async function ensureNativeTeam(teamName,description,leadSessionId,leaderName){let dir=teamDir(teamName),inboxDir=inboxesDir(teamName);await mkdir3(dir,{recursive:!0}),await mkdir3(inboxDir,{recursive:!0}),ensureClaudeSettingsSafe();let existing=await loadConfig(teamName);if(existing)return await backfillTeamRow(sanitizeTeamName(teamName),existing),existing;let sanitized=sanitizeTeamName(teamName),resolvedLeader=sanitizeTeamName(leaderName??teamName),config={name:sanitized,description,createdAt:Date.now(),leadAgentId:`${resolvedLeader}@${sanitized}`,leadSessionId,members:[]};return await saveConfig(teamName,config),await backfillTeamRow(sanitized,config),config}async function backfillTeamRow(name,nativeConfig){try{let{ensureTeamRow:ensureTeamRow2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));await ensureTeamRow2(name,{nativeConfig})}catch{}}function isHealthyLeadSessionId(id){if(typeof id!=="string"||id.length===0)return!1;return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)}async function ensureNativeTeamWithSessionId(teamName,description,sessionId,leaderName){let config=await ensureNativeTeam(teamName,description,sessionId,leaderName);if(config.leadSessionId===sessionId)return config;if(isHealthyLeadSessionId(config.leadSessionId))return config;return config.leadSessionId=sessionId,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(!existsSync9(inbox))await writeFile3(inbox,"[]")}async function unregisterNativeMember(teamName,agentName){let config=await loadConfig(teamName);if(!config)return;let sanitized=sanitizeTeamName(teamName),agentId=`${sanitizeTeamName(agentName)}@${sanitized}`,before=config.members.length;if(config.members=config.members.filter((m)=>m.agentId!==agentId),config.members.length===before)return;await saveConfig(teamName,config)}async function writeNativeInbox(teamName,agentName,message){let path2=inboxPath(teamName,agentName);await mkdir3(inboxesDir(teamName),{recursive:!0}),await acquireLock(path2);try{let messages=[];try{let content=await readFile3(path2,"utf-8");messages=JSON.parse(content)}catch{}messages.push(message),await writeFile3(path2,JSON.stringify(messages,null,2))}finally{await releaseLock(path2)}}async function resolveNativeMemberName(teamName,genieWorkerId){let config=await loadConfig(teamName);if(!config||config.members.length===0)return null;let sanitizedId=sanitizeTeamName(genieWorkerId),sanitizedTeam=sanitizeTeamName(teamName),exactMatch=config.members.find((m)=>m.name===sanitizedId&&m.isActive);if(exactMatch)return exactMatch.name;let agentIdMatch=config.members.find((m)=>m.agentId===`${sanitizedId}@${sanitizedTeam}`&&m.isActive);if(agentIdMatch)return agentIdMatch.name;let teamPrefix=`${sanitizedTeam}-`;if(sanitizedId.startsWith(teamPrefix)){let stripped=sanitizedId.slice(teamPrefix.length),prefixMatch=config.members.find((m)=>m.name===stripped&&m.isActive);if(prefixMatch)return prefixMatch.name}let inactiveMatch=config.members.find((m)=>m.name===sanitizedId);if(inactiveMatch)return inactiveMatch.name;return null}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 path2=inboxPath(teamName,agentName);await acquireLock(path2);try{await writeFile3(path2,"[]")}finally{await releaseLock(path2)}}async function deleteNativeTeam(teamName){let dir=teamDir(teamName);if(!existsSync9(dir))return!1;return await rm(dir,{recursive:!0,force:!0}),!0}function extractLeaderInboxName(config,teamName){if(!config?.leadAgentId)return teamName??"unknown";let atIdx=config.leadAgentId.indexOf("@");return atIdx>0?config.leadAgentId.slice(0,atIdx):teamName??"unknown"}function resolveLeadWorkingDir(config,leaderInboxName){let leadMember=config.members.find((m)=>m.agentId===config.leadAgentId||m.name===leaderInboxName);if(leadMember?.cwd)return leadMember.cwd;if(config.worktreePath)return config.worktreePath;if(config.repo)return config.repo;return config.members.find((m)=>m.cwd)?.cwd??null}async function scanTeamInbox(base,name){let config=null;try{let cfgContent=await readFile3(join12(base,name,"config.json"),"utf-8");config=JSON.parse(cfgContent)}catch{}let leaderInboxName=extractLeaderInboxName(config,name),inboxFile=join12(base,name,"inboxes",`${leaderInboxName}.json`),messages;try{let content=await readFile3(inboxFile,"utf-8");messages=JSON.parse(content)}catch{return null}if(!Array.isArray(messages))return null;let unread=messages.filter((m)=>m.read===!1);if(unread.length===0)return null;let workingDir=config?resolveLeadWorkingDir(config,leaderInboxName):null;return{teamName:name,unreadCount:unread.length,workingDir,firstUnreadText:unread[0]?.text??null}}async function listTeamsWithUnreadInbox(){let base=teamsBaseDir(),teamDirs;try{teamDirs=await readdir2(base)}catch{return[]}let results=[];for(let name of teamDirs){let entry=await scanTeamInbox(base,name);if(entry)results.push(entry)}return results}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=join12(claudeConfigDir2(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir2(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let newest=null;for(let name of jsonls){let s=await stat2(join12(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}}async function readSessionMetadata(filePath){let handle=null;try{handle=await open2(filePath,"r");let buffer=Buffer.alloc(8192),{bytesRead}=await handle.read(buffer,0,buffer.length,0),head=buffer.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
499
499
|
`).slice(0,20)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:void 0,agentName=typeof entry.agentName==="string"?entry.agentName:void 0;if(teamName||agentName)return{teamName,agentName}}catch{}}}catch{return{}}finally{await handle?.close().catch(()=>{})}return{}}function rootScore(metadata){if(!metadata.teamName&&!metadata.agentName)return 2;if(metadata.teamName&&!metadata.agentName)return 1;return 0}function compareSessionRanking(a,b,leadRefs){let aLeadRefs=leadRefs.get(a.name.replace(".jsonl",""))??0,bLeadRefs=leadRefs.get(b.name.replace(".jsonl",""))??0;if(aLeadRefs!==bLeadRefs)return bLeadRefs-aLeadRefs;let aRoot=rootScore(a.metadata),bRoot=rootScore(b.metadata);if(aRoot!==bRoot)return bRoot-aRoot;return b.mtime-a.mtime}async function discoverClaudeParentSessionId(cwd){let envSessionId=process.env.CLAUDE_CODE_SESSION_ID;if(envSessionId)return envSessionId;let projectDir=join12(claudeConfigDir2(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir2(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let ranked=await Promise.all(jsonls.map(async(name)=>{let filePath=join12(projectDir,name),s=await stat2(filePath),metadata=await readSessionMetadata(filePath);return{name,mtime:s.mtimeMs,metadata}})),leadRefs=await countLeadSessionRefs();return ranked.sort((a,b)=>compareSessionRanking(a,b,leadRefs)),ranked[0]?.name.replace(".jsonl","")??null}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 base=teamsBaseDir(),sessionId=await discoverClaudeSessionId(cwd);if(sessionId)try{let teams=await readdir2(base);for(let name of teams){let cfgPath=join12(base,name,"config.json");try{let content=await readFile3(cfgPath,"utf-8"),config=JSON.parse(content);if(config.leadSessionId===sessionId)return config.name}catch{}}}catch{}let tmuxSessionName=await currentTmuxSessionName();if(tmuxSessionName){let cfgPath=join12(base,tmuxSessionName,"config.json");try{let content=await readFile3(cfgPath,"utf-8");return JSON.parse(content).name}catch{}}return null}async function currentTmuxSessionName(){if(!process.env.TMUX)return null;try{let{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));return await getCurrentSessionName2()}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 resolvedLeaderName=opts?.leaderName??teamName,config=await ensureNativeTeam(teamName,`Genie team: ${teamName}`,sessionId,resolvedLeaderName);if(config.leadSessionId!==sessionId)config.leadSessionId=sessionId,await saveConfig(teamName,config);let sanitized=sanitizeTeamName(teamName),leadAgentId=`${sanitizeTeamName(resolvedLeaderName)}@${sanitized}`,existingLead=config.members.find((m)=>m.agentId===leadAgentId),resolvedPaneId=opts?.tmuxPaneId??process.env.TMUX_PANE;if(!existingLead||!existingLead.isActive)await registerNativeMember(teamName,{agentName:resolvedLeaderName,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,resolvedLeaderName);if(!existsSync9(inbox))await writeFile3(inbox,"[]");let finalConfig=await loadConfig(teamName);if(!finalConfig)throw Error(`Failed to load config for team "${teamName}" after creation`);return{sessionId,config:finalConfig}}var init_claude_native_teams=__esm(()=>{init_claude_settings();init_lockfile();init_provider_adapters()});import{existsSync as existsSync10,readFileSync as readFileSync5,statSync}from"fs";import{mkdir as mkdir4,readFile as readFile4,readdir as readdir3,rename,writeFile as writeFile4}from"fs/promises";import{homedir as homedir9}from"os";import{join as join13}from"path";function getGenieHome(){return process.env.GENIE_HOME??join13(homedir9(),".genie")}function workersJsonPath(){return join13(getGenieHome(),"workers.json")}function claudeTeamsDirPath(){return join13(process.env.CLAUDE_CONFIG_DIR??join13(homedir9(),".claude"),"teams")}function teamsSeedMarkerPath(){return join13(getGenieHome(),"state","teams-seed-marker")}function teamsDirMtime(claudeTeamsDir){try{return String(statSync(claudeTeamsDir).mtimeMs)}catch{return null}}function readFreshTeamsSeedMarker(claudeTeamsDir){let mtimeMs=teamsDirMtime(claudeTeamsDir);if(!mtimeMs)return null;try{let marker=JSON.parse(readFileSync5(teamsSeedMarkerPath(),"utf-8"));if(marker.teamsDir!==claudeTeamsDir||marker.mtimeMs!==mtimeMs||!Array.isArray(marker.teamNames))return null;return{teamsDir:marker.teamsDir,mtimeMs:marker.mtimeMs,teamNames:marker.teamNames.filter((name)=>typeof name==="string"&&name.length>0)}}catch{return null}}function hasFreshTeamsSeedMarker(claudeTeamsDir){return readFreshTeamsSeedMarker(claudeTeamsDir)!==null}async function writeTeamsSeedMarker(teamNames){let claudeTeamsDir=claudeTeamsDirPath(),mtimeMs=teamsDirMtime(claudeTeamsDir);if(!mtimeMs)return;let markerPath=teamsSeedMarkerPath(),marker={teamsDir:claudeTeamsDir,mtimeMs,teamNames:[...new Set(teamNames)].sort()};await mkdir4(join13(getGenieHome(),"state"),{recursive:!0}),await writeFile4(markerPath,`${JSON.stringify(marker)}
|
|
500
500
|
`,"utf-8")}function needsMigration(filePath){return existsSync10(filePath)&&!existsSync10(`${filePath}.migrated`)}async function readJson(filePath){try{let content=await readFile4(filePath,"utf-8");return JSON.parse(content)}catch{return null}}async function renameMatchingFiles(dir,filter){if(!existsSync10(dir))return;try{let files=await readdir3(dir);for(let f of files){if(!filter(f))continue;let fp=join13(dir,f);if(needsMigration(fp))await rename(fp,`${fp}.migrated`)}}catch{}}function needsSeed(){if(needsMigration(workersJsonPath()))return!0;let claudeTeamsDir=claudeTeamsDirPath();if(!existsSync10(claudeTeamsDir))return!1;if(hasFreshTeamsSeedMarker(claudeTeamsDir))return!1;try{return __require("fs").readdirSync(claudeTeamsDir).some((e)=>!e.startsWith("."))}catch{return!1}}async function needsSeededTeams(sql){let marker=readFreshTeamsSeedMarker(claudeTeamsDirPath());if(!marker||marker.teamNames.length===0)return!1;try{return(await sql`
|
|
501
501
|
SELECT name FROM teams WHERE name = ANY(${marker.teamNames})
|
|
@@ -1154,12 +1154,12 @@ ${history}`}}}var MAX_COMMITS=5;var init_audit_context=()=>{};var exports_agent_
|
|
|
1154
1154
|
ORDER BY started_at DESC
|
|
1155
1155
|
LIMIT 1
|
|
1156
1156
|
`;return rows.length>0?rows[0].session:null}catch{return null}}async function ls(){let result2=[],seen=new Set;try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
|
|
1157
|
-
SELECT DISTINCT ON (a.role) a.role, a.team, a.metadata, a.created_at, e.repo_path, e.provider
|
|
1157
|
+
SELECT DISTINCT ON (a.role) a.id, a.role, a.team, a.metadata, a.created_at, e.repo_path, e.provider
|
|
1158
1158
|
FROM agents a
|
|
1159
1159
|
LEFT JOIN executors e ON a.current_executor_id = e.id
|
|
1160
1160
|
WHERE a.role IS NOT NULL
|
|
1161
1161
|
ORDER BY a.role, (CASE WHEN position('dir:' in a.id) = 1 THEN 0 ELSE 1 END), a.started_at DESC
|
|
1162
|
-
`;for(let row of rows){let name=row.role;if(!seen.has(name)){let meta=parseMetadata(row.metadata),createdAt=row.created_at instanceof Date?row.created_at.toISOString():row.created_at,entry2=roleToEntry(name,row.team,meta,createdAt)
|
|
1162
|
+
`;for(let row of rows){let name=row.role;if(!seen.has(name)){let meta=parseMetadata(row.metadata),createdAt=row.created_at instanceof Date?row.created_at.toISOString():row.created_at,entry2=roleToEntry(name,row.team,meta,createdAt);entry2.id=row.id;let repoPath=row.repo_path;if(repoPath)entry2.dir=repoPath,entry2.repo=repoPath;result2.push({...entry2,scope:"global"}),seen.add(name)}}}catch{}return result2}async function get2(name,_options){return(await resolve6(name))?.entry??null}async function edit(name,updates,options){if(updates.dir){if(!existsSync32(updates.dir))throw Error(`Directory does not exist: ${updates.dir}`);validateAgentsMd(updates.dir,options?.allowSymlink??!1)}let existing=await get2(name);if(!existing)throw Error(`Agent "${name}" not found in directory.`);let updated=Object.assign(existing,updates),metadataPatch=buildMetadata(updated);try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2();if((await sql`
|
|
1163
1163
|
UPDATE agents
|
|
1164
1164
|
SET metadata = metadata || ${sql.json(metadataPatch)}
|
|
1165
1165
|
WHERE id = ${`dir:${name}`}
|
|
@@ -1645,8 +1645,8 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
|
|
|
1645
1645
|
) AS latest_assignment_outcome
|
|
1646
1646
|
FROM agents a
|
|
1647
1647
|
WHERE a.id = ${agentId}
|
|
1648
|
-
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync39}from"fs";import{join as join45}from"path";function getSystemPromptFile(workingDir){let agentsPath=join45(workingDir,"AGENTS.md");if(existsSync39(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null;if(priorSessionId!==null)priorSessionId=await acquireResumeSessionForAttempt(leaderAgent.id).catch(()=>null)??priorSessionId;let sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,isCliSender:()=>isCliSender,handleBroadcast:()=>handleBroadcast,fanoutBroadcastToNativeInboxes:()=>fanoutBroadcastToNativeInboxes,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir32}from"os";import{join as join46}from"path";async function getRegistry2(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}function isCliSender(sender){return sender==="cli"||sender.startsWith("cli:")}async function detectSenderIdentity(teamName){let envId=process.env.GENIE_AGENT_ID;if(envId&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(envId))return envId;let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry2(),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??join46(homedir32(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join46(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(isCliSender(sender))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 reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1649
|
-
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;let registryMod=await getRegistry2(),senderId=await registryMod.resolveAgentId(from);if(!senderId)return null;return(await registryMod.get(senderId))?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}function broadcastAgentName(agent){return agent.customName??agent.role??agent.id}function mergeBroadcastRosterEntry(bySanitized,sanitized,patch){if(!sanitized)return;let existing=bySanitized.get(sanitized);bySanitized.set(sanitized,{agent:existing?.agent??patch.agent,sanitized,inboxName:patch.inboxName??existing?.inboxName??sanitized,agentId:existing?.agentId??patch.agentId,nativeColor:existing?.nativeColor??patch.nativeColor,nativeActive:existing?.nativeActive??patch.nativeActive})}async function resolveBroadcastRoster(teamName,deps){let[agents,nativeConfig]=await Promise.all([deps.listAgents({team:teamName}).catch(()=>[]),deps.loadConfig(teamName).catch(()=>null)]),bySanitized=new Map;for(let agent of agents){let name=broadcastAgentName(agent),sanitized=deps.sanitizeTeamName(name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:name,inboxName:sanitized,agentId:agent.id,nativeColor:agent.nativeColor})}for(let member of nativeConfig?.members??[]){let sanitized=deps.sanitizeTeamName(member.name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:member.name,inboxName:member.name,nativeColor:member.color,nativeActive:member.isActive})}return[...bySanitized.values()]}function makeBroadcastNativeMessage(from,body,timestamp2,color2){return{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:timestamp2,color:color2,read:!1}}async function broadcastSkipReason(entry2,deps){if(entry2.nativeActive===!1)return"offline";if(!entry2.agentId)return null;let state=await deps.getAgentEffectiveState(entry2.agentId).catch(()=>"offline");if(state==="offline")return"offline";if(state==="terminated")return"terminated";if(state==="error")return"dead";if(state==="done")return"done";return null}async function fanoutBroadcastToNativeInboxes(teamName,from,body,deps){let sender=deps.sanitizeTeamName(from),roster=await resolveBroadcastRoster(teamName,deps),timestamp2=(deps.now??(()=>new Date))().toISOString(),recipients=[];for(let entry2 of roster){if(entry2.sanitized===sender)continue;let skipReason=await broadcastSkipReason(entry2,deps);if(skipReason){recipients.push({agent:entry2.agent,delivered:!1,reason:skipReason});continue}try{await deps.writeNativeInbox(teamName,entry2.inboxName,makeBroadcastNativeMessage(from,body,timestamp2,entry2.nativeColor??"blue")),recipients.push({agent:entry2.agent,delivered:!0})}catch(err){recipients.push({agent:entry2.agent,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return recipients}async function handleBroadcast(body,options,deps={}){let ts3=deps.taskService??await getTaskService(),registry=deps.registry??await getRegistry2(),nativeTeams=deps.nativeTeams??await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),repoPath=deps.repoPath??process.cwd(),from=options.from??await detectSenderIdentity(options.team),teamName=await resolveTeamName2(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body),recipients=await fanoutBroadcastToNativeInboxes(teamName,from,body,{listAgents:registry.listAgents,getAgentEffectiveState:registry.getAgentEffectiveState,loadConfig:nativeTeams.loadConfig,sanitizeTeamName:nativeTeams.sanitizeTeamName,writeNativeInbox:nativeTeams.writeNativeInbox,now:deps.now});try{await(deps.publishSubjectEvent??(await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events))).publishSubjectEvent)(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,team:teamName,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName,recipients},source:"mailbox"})}catch{}let deliveredCount=recipients.filter((r)=>r.delivered).length;console.log(`Broadcast sent to team "${teamName}" (delivered to ${deliveredCount} of ${recipients.length} members).`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);for(let recipient of recipients)if(!recipient.delivered)console.log(` ${recipient.agent}: ${recipient.reason??"delivery failed"}`);return{messageId:msg.id,conversationId:conv.id,team:teamName,recipients}}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let registryMod=await getRegistry2(),teamScope=options.team??process.env.GENIE_TEAM,toAgentId=await registryMod.resolveAgentIdStrict(to,teamScope),senderActor=localActor(from),recipientActor=localActor(toAgentId),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,toAgentId,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,toAgentId,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1648
|
+
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync39}from"fs";import{join as join45}from"path";function getSystemPromptFile(workingDir){let agentsPath=join45(workingDir,"AGENTS.md");if(existsSync39(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null;if(priorSessionId!==null)priorSessionId=await acquireResumeSessionForAttempt(leaderAgent.id).catch(()=>null)??priorSessionId;let sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,parseAtSyntax:()=>parseAtSyntax,isCliSender:()=>isCliSender,handleBroadcast:()=>handleBroadcast,fanoutBroadcastToNativeInboxes:()=>fanoutBroadcastToNativeInboxes,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir32}from"os";import{join as join46}from"path";async function getRegistry2(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}function isCliSender(sender){return sender==="cli"||sender.startsWith("cli:")}async function detectSenderIdentity(teamName){let envId=process.env.GENIE_AGENT_ID;if(envId&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(envId))return envId;let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry2(),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??join46(homedir32(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join46(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}function parseAtSyntax(recipient,defaultTeam){let atIdx=recipient.indexOf("@");if(atIdx===-1)return{recipient,team:defaultTeam};let role=recipient.slice(0,atIdx),team=recipient.slice(atIdx+1);if(!role||!team)return{recipient,team:defaultTeam};return{recipient:role,team}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(isCliSender(sender))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 reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1649
|
+
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;let registryMod=await getRegistry2(),senderId=await registryMod.resolveAgentId(from);if(!senderId)return null;return(await registryMod.get(senderId))?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}function broadcastAgentName(agent){return agent.customName??agent.role??agent.id}function mergeBroadcastRosterEntry(bySanitized,sanitized,patch){if(!sanitized)return;let existing=bySanitized.get(sanitized);bySanitized.set(sanitized,{agent:existing?.agent??patch.agent,sanitized,inboxName:patch.inboxName??existing?.inboxName??sanitized,agentId:existing?.agentId??patch.agentId,nativeColor:existing?.nativeColor??patch.nativeColor,nativeActive:existing?.nativeActive??patch.nativeActive})}async function resolveBroadcastRoster(teamName,deps){let[agents,nativeConfig]=await Promise.all([deps.listAgents({team:teamName}).catch(()=>[]),deps.loadConfig(teamName).catch(()=>null)]),bySanitized=new Map;for(let agent of agents){let name=broadcastAgentName(agent),sanitized=deps.sanitizeTeamName(name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:name,inboxName:sanitized,agentId:agent.id,nativeColor:agent.nativeColor})}for(let member of nativeConfig?.members??[]){let sanitized=deps.sanitizeTeamName(member.name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:member.name,inboxName:member.name,nativeColor:member.color,nativeActive:member.isActive})}return[...bySanitized.values()]}function makeBroadcastNativeMessage(from,body,timestamp2,color2){return{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:timestamp2,color:color2,read:!1}}async function broadcastSkipReason(entry2,deps){if(entry2.nativeActive===!1)return"offline";if(!entry2.agentId)return null;let state=await deps.getAgentEffectiveState(entry2.agentId).catch(()=>"offline");if(state==="offline")return"offline";if(state==="terminated")return"terminated";if(state==="error")return"dead";if(state==="done")return"done";return null}async function fanoutBroadcastToNativeInboxes(teamName,from,body,deps){let sender=deps.sanitizeTeamName(from),roster=await resolveBroadcastRoster(teamName,deps),timestamp2=(deps.now??(()=>new Date))().toISOString(),recipients=[];for(let entry2 of roster){if(entry2.sanitized===sender)continue;let skipReason=await broadcastSkipReason(entry2,deps);if(skipReason){recipients.push({agent:entry2.agent,delivered:!1,reason:skipReason});continue}try{await deps.writeNativeInbox(teamName,entry2.inboxName,makeBroadcastNativeMessage(from,body,timestamp2,entry2.nativeColor??"blue")),recipients.push({agent:entry2.agent,delivered:!0})}catch(err){recipients.push({agent:entry2.agent,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return recipients}async function handleBroadcast(body,options,deps={}){let ts3=deps.taskService??await getTaskService(),registry=deps.registry??await getRegistry2(),nativeTeams=deps.nativeTeams??await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),repoPath=deps.repoPath??process.cwd(),from=options.from??await detectSenderIdentity(options.team),teamName=await resolveTeamName2(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body),recipients=await fanoutBroadcastToNativeInboxes(teamName,from,body,{listAgents:registry.listAgents,getAgentEffectiveState:registry.getAgentEffectiveState,loadConfig:nativeTeams.loadConfig,sanitizeTeamName:nativeTeams.sanitizeTeamName,writeNativeInbox:nativeTeams.writeNativeInbox,now:deps.now});try{await(deps.publishSubjectEvent??(await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events))).publishSubjectEvent)(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,team:teamName,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName,recipients},source:"mailbox"})}catch{}let deliveredCount=recipients.filter((r)=>r.delivered).length;console.log(`Broadcast sent to team "${teamName}" (delivered to ${deliveredCount} of ${recipients.length} members).`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);for(let recipient of recipients)if(!recipient.delivered)console.log(` ${recipient.agent}: ${recipient.reason??"delivery failed"}`);return{messageId:msg.id,conversationId:conv.id,team:teamName,recipients}}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),parsed=parseAtSyntax(options.to,options.team),to=await resolveLeaderAlias(parsed.recipient,parsed.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let registryMod=await getRegistry2(),teamScope=parsed.team??options.team??process.env.GENIE_TEAM,toAgentId=await registryMod.resolveAgentIdStrict(to,teamScope),senderActor=localActor(from),recipientActor=localActor(toAgentId),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,toAgentId,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,teamScope).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,toAgentId,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1650
1650
|
Examples:
|
|
1651
1651
|
genie send 'start task #3' --to engineer # Message a specific agent
|
|
1652
1652
|
genie send 'status update' --to team-lead # Report to team lead
|
|
@@ -2170,7 +2170,7 @@ Owner identities are exclusive \u2014 to spawn a separate worker under the same
|
|
|
2170
2170
|
`;if(anchor.length>0)await relinkExecutorToAgent(anchor[0].id,agentId),createdAnchor=!0,decision=await shouldResume(agentId)}}}return await recordAuditEvent("agent",agentId,"recover.surgery_applied",getActor(),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}).catch(()=>{}),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}}async function jsonlHeadMatchesCustomName(filePath,customName){let{open:open4}=await import("fs/promises"),handle=null;try{handle=await open4(filePath,"r");let buffer2=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer2,0,buffer2.length,0),head=buffer2.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
2171
2171
|
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry2=JSON.parse(trimmed);if(typeof entry2.agentName==="string"&&entry2.agentName===customName)return!0}catch{}}return!1}catch{return!1}finally{await handle?.close().catch(()=>{})}}async function resolveClaudeProjectDir(cwd){let{join:join51}=await import("path"),{homedir:homedir34}=await import("os"),sanitize=(p)=>p.replace(/[^a-zA-Z0-9]/g,"-"),claudeConfigDir5=process.env.CLAUDE_CONFIG_DIR??join51(homedir34(),".claude");return join51(claudeConfigDir5,"projects",sanitize(cwd))}async function listJsonlsByMtime(projectDir){let{readdir:readdir6,stat:stat5}=await import("fs/promises"),{join:join51}=await import("path"),entries;try{entries=await readdir6(projectDir)}catch{return[]}let jsonls=entries.filter((e)=>e.endsWith(".jsonl")),candidates=[];for(let name of jsonls){let full=join51(projectDir,name);try{let s=await stat5(full);candidates.push({name,full,mtime:s.mtimeMs})}catch{}}return candidates.sort((a,b2)=>b2.mtime-a.mtime),candidates}async function scanJsonlByCustomName(cwd,customName){let projectDir=await resolveClaudeProjectDir(cwd),candidates=await listJsonlsByMtime(projectDir);for(let candidate of candidates)if(await jsonlHeadMatchesCustomName(candidate.full,customName))return candidate.name.replace(/\.jsonl$/,"");return null}async function confirmRecover(agentId){if(!process.stdin.isTTY)return console.error(`Refusing to recover "${agentId}" without --yes in a non-interactive shell. Re-run with \`--yes\` for unattended use.`),!1;let{createInterface:createInterface3}=await import("readline"),rl=createInterface3({input:process.stdin,output:process.stdout}),answer=await new Promise((resolve7)=>{rl.question(`About to recover agent "${agentId}" (flip auto_resume, terminate stale spawning executors, resume). Continue? [y/N] `,(a)=>{rl.close(),resolve7(a.trim().toLowerCase())})});return answer==="y"||answer==="yes"}async function handleWorkerRecover(name,options){let agent=await resolveAgentForRecover(name);if(!options.yes){if(!await confirmRecover(agent.id)){console.log("Recovery cancelled.");return}}console.log(`Recovering agent "${agent.id}"...`);let surgery=await recoverSurgery(agent.id);if(surgery.flippedAutoResume)console.log(" \u2713 auto_resume re-enabled");if(surgery.staleSpawningTerminated>0)console.log(` \u2713 terminated ${surgery.staleSpawningTerminated} stale spawning executor(s)`);if(surgery.createdAnchor)console.log(" \u2713 created executor anchor from JSONL on disk");if(surgery.sessionId)console.log(` \u2713 session UUID located: ${surgery.sessionId}`);else console.error(` \u2717 no recoverable session UUID for "${agent.id}". JSONL scan in ${agent.repoPath??"<no repo_path>"} did not match custom_name="${agent.customName??agent.role??"<none>"}".`),process.exit(1);let resumeName=agent.customName??agent.role??agent.id;await handleWorkerResume(resumeName,{all:!1,noResetAttempts:!1});let post=await get(agent.id);if(post?.paneId&&post.session)console.log(` pane: ${post.paneId}`),console.log(` attach: tmux attach -t ${post.session}`)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
|
|
2172
2172
|
`)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let sessionId=await acquireResumeSessionForAttempt(agent.id)??decision.sessionId,params=await buildResumeParams(agent,template,sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"worker.resume.attempted",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,claude_session_id:params.resume,pane_id:paneId}).catch(()=>{});let resumedPid=null;try{if(paneId!=="inline"){let{execSync:execSync10}=__require("child_process"),pidOutput=execSync10(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),parsedPid=Number.parseInt(pidOutput,10);if(resumedPid=parsedPid>0?parsedPid:null,!execSync10(genieTmuxCmd("list-panes -a -F '#{pane_id}'"),{encoding:"utf-8"}).split(`
|
|
2173
|
-
`).map((line)=>line.trim()).includes(paneId))throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pane_not_in_list");if(resumedPid!==null)try{process.kill(resumedPid,0)}catch(err){if(err.code==="ESRCH")throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pid_dead")}}}catch(err){if(err instanceof ResumePaneVanishedError)throw recordAuditEvent("worker",agent.id,"worker.resume.skipped",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,reason:err.reason,error:err.message}).catch(()=>{}),err;throw err}recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordAuditEvent("worker",agent.id,"worker.resume.completed",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,claude_session_id:params.resume}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await listForRender(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status2,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status2.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var SpawnPaneVanishedError,AgentReadinessTimeoutError,OwnerSpawnCollisionError,_spawnAutoSyncDeps,UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES,ResumePaneVanishedError;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_agent_sync();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_frontmatter();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();SpawnPaneVanishedError=class SpawnPaneVanishedError extends Error{paneId;expectedPid;reason;constructor(paneId,expectedPid,reason){super(`Spawn pane "${paneId}" (pid=${expectedPid??"unknown"}) vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched script likely failed to exec the worker binary; check ~/.genie/spawn-scripts/ and tmux server log.`);this.name="SpawnPaneVanishedError",this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}};AgentReadinessTimeoutError=class AgentReadinessTimeoutError extends Error{role;paneId;elapsedMs;constructor(role,paneId,elapsedMs){super(`Agent "${role}" did not become ready within ${Math.round(elapsedMs/1000)}s (pane ${paneId}). This likely means the worker process exited before the readiness probe succeeded. Pass tolerateReadinessTimeout:true on SpawnOptions to fall back to warn-and-proceed.`);this.name="AgentReadinessTimeoutError",this.role=role,this.paneId=paneId,this.elapsedMs=elapsedMs}};OwnerSpawnCollisionError=class OwnerSpawnCollisionError extends Error{ownerName;team;conflictId;conflictPaneId;conflictState;name="OwnerSpawnCollisionError";constructor(ownerName,team,conflictId,conflictPaneId,conflictState){super(`owner agent "${ownerName}" already alive in team "${team}"`);this.ownerName=ownerName;this.team=team;this.conflictId=conflictId;this.conflictPaneId=conflictPaneId;this.conflictState=conflictState}};_spawnAutoSyncDeps={resolveAgent:resolve6,loadIdentity,syncSingleAgentByName,findWorkspace,parseFrontmatter,existsSync:existsSync42,readFileSync:readFileSync27,stderr:(line)=>console.error(line)};UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"]);ResumePaneVanishedError=class ResumePaneVanishedError extends Error{agentId;paneId;expectedPid;reason;constructor(agentId,paneId,expectedPid,reason){super(`Resumed pane "${paneId}" (pid=${expectedPid??"unknown"}) for agent "${agentId}" vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched resume script likely failed to exec the worker binary.`);this.name="ResumePaneVanishedError",this.agentId=agentId,this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}}});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile11,readdir as readdir6}from"fs/promises";import{homedir as homedir34}from"os";import{join as join51}from"path";function getCodexDir(){return join51(homedir34(),".codex")}function getSessionsDir(){return join51(getCodexDir(),"sessions")}function getStateDbPath(){return join51(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join51(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join51(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join51(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join51(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile11(filePath,"utf-8"),nlIdx=content.indexOf(`
|
|
2173
|
+
`).map((line)=>line.trim()).includes(paneId))throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pane_not_in_list");if(resumedPid!==null)try{process.kill(resumedPid,0)}catch(err){if(err.code==="ESRCH")throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pid_dead")}}}catch(err){if(err instanceof ResumePaneVanishedError)throw recordAuditEvent("worker",agent.id,"worker.resume.skipped",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,reason:err.reason,error:err.message}).catch(()=>{}),err;throw err}recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordAuditEvent("worker",agent.id,"worker.resume.completed",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,claude_session_id:params.resume}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{id:w.id,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,{id:w.id,state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await listForRender(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({id:entry2.id??running2?.id,name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({id:info.id,name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status2,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status2.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var SpawnPaneVanishedError,AgentReadinessTimeoutError,OwnerSpawnCollisionError,_spawnAutoSyncDeps,UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES,ResumePaneVanishedError;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_agent_sync();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_frontmatter();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();SpawnPaneVanishedError=class SpawnPaneVanishedError extends Error{paneId;expectedPid;reason;constructor(paneId,expectedPid,reason){super(`Spawn pane "${paneId}" (pid=${expectedPid??"unknown"}) vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched script likely failed to exec the worker binary; check ~/.genie/spawn-scripts/ and tmux server log.`);this.name="SpawnPaneVanishedError",this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}};AgentReadinessTimeoutError=class AgentReadinessTimeoutError extends Error{role;paneId;elapsedMs;constructor(role,paneId,elapsedMs){super(`Agent "${role}" did not become ready within ${Math.round(elapsedMs/1000)}s (pane ${paneId}). This likely means the worker process exited before the readiness probe succeeded. Pass tolerateReadinessTimeout:true on SpawnOptions to fall back to warn-and-proceed.`);this.name="AgentReadinessTimeoutError",this.role=role,this.paneId=paneId,this.elapsedMs=elapsedMs}};OwnerSpawnCollisionError=class OwnerSpawnCollisionError extends Error{ownerName;team;conflictId;conflictPaneId;conflictState;name="OwnerSpawnCollisionError";constructor(ownerName,team,conflictId,conflictPaneId,conflictState){super(`owner agent "${ownerName}" already alive in team "${team}"`);this.ownerName=ownerName;this.team=team;this.conflictId=conflictId;this.conflictPaneId=conflictPaneId;this.conflictState=conflictState}};_spawnAutoSyncDeps={resolveAgent:resolve6,loadIdentity,syncSingleAgentByName,findWorkspace,parseFrontmatter,existsSync:existsSync42,readFileSync:readFileSync27,stderr:(line)=>console.error(line)};UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"]);ResumePaneVanishedError=class ResumePaneVanishedError extends Error{agentId;paneId;expectedPid;reason;constructor(agentId,paneId,expectedPid,reason){super(`Resumed pane "${paneId}" (pid=${expectedPid??"unknown"}) for agent "${agentId}" vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched resume script likely failed to exec the worker binary.`);this.name="ResumePaneVanishedError",this.agentId=agentId,this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}}});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile11,readdir as readdir6}from"fs/promises";import{homedir as homedir34}from"os";import{join as join51}from"path";function getCodexDir(){return join51(homedir34(),".codex")}function getSessionsDir(){return join51(getCodexDir(),"sessions")}function getStateDbPath(){return join51(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join51(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join51(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join51(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join51(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile11(filePath,"utf-8"),nlIdx=content.indexOf(`
|
|
2174
2174
|
`),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry2=JSON.parse(firstLine);if(entry2.type==="session_meta"&&entry2.payload?.cwd)return{cwd:entry2.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query2=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query2.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query:query2}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile11(logPath,"utf-8")}catch{return[]}return content.split(`
|
|
2175
2175
|
`).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result2=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result2=result2.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
|
|
2176
2176
|
`).filter((line)=>!isTmuxMarkerOrNoise(line));while(filtered.length>0&&filtered[0].trim()==="")filtered.shift();while(filtered.length>0&&filtered[filtered.length-1].trim()==="")filtered.pop();return filtered.join(`
|
|
@@ -2947,11 +2947,11 @@ Examples:
|
|
|
2947
2947
|
genie team create hotfix --repo . --branch main # Create from main branch`).action(async(name,options)=>{try{let merged={...options,tmuxSession:options.tmuxSession??options.session};await handleTeamCreate(name,merged)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--all","Include archived teams").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("archive <name>").description("Archive a team (preserves all data, kills members)").action(async(name)=>{try{if(await archiveTeam(name))console.log(`Team "${name}" archived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("unarchive <name>").description("Restore an archived team").action(async(name)=>{try{if(await unarchiveTeam(name))console.log(`Team "${name}" unarchived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team (archives \u2014 preserves data). Use `genie team archive` directly.").action(async(name)=>{try{if(await disbandTeam(name))console.log("Note: disband now archives the team. Use `genie team archive` directly."),console.log(`Team "${name}" disbanded (archived).`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("cleanup").description("Kill tmux windows for done/archived teams").option("--dry-run","Show what would be cleaned without doing it").action(async(options)=>{try{await handleTeamCleanup(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("repair <name>").description("Archive an orphaned team-config dir to ~/.claude/teams/_archive/").option("--dry-run","Show what would be archived without doing it").addHelpText("after",`
|
|
2948
2948
|
This command resolves the doctor's "team_config_orphans" warning by moving
|
|
2949
2949
|
~/.claude/teams/<name>/ (which has no corresponding PG team row) into the
|
|
2950
|
-
_archive/ subdirectory. Inboxes are preserved on disk; nothing is deleted.`).action(async(name,options)=>{try{await handleTeamRepair(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve14(options.repo);if(options.wish){let wishPath=join73(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync61(wishPath)){let cwdWishDir=join73(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join73(cwdWishDir,"WISH.md");if(existsSync61(cwdWishPath)){let destDir=join73(resolvedRepo,".genie","wishes",options.wish);await mkdir8(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve14(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let leaderAgent=config.name;config.
|
|
2950
|
+
_archive/ subdirectory. Inboxes are preserved on disk; nothing is deleted.`).action(async(name,options)=>{try{await handleTeamRepair(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve14(options.repo);if(options.wish){let wishPath=join73(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync61(wishPath)){let cwdWishDir=join73(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join73(cwdWishDir,"WISH.md");if(existsSync61(cwdWishPath)){let destDir=join73(resolvedRepo,".genie","wishes",options.wish);await mkdir8(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve14(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let leaderAgent=config.name;config.spawner=process.env.GENIE_AGENT_NAME||"cli",await updateTeamConfig(config.name,config);let sourceWishPath=join73(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync61(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join73(config.worktreePath,".genie","wishes",slug);await mkdir8(destWishDir,{recursive:!0});let destWishPath=join73(destWishDir,"WISH.md");await copyFile3(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardRoles=["team-lead","engineer","reviewer","qa","fix"];for(let role of standardRoles)await hireAgent(config.name,role);console.log(` Team: hired ${standardRoles.join(", ")}`);let members=standardRoles.filter((r)=>r!=="team-lead").join(", "),spawner=config.spawner||"cli",kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`,broadcasts=await getRecentBroadcasts(config.name);if(broadcasts.length>0){let broadcastLines=broadcasts.map((b2)=>`[${b2.sender}] ${b2.text}`).join(`
|
|
2951
2951
|
`);kickoffPrompt+=`
|
|
2952
2952
|
|
|
2953
2953
|
Recent team broadcasts for context:
|
|
2954
|
-
${broadcastLines}`}await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,role:leaderAgent,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt}),console.log(` Leader: ${leaderAgent} spawned as ${slug}`)}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams();if(teams.length===1)return teams[0].name;return null}async function getRecentBroadcasts(teamName,minutesAgo=5){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),since=new Date(Date.now()-minutesAgo*60*1000);return(await sql`
|
|
2954
|
+
${broadcastLines}`}await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,role:leaderAgent,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let{getAgentByName:getAgentByName2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),leader=await getAgentByName2(leaderAgent,config.name).catch(()=>null);if(leader?.id)config.leader=leader.id,await updateTeamConfig(config.name,config);console.log(` Leader: ${leaderAgent} spawned as ${slug}`)}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams();if(teams.length===1)return teams[0].name;return null}async function getRecentBroadcasts(teamName,minutesAgo=5){try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),since=new Date(Date.now()-minutesAgo*60*1000);return(await sql`
|
|
2955
2955
|
SELECT m.sender_id, m.body, m.created_at
|
|
2956
2956
|
FROM messages m
|
|
2957
2957
|
JOIN conversations c ON c.id = m.conversation_id
|
|
@@ -3985,8 +3985,8 @@ module.exports = {
|
|
|
3985
3985
|
`);return}process.stdout.write(`id status appliedAt from
|
|
3986
3986
|
`),process.stdout.write(`------------------------------- ---------- ------------------------ ------------
|
|
3987
3987
|
`);for(let r of rows){let id=r.id.padEnd(31),st=r.status.padEnd(10),at=(r.appliedAt||"-").padEnd(24),from=r.appliedFrom||"-";process.stdout.write(`${id} ${st} ${at} ${from}
|
|
3988
|
-
`)}return}let result2=await migrate({quiet:options.quiet,dryRun:options.dryRun});process.exit(result2.ok?0:1)}init_setup();init_shortcuts();import{existsSync as existsSync26}from"fs";import{homedir as homedir26}from"os";import{join as join32}from"path";async function shortcutsShowCommand(){displayShortcuts();let home=homedir26(),tmuxConf=join32(home,".tmux.conf"),zshrc=join32(home,".zshrc"),bashrc=join32(home,".bashrc");if(console.log("Installation status:"),isShortcutsInstalled(tmuxConf))console.log(" \x1B[32m\u2713\x1B[0m tmux.conf");else console.log(" \x1B[33m-\x1B[0m tmux.conf");let shellRc=existsSync26(zshrc)?zshrc:bashrc;if(isShortcutsInstalled(shellRc))console.log(` \x1B[32m\u2713\x1B[0m ${shellRc.replace(home,"~")}`);else console.log(` \x1B[33m-\x1B[0m ${shellRc.replace(home,"~")}`);console.log(),console.log("Run \x1B[36mgenie shortcuts install\x1B[0m to install shortcuts."),console.log("Run \x1B[36mgenie shortcuts uninstall\x1B[0m to remove shortcuts."),console.log()}async function shortcutsInstallCommand(){await installShortcuts()}async function shortcutsUninstallCommand(){await uninstallShortcuts()}init_esm14();init_claude_settings();init_genie_config2();import{existsSync as existsSync27,lstatSync,rmSync as rmSync2,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir27}from"os";import{join as join33}from"path";var ORCHESTRATION_RULES_PATH=join33(homedir27(),".claude","rules","genie-orchestration.md"),LOCAL_BIN=join33(homedir27(),".local","bin"),SYMLINKS=["genie","term"];function isGenieSymlink(path3){try{if(!existsSync27(path3))return!1;if(!lstatSync(path3).isSymbolicLink())return!1;return!0}catch{return!1}}function removeSymlinks(){let removed=[];for(let name of SYMLINKS){let symlinkPath=join33(LOCAL_BIN,name);if(isGenieSymlink(symlinkPath))try{unlinkSync8(symlinkPath),removed.push(name)}catch{}}return removed}function tryRemoveStep(label,successMsg,fn){console.log(`\x1B[2m${label}\x1B[0m`);try{fn(),console.log(` \x1B[32m+\x1B[0m ${successMsg}`)}catch(error){let message=error instanceof Error?error.message:String(error);console.log(` \x1B[33m!\x1B[0m ${label.replace("...","")} failed: ${message}`)}}function performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir){if(hasHookScript)tryRemoveStep("Removing hook script...","Hook script removed",()=>removeHookScript());if(existingSymlinks.length>0){console.log("\x1B[2mRemoving symlinks...\x1B[0m");let removed=removeSymlinks();if(removed.length>0)console.log(` \x1B[32m+\x1B[0m Removed: ${removed.join(", ")}`)}if(existsSync27(ORCHESTRATION_RULES_PATH))tryRemoveStep("Removing orchestration rules...","Orchestration rules removed (~/.claude/rules/genie-orchestration.md)",()=>unlinkSync8(ORCHESTRATION_RULES_PATH));if(hasGenieDir)tryRemoveStep("Removing genie directory...","Directory removed",()=>rmSync2(genieDir,{recursive:!0,force:!0}))}async function uninstallCommand(){console.log(),console.log("\x1B[1m\x1B[33m Uninstall Genie CLI\x1B[0m"),console.log();let genieDir=getGenieDir(),hasGenieDir=existsSync27(genieDir),hasHookScript=hookScriptExists(),hasOrchestrationRules=existsSync27(ORCHESTRATION_RULES_PATH),existingSymlinks=SYMLINKS.filter((name)=>isGenieSymlink(join33(LOCAL_BIN,name)));if(console.log("\x1B[2mThis will remove:\x1B[0m"),hasHookScript)console.log(" \x1B[31m-\x1B[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)");if(hasOrchestrationRules)console.log(" \x1B[31m-\x1B[0m Orchestration rules (~/.claude/rules/genie-orchestration.md)");if(hasGenieDir)console.log(` \x1B[31m-\x1B[0m Genie directory (${contractPath(genieDir)})`);if(existingSymlinks.length>0)console.log(` \x1B[31m-\x1B[0m Symlinks from ~/.local/bin: ${existingSymlinks.join(", ")}`);if(console.log(),!hasGenieDir&&!hasHookScript&&!hasOrchestrationRules&&existingSymlinks.length===0){console.log("\x1B[33mNothing to uninstall.\x1B[0m"),console.log();return}if(!await esm_default4({message:"Are you sure you want to uninstall Genie CLI?",default:!1})){console.log(),console.log("\x1B[2mUninstall cancelled.\x1B[0m"),console.log();return}console.log(),performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir),console.log(),console.log("\x1B[32m+\x1B[0m Genie CLI uninstalled."),console.log(),console.log("\x1B[2mNote: If you installed via npm/bun, also run:\x1B[0m"),console.log(" \x1B[36mbun remove -g @automagik/genie\x1B[0m"),console.log(" \x1B[2mor\x1B[0m"),console.log(" \x1B[36mnpm uninstall -g @automagik/genie\x1B[0m"),console.log()}init_genie_config2();init_version();import{execSync as execSync3,spawn as spawn3}from"child_process";import{chmodSync as chmodSync2,closeSync as closeSync3,copyFileSync as copyFileSync3,existsSync as existsSync29,mkdirSync as mkdirSync15,openSync as openSync3,readFileSync as readFileSync20,readSync as readSync2,readdirSync as readdirSync9,rmSync as rmSync3,statSync as statSync7,writeFileSync as writeFileSync13}from"fs";import{chmod,copyFile,mkdir as mkdir5,unlink as unlink2}from"fs/promises";import{homedir as homedir28}from"os";import{join as join34}from"path";var REGISTRY=[];async function cleanupLegacyArtifacts(skipList,registry=REGISTRY){let entries=[];for(let artifact of registry){if(skipList.has(artifact.name)){entries.push({name:artifact.name,outcome:"skipped",removed:[],warnings:[]});continue}if(!await artifact.detect()){entries.push({name:artifact.name,outcome:"absent",removed:[],warnings:[]});continue}let result2=await artifact.cleanup();entries.push({name:artifact.name,outcome:"cleaned",removed:result2.removed,warnings:result2.warnings})}return{entries}}function parseSkipCleanupFlag(value){let skip=new Set;if(!value)return skip;for(let part of value.split(",")){let name=part.trim();if(name)skip.add(name)}return skip}var GENIE_HOME2=process.env.GENIE_HOME||join34(homedir28(),".genie"),GENIE_SRC=join34(GENIE_HOME2,"src"),GENIE_BIN=join34(GENIE_HOME2,"bin"),LOCAL_BIN2=join34(homedir28(),".local","bin"),TRUTHY=new Set(["1","true","yes","on"]),UPDATE_DIAGNOSTIC_SCHEMA_VERSION=2,VERIFY_PROBE_DEADLINE_MS=15000,VERIFY_PROBE_POLL_INTERVAL_MS=500,FETCH_LATEST_TIMEOUT_MS=5000;function normalizeVersion(value){let trimmed=value.trim(),plusIdx=trimmed.indexOf("+");return plusIdx===-1?trimmed:trimmed.slice(0,plusIdx)}function decideVerify(args){if(args.skipReason)return{kind:"skipped",reason:args.skipReason};if(args.serverHealthBody===null)return{kind:"health-unreachable",endpoint:args.endpoint};let cliVersion=normalizeVersion(args.cliVersion),rawServerVersion=args.serverHealthBody.version??null,serverVersion=rawServerVersion===null?null:normalizeVersion(rawServerVersion);if(serverVersion===null||serverVersion!==cliVersion)return{kind:"version-mismatch",cliVersion,serverVersion};return{kind:"ok",cliVersion,serverVersion}}function shortCircuitIfCurrent(currentVersion,latestVersion){if(!latestVersion)return!1;return normalizeVersion(currentVersion)===normalizeVersion(latestVersion)}async function fetchLatestVersion(channel){let candidates=[{cmd:"npm",args:["view",`@automagik/genie@${channel}`,"version"]},{cmd:"bunx",args:["npm","view",`@automagik/genie@${channel}`,"version"]}];for(let{cmd,args}of candidates){let result2=await runCommandSilent(cmd,args,void 0,FETCH_LATEST_TIMEOUT_MS);if(!result2.success)continue;let trimmed=result2.output.trim();if(!trimmed)continue;let lines=trimmed.split(/\r?\n/).filter(Boolean),tokens2=lines[lines.length-1].split(/\s+/),candidate=tokens2[tokens2.length-1].replace(/^['"]|['"]$/g,"");if(/^\d+\.\d+/.test(candidate))return candidate}return null}async function promptConfirm(question){if(!process.stdin.isTTY||!process.stdout.isTTY)return!0;return process.stdout.write(`${question} [Y/n] `),new Promise((resolve5)=>{let onData=(chunk)=>{process.stdin.removeListener("data",onData),process.stdin.pause();let answer=chunk.toString("utf-8").trim().toLowerCase();if(answer===""||answer==="y"||answer==="yes")resolve5(!0);else resolve5(!1)};process.stdin.resume(),process.stdin.once("data",onData)})}function shouldAutoConfirm(opts){if(opts.yes)return!0;return isTruthyEnv(process.env.GENIE_UPDATE_YES)}var VERIFY_PROBE_ENDPOINT="pgserve status --json + ~/.genie/serve.pid";async function readServerHealth(){if(!(await runCommandSilent("pgserve",["status","--json"],void 0,3000)).success)return null;let pidPath=join34(GENIE_HOME2,"serve.pid"),rawPid=safeRead(pidPath,32);if(!rawPid)return null;let pid=Number.parseInt(rawPid.trim(),10);if(!Number.isFinite(pid)||pid<=0)return null;try{process.kill(pid,0)}catch{return null}return{version:VERSION}}async function pollHealth(readHealth,deadlineMs,intervalMs){let startedAt=Date.now();while(Date.now()-startedAt<deadlineMs){let body=null;try{body=await readHealth()}catch{}if(body!==null)return body;await new Promise((r)=>setTimeout(r,intervalMs))}return null}async function runVerifyProbe(opts){if(opts.skipReason)return decideVerify({cliVersion:opts.cliVersion,serverHealthBody:null,endpoint:VERIFY_PROBE_ENDPOINT,skipReason:opts.skipReason});let reader=opts.readHealth??readServerHealth,deadline=opts.deadlineMs??VERIFY_PROBE_DEADLINE_MS,interval=opts.intervalMs??VERIFY_PROBE_POLL_INTERVAL_MS,body=await pollHealth(reader,deadline,interval);return decideVerify({cliVersion:opts.cliVersion,serverHealthBody:body,endpoint:VERIFY_PROBE_ENDPOINT})}function formatVerifyBanner(result2){let lines=[];switch(result2.kind){case"ok":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${result2.cliVersion}`),lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} Server: v${result2.serverVersion} (healthy)`);break;case"health-unreachable":lines.push(`${colorize("\x1B[33m","\x1B[0m","!")} CLI: v${VERSION}`),lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Server: unreachable (probe: ${result2.endpoint})`);break;case"version-mismatch":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${result2.cliVersion}`),lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Server: v${result2.serverVersion??"unknown"} (mismatch)`);break;case"auth-invalid":lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Auth: invalid`);break;case"skipped":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${VERSION}`),lines.push(`${colorize("\x1B[2m","\x1B[0m",`\xB7 Server: v\u2026 (skipped: ${result2.reason})`)}`);break}return lines}function colorEnabled(){if(process.env.NO_COLOR)return!1;if(process.env.FORCE_COLOR&&process.env.FORCE_COLOR!=="0")return!0;return Boolean(process.stdout.isTTY)}function colorize(open4,close,text){return colorEnabled()?`${open4}${text}${close}`:text}function log(message){console.log(`${colorize("\x1B[32m","\x1B[0m","\u25B8")} ${message}`)}function success(message){console.log(`${colorize("\x1B[32m","\x1B[0m","\u2714")} ${message}`)}function error(message){console.log(`${colorize("\x1B[31m","\x1B[0m","\u2716")} ${message}`)}function formatDuration2(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function isTruthyEnv(value){return value!==void 0&&TRUTHY.has(value.trim().toLowerCase())}async function withTemporaryEnv(key,value,fn){let previous=process.env[key];process.env[key]=value;try{return await fn()}finally{if(previous===void 0)delete process.env[key];else process.env[key]=previous}}function safeExec(command,timeoutMs=1500){try{return execSync3(command,{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:timeoutMs}).trim()}catch(err){let stdout=err.stdout;if(typeof stdout==="string"&&stdout.trim())return stdout.trim();return""}}function safeRead(path3,maxChars=4000){try{let value=readFileSync20(path3,"utf-8");if(value.length<=maxChars)return value;return value.slice(value.length-maxChars)}catch{return null}}function tailLines(path3,maxBytes=64000,maxLines=200){let fd=null;try{let stat4=statSync7(path3),bytesToRead=Math.min(stat4.size,maxBytes),buffer2=Buffer.alloc(bytesToRead);return fd=openSync3(path3,"r"),readSync2(fd,buffer2,0,bytesToRead,Math.max(0,stat4.size-bytesToRead)),buffer2.toString("utf-8").split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).slice(-maxLines)}catch{return[]}finally{if(fd!==null)try{closeSync3(fd)}catch{}}}function summarizeJsonlSignals(path3){let signals2=new Map;for(let line of tailLines(path3))try{let event=JSON.parse(line),level=typeof event.level==="string"?event.level:"unknown";if(level!=="error"&&level!=="warn")continue;let name=typeof event.event==="string"?event.event:"unknown",key=`${level}:${name}`,existing=signals2.get(key)??{level,event:name,count:0};if(existing.count++,typeof event.timestamp==="string")existing.lastTimestamp=event.timestamp;if(typeof event.error==="string")existing.lastError=event.error;signals2.set(key,existing)}catch{}return[...signals2.values()].sort((a,b2)=>b2.count-a.count).slice(0,10)}async function collectUpdateDiagnostics(ctx,maintenance,extras){let logsDir=join34(GENIE_HOME2,"logs");mkdirSync15(logsDir,{recursive:!0});let generatedAt=new Date().toISOString(),safeStamp=generatedAt.replace(/[:.]/g,"-"),path3=join34(logsDir,`update-diagnostics-${safeStamp}.json`),schedulerLog=join34(logsDir,"scheduler.log"),tuiCrashLog=join34(logsDir,"tui-crash.log"),signals2=summarizeJsonlSignals(schedulerLog),diagnostics={schemaVersion:UPDATE_DIAGNOSTIC_SCHEMA_VERSION,cli:"genie",generatedAt,verify:extras.verify,cleanups:extras.cleanups,update:ctx,runtime:{platform:process.platform,arch:process.arch,cwd:process.cwd(),node:process.version,bun:(await runCommandSilent("bun",["--version"])).output.trim()||null,npm:(await runCommandSilent("npm",["--version"])).output.trim()||null,genie:{which:(await runCommandSilent("which",["genie"])).output.trim()||null,tuiDisabled:isTruthyEnv(process.env.GENIE_TUI_DISABLE),updateSkipMaintenance:isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)}},paths:{genieHome:GENIE_HOME2,logsDir,servePid:safeRead(join34(GENIE_HOME2,"serve.pid"),200),pgservePort:safeRead(join34(GENIE_HOME2,"pgserve.port"),200),schedulerLog,tuiCrashLog},processSnapshot:{genie:safeExec("ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r | rg -i 'dist/genie.js|/src/genie.ts|pgserve|postgres -D .*\\.genie/data/pgserve|tmux -L genie-tui|bun' || true",2000)||null,tuiTmux:safeExec("tmux -L genie-tui ls 2>/dev/null || true",1000)||null},maintenance:{...maintenance,pgAutostartDisabled:!0,legend:{"[ok]":"healthy","[fix]":"fixed during maintenance","[--]":"skipped/non-blocking","[!!]":"operator action needed; update still completed"}},recentLogSignals:{scheduler:signals2,schedulerTail:tailLines(schedulerLog,32000,80),tuiCrashTail:tailLines(tuiCrashLog,32000,80)}};return writeFileSync13(path3,`${JSON.stringify(diagnostics,null,2)}
|
|
3989
|
-
`),{path:path3,signals:signals2}}async function runCommand(command,args,cwd){return new Promise((resolve5)=>{let output=[],child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}});child.stdout?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stdout.write(str2)}),child.stderr?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stderr.write(str2)}),child.on("close",(code)=>{resolve5({success:code===0,output:output.join("")})}),child.on("error",(err)=>{error(err.message),resolve5({success:!1,output:err.message})})})}async function getGitInfo(cwd){try{let branchResult=await runCommandSilent("git",["rev-parse","--abbrev-ref","HEAD"],cwd),commitResult=await runCommandSilent("git",["rev-parse","--short","HEAD"],cwd),dateResult=await runCommandSilent("git",["log","-1","--format=%ci"],cwd);if(branchResult.success&&commitResult.success&&dateResult.success)return{branch:branchResult.output.trim(),commit:commitResult.output.trim(),commitDate:dateResult.output.trim().split(" ")[0]}}catch{}return null}async function runCommandSilent(command,args,cwd,timeoutMs=4000){return new Promise((resolve5)=>{let output=[],settled=!1,child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"]}),timer2=setTimeout(()=>{if(settled)return;settled=!0,child.kill("SIGTERM"),resolve5({success:!1,output:`Timed out after ${timeoutMs}ms`})},timeoutMs);child.stdout?.on("data",(data)=>{output.push(data.toString())}),child.stderr?.on("data",(data)=>{output.push(data.toString())}),child.on("close",(code)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve5({success:code===0,output:output.join("")})}),child.on("error",(err)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve5({success:!1,output:err.message})})})}function detectFromBinaryPath(path3){let resolved=path3;try{resolved=__require("fs").realpathSync(path3)}catch{}if(resolved.includes(".bun"))return"bun";if(resolved.includes("node_modules"))return"npm";if(path3===join34(LOCAL_BIN2,"genie")||resolved.startsWith(GENIE_BIN))return"source";return null}async function detectInstallationType(){let whichResult=await runCommandSilent("which",["genie"]);if(whichResult.success){let detected=detectFromBinaryPath(whichResult.output.trim());if(detected)return detected}if(genieConfigExists())try{let config=await loadGenieConfig();if(config.installMethod)return config.installMethod}catch{}if(existsSync29(join34(GENIE_SRC,".git")))return"source";return(await runCommandSilent("which",["bun"])).success?"bun":"npm"}async function updateViaBun(channel){try{__require("fs").unlinkSync(join34(homedir28(),".bun","install","global","bun.lock"))}catch{}if(log(`Updating via bun (channel: ${channel})...`),!(await runCommand("bun",["add","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via bun"),!1;return console.log(),success(`Genie CLI updated via bun (${channel})!`),!0}async function updateViaNpm(channel){if(log(`Updating via npm (channel: ${channel})...`),!(await runCommand("npm",["install","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via npm"),!1;return console.log(),success(`Genie CLI updated via npm (${channel})!`),!0}async function detectGlobalInstalls(){let found=new Set,[npmResult,bunResult]=await Promise.all([runCommandSilent("npm",["list","-g","@automagik/genie"]),runCommandSilent("bun",["pm","ls","-g"])]);if(npmResult.success&&!npmResult.output.includes("(empty)"))found.add("npm");if(bunResult.success&&bunResult.output.includes("@automagik/genie"))found.add("bun");return found}async function updateSource(){if(!existsSync29(GENIE_SRC))error(`Source install path not found: ${GENIE_SRC}`),console.error(" Detection picked the source-install path, but the directory does not exist."),console.error(" This usually means a stale install hint (config or ~/.genie/src/.git) is"),console.error(" pointing somewhere genuine. Either:"),console.error(` 1. Re-clone the source: git clone https://github.com/automagik-dev/genie ${GENIE_SRC}`),console.error(" 2. Update via package manager instead: genie update --next --via bun"),console.error(" 3. Inspect detection: genie doctor --update-detection"),process.exit(1);if(!existsSync29(join34(GENIE_SRC,".git")))error(`Source install path is not a git checkout: ${GENIE_SRC}`),console.error(` ${GENIE_SRC} exists but has no .git/. Cannot run \`git fetch\` from it.`),console.error(` Either delete ${GENIE_SRC} and re-clone, or update via package manager:`),console.error(" genie update --next --via bun"),process.exit(1);let beforeInfo=await getGitInfo(GENIE_SRC);if(beforeInfo)console.log(`Current: \x1B[2m${beforeInfo.branch}@${beforeInfo.commit} (${beforeInfo.commitDate})\x1B[0m`),console.log();if(log("Fetching latest changes..."),!(await runCommand("git",["fetch","origin"],GENIE_SRC)).success)error("Failed to fetch from origin"),process.exit(1);if(log("Resetting to origin/main..."),!(await runCommand("git",["reset","--hard","origin/main"],GENIE_SRC)).success)error("Failed to reset to origin/main"),process.exit(1);console.log();let afterInfo=await getGitInfo(GENIE_SRC);if(beforeInfo&&afterInfo&&beforeInfo.commit===afterInfo.commit){success("Already up to date!"),console.log();return}if(log("Installing dependencies..."),!(await runCommand("bun",["install"],GENIE_SRC)).success)error("Failed to install dependencies"),process.exit(1);if(console.log(),log("Building..."),!(await runCommand("bun",["run","build"],GENIE_SRC)).success)error("Failed to build"),process.exit(1);console.log(),log("Installing binaries...");try{await mkdir5(GENIE_BIN,{recursive:!0}),await mkdir5(LOCAL_BIN2,{recursive:!0});let binaries=["genie.js","term.js"],names=["genie","term"];for(let i2=0;i2<binaries.length;i2++){let src=join34(GENIE_SRC,"dist",binaries[i2]),binDest=join34(GENIE_BIN,binaries[i2]),linkDest=join34(LOCAL_BIN2,names[i2]);await copyFile(src,binDest),await chmod(binDest,493),await symlinkOrCopy(binDest,linkDest)}for(let legacy of["claudio.js","claudio"]){let legacyBin=join34(GENIE_BIN,legacy),legacyLink=join34(LOCAL_BIN2,legacy);try{await unlink2(legacyBin)}catch{}try{await unlink2(legacyLink)}catch{}}success("Binaries installed")}catch(err){error(`Failed to install binaries: ${err}`),process.exit(1)}if(console.log(),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),success("Genie CLI updated successfully!"),console.log(),afterInfo)console.log(`Version: \x1B[36m${afterInfo.branch}@${afterInfo.commit}\x1B[0m (${afterInfo.commitDate})`),console.log()}async function symlinkOrCopy(src,dest){let{symlink:symlink2,unlink:unlink3}=await import("fs/promises");try{if(existsSync29(dest))await unlink3(dest);await symlink2(src,dest)}catch{await copyFile(src,dest)}}var FRAMEWORK_MARKER_FILES=new Set([".orphaned_at"]);function copyDirSync(src,dest){mkdirSync15(dest,{recursive:!0});for(let entry2 of readdirSync9(src,{withFileTypes:!0})){if(FRAMEWORK_MARKER_FILES.has(entry2.name))continue;let srcPath=join34(src,entry2.name),destPath=join34(dest,entry2.name);if(entry2.isDirectory())copyDirSync(srcPath,destPath);else copyFileSync3(srcPath,destPath)}}async function resolveGlobalPkgDir(installType){if(installType==="bun"){let bunPath=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync29(bunPath))return bunPath}if(installType==="npm"){let npmRootResult=await runCommandSilent("npm",["root","-g"]);if(npmRootResult.success){let npmPath=join34(npmRootResult.output.trim(),"@automagik","genie");if(existsSync29(npmPath))return npmPath}}let bunFallback=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync29(bunFallback))return bunFallback;let npmRootFallback=await runCommandSilent("npm",["root","-g"]);if(npmRootFallback.success){let npmPath=join34(npmRootFallback.output.trim(),"@automagik","genie");if(existsSync29(npmPath))return npmPath}return null}function updatePluginRegistry(claudePlugins,cacheDir,version){let registryPath=join34(claudePlugins,"installed_plugins.json");try{if(!existsSync29(registryPath))return;let registry=JSON.parse(readFileSync20(registryPath,"utf-8")),entries=registry.plugins?.["genie@automagik"];if(!Array.isArray(entries))return;for(let entry2 of entries)if(entry2.scope==="user")entry2.installPath=cacheDir,entry2.version=version,entry2.lastUpdated=new Date().toISOString();writeFileSync13(registryPath,JSON.stringify(registry,null,2))}catch(err){log(`Registry update failed (non-fatal): ${err}`)}}function syncTmuxConf(tmuxScriptsSrc){mkdirSync15(GENIE_HOME2,{recursive:!0});let tmuxConfSrc=join34(tmuxScriptsSrc,"genie.tmux.conf"),tmuxConfDest=join34(GENIE_HOME2,"tmux.conf");if(existsSync29(tmuxConfSrc))try{copyFileSync3(tmuxConfSrc,tmuxConfDest),success(`Installed tmux config to ${tmuxConfDest}`);try{let{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));execSync3(`${tmuxBin2()} -L genie source-file '${tmuxConfDest}'`,{stdio:"ignore"}),success("Reloaded genie tmux server configuration")}catch{}}catch{}let tuiConfSrc=join34(tmuxScriptsSrc,"tui-tmux.conf"),tuiConfDest=join34(GENIE_HOME2,"tui-tmux.conf");if(existsSync29(tuiConfSrc))try{copyFileSync3(tuiConfSrc,tuiConfDest),success(`Installed TUI tmux config to ${tuiConfDest}`)}catch{}let themeSrc=join34(tmuxScriptsSrc,".generated.theme.conf"),themeDest=join34(GENIE_HOME2,".generated.theme.conf");if(existsSync29(themeSrc))try{copyFileSync3(themeSrc,themeDest),success(`Installed tmux theme to ${themeDest}`)}catch{}let osc52Src=join34(tmuxScriptsSrc,"osc52-copy.sh"),osc52Dest=join34(GENIE_HOME2,"scripts","osc52-copy.sh");if(existsSync29(osc52Src))try{copyFileSync3(osc52Src,osc52Dest),chmodSync2(osc52Dest,493),success(`Installed OSC 52 clipboard helper to ${osc52Dest}`)}catch{}}function syncTmuxScripts(globalPkgDir){let tmuxScriptsSrc=join34(globalPkgDir,"scripts","tmux");if(!existsSync29(tmuxScriptsSrc))return;let scriptsDir=join34(GENIE_HOME2,"scripts");mkdirSync15(scriptsDir,{recursive:!0});let scriptCount=0;for(let entry2 of readdirSync9(tmuxScriptsSrc))if(entry2.endsWith(".sh")||entry2==="genie.tmux.conf"||entry2==="tui-tmux.conf"||entry2===".generated.theme.conf"){let src=join34(tmuxScriptsSrc,entry2),dest=join34(scriptsDir,entry2);copyFileSync3(src,dest);try{chmodSync2(dest,entry2.endsWith(".sh")?493:420)}catch{}scriptCount++}if(scriptCount>0)success(`Refreshed ${scriptCount} tmux scripts at ${scriptsDir}`);syncTmuxConf(tmuxScriptsSrc)}function syncMarketplaceVersion(claudePlugins,version){let marketplacePath=join34(claudePlugins,"marketplaces","automagik",".claude-plugin","marketplace.json");try{if(!existsSync29(marketplacePath))return;let data=JSON.parse(readFileSync20(marketplacePath,"utf-8"));if(Array.isArray(data.plugins)){for(let plugin of data.plugins)if(plugin.name==="genie")plugin.version=version}writeFileSync13(marketplacePath,JSON.stringify(data,null,2)),success(`Updated marketplace.json to v${version}`)}catch(err){log(`Marketplace version update failed (non-fatal): ${err}`)}}function syncPluginPackageVersion(claudePlugins,version){let pkgPath=join34(claudePlugins,"marketplaces","automagik","plugins","genie","package.json");try{if(!existsSync29(pkgPath))return;let data=JSON.parse(readFileSync20(pkgPath,"utf-8"));data.version=version,writeFileSync13(pkgPath,JSON.stringify(data,null,2)),success(`Updated plugin package.json to v${version}`)}catch(err){log(`Plugin package.json update failed (non-fatal): ${err}`)}}function syncSkillsSymlink(claudePlugins,version){let skillsLink=join34(claudePlugins,"marketplaces","automagik","plugins","genie","skills"),cacheSkills=join34("..","..","..","..","cache","automagik","genie",version,"skills");try{let{symlinkSync,unlinkSync:unlinkSync9,lstatSync:lstatSync2}=__require("fs");try{lstatSync2(skillsLink),unlinkSync9(skillsLink)}catch{}symlinkSync(cacheSkills,skillsLink),success(`Skills symlink \u2192 cache/${version}/skills`)}catch(err){log(`Skills symlink update failed (non-fatal): ${err}`)}}async function syncPlugin(installType){log("Syncing Claude Code plugin...");let globalPkgDir=await resolveGlobalPkgDir(installType);if(!globalPkgDir)return log("Could not find installed package \u2014 skipping plugin sync"),{skippedReason:"installed package not found"};let pluginSrc=join34(globalPkgDir,"plugins","genie");if(!existsSync29(pluginSrc))return log("Plugin source not found in package \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"plugin source not found in package"};let version;try{version=JSON.parse(readFileSync20(join34(globalPkgDir,"package.json"),"utf-8")).version}catch{return log("Could not read package version \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"could not read package version"}}let claudePlugins=join34(homedir28(),".claude","plugins"),cacheDir=join34(claudePlugins,"cache","automagik","genie",version);try{if(existsSync29(cacheDir))rmSync3(cacheDir,{recursive:!0,force:!0});copyDirSync(pluginSrc,cacheDir);let skillsSrc=join34(globalPkgDir,"skills");if(existsSync29(skillsSrc)&&!existsSync29(join34(cacheDir,"skills")))copyDirSync(skillsSrc,join34(cacheDir,"skills"))}catch(err){return error(`Failed to copy plugin: ${err}`),{version,globalPkgDir,cacheDir,skippedReason:`failed to copy plugin: ${err}`}}return updatePluginRegistry(claudePlugins,cacheDir,version),syncMarketplaceVersion(claudePlugins,version),syncPluginPackageVersion(claudePlugins,version),syncSkillsSymlink(claudePlugins,version),syncTmuxScripts(globalPkgDir),success(`Plugin synced to v${version}`),{version,globalPkgDir,cacheDir}}async function resolveChannel(options){if(options.next)return"next";if(options.stable)return"latest";if(genieConfigExists())try{let config=await loadGenieConfig();if(config.updateChannel)return config.updateChannel}catch{}return"latest"}async function persistChannel(channel){try{let config=await loadGenieConfig();config.updateChannel=channel,await saveGenieConfig(config)}catch{}}async function updateCommand(options={}){console.log(),console.log(`${colorize("\x1B[1m","\x1B[0m","\uD83E\uDDDE Genie CLI Update")}`),console.log(`${colorize("\x1B[2m","\x1B[0m","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`),console.log();let cleanupSkipList=buildCleanupSkipList(options),noRestart=options.restart===!1||isTruthyEnv(process.env.GENIE_UPDATE_NO_RESTART),noVerify=options.verify===!1||isTruthyEnv(process.env.GENIE_UPDATE_NO_VERIFY),channel=await resolveChannel(options);if(options.next||options.stable)await persistChannel(channel);let latestVersion=await fetchLatestVersion(channel);if(shortCircuitIfCurrent(VERSION,latestVersion)){success(`Already up to date (v${normalizeVersion(VERSION)}, channel ${channel})`),console.log();return}let installType=await detectInstallationType();if(log(`Detected installation: ${installType}`),log(`Channel: ${channel}${channel==="next"?" (dev builds)":" (stable)"}`),latestVersion)log(`Update available: ${normalizeVersion(VERSION)} \u2192 ${normalizeVersion(latestVersion)}`);else log(`Registry version unavailable (proceeding with reinstall of channel ${channel})`);if(console.log(),installType==="unknown")error("No Genie CLI installation found"),console.log(),console.log("Install method not configured. Please reinstall genie:"),console.log(`${colorize("\x1B[36m","\x1B[0m"," curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash")}`),console.log(),process.exit(1);if(!shouldAutoConfirm(options)){let proceedQuestion=latestVersion?`Update v${normalizeVersion(VERSION)} \u2192 v${normalizeVersion(latestVersion)}?`:`Reinstall channel "${channel}"?`;if(!await promptConfirm(proceedQuestion)){console.log(),log("Update declined."),console.log();return}}if(installType==="source"){await updateSource();return}let globalInstalls=await detectGlobalInstalls(),primaryMethod=installType;if(!(primaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))process.exit(1);let secondaryMethod=primaryMethod==="bun"?"npm":"bun";if(globalInstalls.has(secondaryMethod)){if(console.log(),log(`Also updating ${secondaryMethod}-global install...`),!(secondaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))error(`Secondary update via ${secondaryMethod} failed (non-blocking)`)}let plugin=await syncPlugin(installType),cleanupReport=await runLegacyCleanupSafe(cleanupSkipList);await runPostUpdateMaintenanceSafe({...options,noRestart,noVerify},{channel,installType,primaryMethod,globalInstalls:[...globalInstalls].sort(),plugin,latestVersion,cliVersion:VERSION},cleanupReport)}function buildCleanupSkipList(options){let skipList=parseSkipCleanupFlag(options.skipCleanup);if(options.sidecarCleanup===!1)skipList.add("nats-reply-sidecar"),log("--no-sidecar-cleanup (no-op for genie, retained for cross-CLI portability)");return skipList}async function runLegacyCleanupSafe(skipList){try{return await cleanupLegacyArtifacts(skipList)}catch(err){let msg=err instanceof Error?err.message:String(err);return log(`Legacy artifact cleanup failed (non-fatal): ${msg}`),{entries:[]}}}function printPostUpdateMaintenanceIntro(){console.log(),log("Running post-update maintenance..."),console.log(" Purpose: make first launch after update fast and collect upgrade health signals."),console.log(" Checks: runtime partitions, watchdog status, session backfill drift, zombie rows, team config orphans."),console.log(" PG policy: read-only; uses an already-running pgserve when available and will not auto-start it."),console.log(" Legend: [ok]=healthy, [fix]=fixed, [--]=skipped/non-blocking, [!!]=operator action needed.")}async function runMaintenanceWithCapturedLines(maintenanceLines){let{runPostUpdateMaintenance:runPostUpdateMaintenance2}=await Promise.resolve().then(() => (init_doctor(),exports_doctor));await withTemporaryEnv("GENIE_PG_NO_AUTOSTART","1",()=>runPostUpdateMaintenance2({log:(line)=>{maintenanceLines.push(line),console.log(line)}}))}function printDiagnosticsSummary(diagnostics){if(log("Post-update diagnostics captured."),console.log(` Report: ${diagnostics.path}`),console.log(" Include this file when opening a GitHub issue; it contains install metadata, step output,"),console.log(" local process state, and recent scheduler/TUI log signals."),diagnostics.signals.length===0)return;console.log(" Recent scheduler signals:");for(let signal of diagnostics.signals.slice(0,3)){let errorDetail=signal.lastError?` \u2014 ${signal.lastError}`:"";console.log(` ${signal.level}:${signal.event} \xD7${signal.count}${errorDetail}`)}}async function capturePostUpdateDiagnostics(diagnosticsCtx,maintenance,extras){if(!diagnosticsCtx)return;try{let diagnostics=await collectUpdateDiagnostics(diagnosticsCtx,maintenance,extras);printDiagnosticsSummary(diagnostics)}catch(err){let msg=err instanceof Error?err.message:String(err);log(`Post-update diagnostics capture failed (non-fatal): ${msg}`)}}function printVerifyBanner(result2){console.log();for(let line of formatVerifyBanner(result2))console.log(` ${line}`);console.log()}async function runPostUpdateMaintenanceSafe(options,diagnosticsCtx,cleanupReport){if(options.noRestart){log("--no-restart: skipping maintenance and verify probe.");let verify2=await runVerifyProbe({cliVersion:VERSION,skipReason:"no-restart"});printVerifyBanner(verify2),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome:"completed",durationMs:0,lines:[]},{verify:verify2,cleanups:cleanupReport});return}if(options.skipMaintenance||isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)){log("Skipping post-update maintenance (requested).");let verify2=await runVerifyProbe({cliVersion:VERSION,skipReason:"no-restart"});printVerifyBanner(verify2),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome:"completed",durationMs:0,lines:[]},{verify:verify2,cleanups:cleanupReport});return}let startedAt=Date.now(),maintenanceLines=[],outcome="completed",maintenanceError;try{printPostUpdateMaintenanceIntro(),await runMaintenanceWithCapturedLines(maintenanceLines),success(`Post-update maintenance complete (${formatDuration2(Date.now()-startedAt)}).`)}catch(err){outcome="failed",maintenanceError=err instanceof Error?err.message:String(err),error(`Post-update maintenance skipped: ${maintenanceError}`)}let verify=options.noVerify?await runVerifyProbe({cliVersion:VERSION,skipReason:"no-verify-flag"}):await runVerifyProbe({cliVersion:VERSION});if(printVerifyBanner(verify),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome,durationMs:Date.now()-startedAt,lines:maintenanceLines,error:maintenanceError},{verify,cleanups:cleanupReport}),verify.kind==="health-unreachable"&&!options.noVerify)process.exitCode=1}init_version();init_trust();init_trust();init_trust();import{execSync as execSync4}from"child_process";import{existsSync as existsSync31,mkdirSync as mkdirSync16,readFileSync as readFileSync22,renameSync as renameSync6,writeFileSync as writeFileSync14}from"fs";import{dirname as dirname13,isAbsolute,join as join36,resolve as resolve5}from"path";function resolveOriginUrl(repoRoot){try{return execSync4("git config --get remote.origin.url",{cwd:repoRoot,encoding:"utf-8"}).trim()||null}catch{return null}}function findRepoRoot(start){let current=start;while(current!=="/"){if(existsSync31(join36(current,".git")))return current;current=dirname13(current)}return null}function writeTrustFile(path3,file){let dir=dirname13(path3);if(!existsSync31(dir))mkdirSync16(dir,{recursive:!0});let sorted={version:1,entries:[...file.entries].sort((a,b2)=>a.path.localeCompare(b2.path))},serialized=`${JSON.stringify(sorted,null,2)}
|
|
3988
|
+
`)}return}let result2=await migrate({quiet:options.quiet,dryRun:options.dryRun});process.exit(result2.ok?0:1)}init_setup();init_shortcuts();import{existsSync as existsSync26}from"fs";import{homedir as homedir26}from"os";import{join as join32}from"path";async function shortcutsShowCommand(){displayShortcuts();let home=homedir26(),tmuxConf=join32(home,".tmux.conf"),zshrc=join32(home,".zshrc"),bashrc=join32(home,".bashrc");if(console.log("Installation status:"),isShortcutsInstalled(tmuxConf))console.log(" \x1B[32m\u2713\x1B[0m tmux.conf");else console.log(" \x1B[33m-\x1B[0m tmux.conf");let shellRc=existsSync26(zshrc)?zshrc:bashrc;if(isShortcutsInstalled(shellRc))console.log(` \x1B[32m\u2713\x1B[0m ${shellRc.replace(home,"~")}`);else console.log(` \x1B[33m-\x1B[0m ${shellRc.replace(home,"~")}`);console.log(),console.log("Run \x1B[36mgenie shortcuts install\x1B[0m to install shortcuts."),console.log("Run \x1B[36mgenie shortcuts uninstall\x1B[0m to remove shortcuts."),console.log()}async function shortcutsInstallCommand(){await installShortcuts()}async function shortcutsUninstallCommand(){await uninstallShortcuts()}init_esm14();init_claude_settings();init_genie_config2();import{existsSync as existsSync27,lstatSync,rmSync as rmSync2,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir27}from"os";import{join as join33}from"path";var ORCHESTRATION_RULES_PATH=join33(homedir27(),".claude","rules","genie-orchestration.md"),LOCAL_BIN=join33(homedir27(),".local","bin"),SYMLINKS=["genie","term"];function isGenieSymlink(path3){try{if(!existsSync27(path3))return!1;if(!lstatSync(path3).isSymbolicLink())return!1;return!0}catch{return!1}}function removeSymlinks(){let removed=[];for(let name of SYMLINKS){let symlinkPath=join33(LOCAL_BIN,name);if(isGenieSymlink(symlinkPath))try{unlinkSync8(symlinkPath),removed.push(name)}catch{}}return removed}function tryRemoveStep(label,successMsg,fn){console.log(`\x1B[2m${label}\x1B[0m`);try{fn(),console.log(` \x1B[32m+\x1B[0m ${successMsg}`)}catch(error){let message=error instanceof Error?error.message:String(error);console.log(` \x1B[33m!\x1B[0m ${label.replace("...","")} failed: ${message}`)}}function performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir){if(hasHookScript)tryRemoveStep("Removing hook script...","Hook script removed",()=>removeHookScript());if(existingSymlinks.length>0){console.log("\x1B[2mRemoving symlinks...\x1B[0m");let removed=removeSymlinks();if(removed.length>0)console.log(` \x1B[32m+\x1B[0m Removed: ${removed.join(", ")}`)}if(existsSync27(ORCHESTRATION_RULES_PATH))tryRemoveStep("Removing orchestration rules...","Orchestration rules removed (~/.claude/rules/genie-orchestration.md)",()=>unlinkSync8(ORCHESTRATION_RULES_PATH));if(hasGenieDir)tryRemoveStep("Removing genie directory...","Directory removed",()=>rmSync2(genieDir,{recursive:!0,force:!0}))}async function uninstallCommand(){console.log(),console.log("\x1B[1m\x1B[33m Uninstall Genie CLI\x1B[0m"),console.log();let genieDir=getGenieDir(),hasGenieDir=existsSync27(genieDir),hasHookScript=hookScriptExists(),hasOrchestrationRules=existsSync27(ORCHESTRATION_RULES_PATH),existingSymlinks=SYMLINKS.filter((name)=>isGenieSymlink(join33(LOCAL_BIN,name)));if(console.log("\x1B[2mThis will remove:\x1B[0m"),hasHookScript)console.log(" \x1B[31m-\x1B[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)");if(hasOrchestrationRules)console.log(" \x1B[31m-\x1B[0m Orchestration rules (~/.claude/rules/genie-orchestration.md)");if(hasGenieDir)console.log(` \x1B[31m-\x1B[0m Genie directory (${contractPath(genieDir)})`);if(existingSymlinks.length>0)console.log(` \x1B[31m-\x1B[0m Symlinks from ~/.local/bin: ${existingSymlinks.join(", ")}`);if(console.log(),!hasGenieDir&&!hasHookScript&&!hasOrchestrationRules&&existingSymlinks.length===0){console.log("\x1B[33mNothing to uninstall.\x1B[0m"),console.log();return}if(!await esm_default4({message:"Are you sure you want to uninstall Genie CLI?",default:!1})){console.log(),console.log("\x1B[2mUninstall cancelled.\x1B[0m"),console.log();return}console.log(),performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir),console.log(),console.log("\x1B[32m+\x1B[0m Genie CLI uninstalled."),console.log(),console.log("\x1B[2mNote: If you installed via npm/bun, also run:\x1B[0m"),console.log(" \x1B[36mbun remove -g @automagik/genie\x1B[0m"),console.log(" \x1B[2mor\x1B[0m"),console.log(" \x1B[36mnpm uninstall -g @automagik/genie\x1B[0m"),console.log()}init_genie_config2();init_version();import{execSync as execSync3,spawn as spawn3}from"child_process";import{chmodSync as chmodSync2,closeSync as closeSync3,copyFileSync as copyFileSync3,existsSync as existsSync29,mkdirSync as mkdirSync15,openSync as openSync3,readFileSync as readFileSync20,readSync as readSync2,readdirSync as readdirSync9,readlinkSync,rmSync as rmSync3,statSync as statSync7,writeFileSync as writeFileSync13}from"fs";import{chmod,copyFile,mkdir as mkdir5,unlink as unlink2}from"fs/promises";import{homedir as homedir28}from"os";import{join as join34}from"path";var REGISTRY=[];async function cleanupLegacyArtifacts(skipList,registry=REGISTRY){let entries=[];for(let artifact of registry){if(skipList.has(artifact.name)){entries.push({name:artifact.name,outcome:"skipped",removed:[],warnings:[]});continue}if(!await artifact.detect()){entries.push({name:artifact.name,outcome:"absent",removed:[],warnings:[]});continue}let result2=await artifact.cleanup();entries.push({name:artifact.name,outcome:"cleaned",removed:result2.removed,warnings:result2.warnings})}return{entries}}function parseSkipCleanupFlag(value){let skip=new Set;if(!value)return skip;for(let part of value.split(",")){let name=part.trim();if(name)skip.add(name)}return skip}var GENIE_HOME2=process.env.GENIE_HOME||join34(homedir28(),".genie"),GENIE_SRC=join34(GENIE_HOME2,"src"),GENIE_BIN=join34(GENIE_HOME2,"bin"),LOCAL_BIN2=join34(homedir28(),".local","bin"),TRUTHY=new Set(["1","true","yes","on"]),UPDATE_DIAGNOSTIC_SCHEMA_VERSION=2,VERIFY_PROBE_DEADLINE_MS=15000,VERIFY_PROBE_POLL_INTERVAL_MS=500,FETCH_LATEST_TIMEOUT_MS=5000;function normalizeVersion(value){let trimmed=value.trim(),plusIdx=trimmed.indexOf("+");return plusIdx===-1?trimmed:trimmed.slice(0,plusIdx)}function decideVerify(args){if(args.skipReason)return{kind:"skipped",reason:args.skipReason};if(args.serverHealthBody===null)return{kind:"health-unreachable",endpoint:args.endpoint};let cliVersion=normalizeVersion(args.cliVersion),rawServerVersion=args.serverHealthBody.version??null,serverVersion=rawServerVersion===null?null:normalizeVersion(rawServerVersion);if(args.serverHealthBody.daemonInodeStale===!0)return{kind:"daemon-stale-inode",cliVersion,diskVersion:serverVersion,pid:args.serverHealthBody.daemonPid??0,cwd:args.serverHealthBody.daemonCwd??""};if(serverVersion===null||serverVersion!==cliVersion)return{kind:"version-mismatch",cliVersion,serverVersion};return{kind:"ok",cliVersion,serverVersion}}function shortCircuitIfCurrent(currentVersion,latestVersion){if(!latestVersion)return!1;return normalizeVersion(currentVersion)===normalizeVersion(latestVersion)}async function fetchLatestVersion(channel){let candidates=[{cmd:"npm",args:["view",`@automagik/genie@${channel}`,"version"]},{cmd:"bunx",args:["npm","view",`@automagik/genie@${channel}`,"version"]}];for(let{cmd,args}of candidates){let result2=await runCommandSilent(cmd,args,void 0,FETCH_LATEST_TIMEOUT_MS);if(!result2.success)continue;let trimmed=result2.output.trim();if(!trimmed)continue;let lines=trimmed.split(/\r?\n/).filter(Boolean),tokens2=lines[lines.length-1].split(/\s+/),candidate=tokens2[tokens2.length-1].replace(/^['"]|['"]$/g,"");if(/^\d+\.\d+/.test(candidate))return candidate}return null}async function promptConfirm(question){if(!process.stdin.isTTY||!process.stdout.isTTY)return!0;return process.stdout.write(`${question} [Y/n] `),new Promise((resolve5)=>{let onData=(chunk)=>{process.stdin.removeListener("data",onData),process.stdin.pause();let answer=chunk.toString("utf-8").trim().toLowerCase();if(answer===""||answer==="y"||answer==="yes")resolve5(!0);else resolve5(!1)};process.stdin.resume(),process.stdin.once("data",onData)})}function shouldAutoConfirm(opts){if(opts.yes)return!0;return isTruthyEnv(process.env.GENIE_UPDATE_YES)}var VERIFY_PROBE_ENDPOINT="pgserve status --json + ~/.genie/serve.pid";function readDaemonCwd(pid){if(process.platform!=="linux")return null;try{let cwd=readlinkSync(`/proc/${pid}/cwd`);return{cwd,staleInode:cwd.endsWith(" (deleted)")}}catch{return null}}function readInstalledPackageVersion(){let candidates=[join34(homedir28(),".bun","install","global","node_modules","@automagik","genie","package.json")],npmPrefix=safeExec("npm prefix -g",1500);if(npmPrefix)candidates.push(join34(npmPrefix,"lib","node_modules","@automagik","genie","package.json"));for(let path3 of candidates)try{let pkg=JSON.parse(readFileSync20(path3,"utf-8"));if(typeof pkg.version==="string"&&/^\d+\.\d+/.test(pkg.version))return pkg.version}catch{}return null}async function readServerHealth(){if(!(await runCommandSilent("pgserve",["status","--json"],void 0,3000)).success)return null;let pidPath=join34(GENIE_HOME2,"serve.pid"),rawPid=safeRead(pidPath,32);if(!rawPid)return null;let pid=Number.parseInt(rawPid.trim(),10);if(!Number.isFinite(pid)||pid<=0)return null;try{process.kill(pid,0)}catch{return null}let cwdInfo=readDaemonCwd(pid);return{version:readInstalledPackageVersion()??VERSION,daemonInodeStale:cwdInfo?.staleInode??!1,daemonPid:pid,daemonCwd:cwdInfo?.cwd}}async function pollHealth(readHealth,deadlineMs,intervalMs){let startedAt=Date.now();while(Date.now()-startedAt<deadlineMs){let body=null;try{body=await readHealth()}catch{}if(body!==null)return body;await new Promise((r)=>setTimeout(r,intervalMs))}return null}async function runVerifyProbe(opts){if(opts.skipReason)return decideVerify({cliVersion:opts.cliVersion,serverHealthBody:null,endpoint:VERIFY_PROBE_ENDPOINT,skipReason:opts.skipReason});let reader=opts.readHealth??readServerHealth,deadline=opts.deadlineMs??VERIFY_PROBE_DEADLINE_MS,interval=opts.intervalMs??VERIFY_PROBE_POLL_INTERVAL_MS,body=await pollHealth(reader,deadline,interval);return decideVerify({cliVersion:opts.cliVersion,serverHealthBody:body,endpoint:VERIFY_PROBE_ENDPOINT})}async function pm2GenieServe(){let result2=await runCommandSilent("pm2",["jlist"],void 0,3000);if(!result2.success)return null;try{let entry2=JSON.parse(result2.output).find((p)=>p.name==="genie-serve");if(!entry2||typeof entry2.pid!=="number"||entry2.pid<=0)return null;if(entry2.pm2_env?.status!=="online")return null;return{pid:entry2.pid,restartCount:entry2.pm2_env?.restart_time??0}}catch{return null}}async function restartServeIfStale(){let before=await pm2GenieServe();if(!before)return null;let cwdInfo=readDaemonCwd(before.pid);if(!cwdInfo||!cwdInfo.staleInode)return null;if(log(`Restarting pm2 genie-serve (stale inode detected: ${cwdInfo.cwd})`),!(await runCommandSilent("pm2",["restart","genie-serve","--update-env"],void 0,1e4)).success)return error("pm2 restart genie-serve failed; daemon will keep serving pre-update bytes until manually restarted"),null;let deadline=Date.now()+1e4;while(Date.now()<deadline){let after=await pm2GenieServe();if(after&&(after.pid!==before.pid||after.restartCount>before.restartCount))return success(`pm2 genie-serve restarted (pid ${before.pid} \u2192 ${after.pid})`),{oldPid:before.pid,newPid:after.pid};await new Promise((r)=>setTimeout(r,500))}return error("pm2 genie-serve restart did not produce a new pid within 10s \u2014 verify probe will surface the stale state"),null}function formatVerifyBanner(result2){let lines=[];switch(result2.kind){case"ok":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${result2.cliVersion}`),lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} Server: v${result2.serverVersion} (healthy)`);break;case"health-unreachable":lines.push(`${colorize("\x1B[33m","\x1B[0m","!")} CLI: v${VERSION}`),lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Server: unreachable (probe: ${result2.endpoint})`);break;case"version-mismatch":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${result2.cliVersion}`),lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Server: v${result2.serverVersion??"unknown"} (mismatch)`);break;case"daemon-stale-inode":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${result2.cliVersion}`),lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Server: stale inode (pid ${result2.pid}, on-disk v${result2.diskVersion??"unknown"})`),lines.push(`${colorize("\x1B[2m","\x1B[0m",` cwd: ${result2.cwd}`)}`),lines.push(`${colorize("\x1B[2m","\x1B[0m"," fix: pm2 restart genie-serve --update-env")}`);break;case"auth-invalid":lines.push(`${colorize("\x1B[31m","\x1B[0m","\u2716")} Auth: invalid`);break;case"skipped":lines.push(`${colorize("\x1B[32m","\x1B[0m","\u2714")} CLI: v${VERSION}`),lines.push(`${colorize("\x1B[2m","\x1B[0m",`\xB7 Server: v\u2026 (skipped: ${result2.reason})`)}`);break}return lines}function colorEnabled(){if(process.env.NO_COLOR)return!1;if(process.env.FORCE_COLOR&&process.env.FORCE_COLOR!=="0")return!0;return Boolean(process.stdout.isTTY)}function colorize(open4,close,text){return colorEnabled()?`${open4}${text}${close}`:text}function log(message){console.log(`${colorize("\x1B[32m","\x1B[0m","\u25B8")} ${message}`)}function success(message){console.log(`${colorize("\x1B[32m","\x1B[0m","\u2714")} ${message}`)}function error(message){console.log(`${colorize("\x1B[31m","\x1B[0m","\u2716")} ${message}`)}function formatDuration2(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function isTruthyEnv(value){return value!==void 0&&TRUTHY.has(value.trim().toLowerCase())}async function withTemporaryEnv(key,value,fn){let previous=process.env[key];process.env[key]=value;try{return await fn()}finally{if(previous===void 0)delete process.env[key];else process.env[key]=previous}}function safeExec(command,timeoutMs=1500){try{return execSync3(command,{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:timeoutMs}).trim()}catch(err){let stdout=err.stdout;if(typeof stdout==="string"&&stdout.trim())return stdout.trim();return""}}function safeRead(path3,maxChars=4000){try{let value=readFileSync20(path3,"utf-8");if(value.length<=maxChars)return value;return value.slice(value.length-maxChars)}catch{return null}}function tailLines(path3,maxBytes=64000,maxLines=200){let fd=null;try{let stat4=statSync7(path3),bytesToRead=Math.min(stat4.size,maxBytes),buffer2=Buffer.alloc(bytesToRead);return fd=openSync3(path3,"r"),readSync2(fd,buffer2,0,bytesToRead,Math.max(0,stat4.size-bytesToRead)),buffer2.toString("utf-8").split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).slice(-maxLines)}catch{return[]}finally{if(fd!==null)try{closeSync3(fd)}catch{}}}function summarizeJsonlSignals(path3){let signals2=new Map;for(let line of tailLines(path3))try{let event=JSON.parse(line),level=typeof event.level==="string"?event.level:"unknown";if(level!=="error"&&level!=="warn")continue;let name=typeof event.event==="string"?event.event:"unknown",key=`${level}:${name}`,existing=signals2.get(key)??{level,event:name,count:0};if(existing.count++,typeof event.timestamp==="string")existing.lastTimestamp=event.timestamp;if(typeof event.error==="string")existing.lastError=event.error;signals2.set(key,existing)}catch{}return[...signals2.values()].sort((a,b2)=>b2.count-a.count).slice(0,10)}async function collectUpdateDiagnostics(ctx,maintenance,extras){let logsDir=join34(GENIE_HOME2,"logs");mkdirSync15(logsDir,{recursive:!0});let generatedAt=new Date().toISOString(),safeStamp=generatedAt.replace(/[:.]/g,"-"),path3=join34(logsDir,`update-diagnostics-${safeStamp}.json`),schedulerLog=join34(logsDir,"scheduler.log"),tuiCrashLog=join34(logsDir,"tui-crash.log"),signals2=summarizeJsonlSignals(schedulerLog),diagnostics={schemaVersion:UPDATE_DIAGNOSTIC_SCHEMA_VERSION,cli:"genie",generatedAt,verify:extras.verify,cleanups:extras.cleanups,update:ctx,runtime:{platform:process.platform,arch:process.arch,cwd:process.cwd(),node:process.version,bun:(await runCommandSilent("bun",["--version"])).output.trim()||null,npm:(await runCommandSilent("npm",["--version"])).output.trim()||null,genie:{which:(await runCommandSilent("which",["genie"])).output.trim()||null,tuiDisabled:isTruthyEnv(process.env.GENIE_TUI_DISABLE),updateSkipMaintenance:isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)}},paths:{genieHome:GENIE_HOME2,logsDir,servePid:safeRead(join34(GENIE_HOME2,"serve.pid"),200),pgservePort:safeRead(join34(GENIE_HOME2,"pgserve.port"),200),schedulerLog,tuiCrashLog},processSnapshot:{genie:safeExec("ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r | rg -i 'dist/genie.js|/src/genie.ts|pgserve|postgres -D .*\\.genie/data/pgserve|tmux -L genie-tui|bun' || true",2000)||null,tuiTmux:safeExec("tmux -L genie-tui ls 2>/dev/null || true",1000)||null},maintenance:{...maintenance,pgAutostartDisabled:!0,legend:{"[ok]":"healthy","[fix]":"fixed during maintenance","[--]":"skipped/non-blocking","[!!]":"operator action needed; update still completed"}},recentLogSignals:{scheduler:signals2,schedulerTail:tailLines(schedulerLog,32000,80),tuiCrashTail:tailLines(tuiCrashLog,32000,80)}};return writeFileSync13(path3,`${JSON.stringify(diagnostics,null,2)}
|
|
3989
|
+
`),{path:path3,signals:signals2}}async function runCommand(command,args,cwd){return new Promise((resolve5)=>{let output=[],child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}});child.stdout?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stdout.write(str2)}),child.stderr?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stderr.write(str2)}),child.on("close",(code)=>{resolve5({success:code===0,output:output.join("")})}),child.on("error",(err)=>{error(err.message),resolve5({success:!1,output:err.message})})})}async function getGitInfo(cwd){try{let branchResult=await runCommandSilent("git",["rev-parse","--abbrev-ref","HEAD"],cwd),commitResult=await runCommandSilent("git",["rev-parse","--short","HEAD"],cwd),dateResult=await runCommandSilent("git",["log","-1","--format=%ci"],cwd);if(branchResult.success&&commitResult.success&&dateResult.success)return{branch:branchResult.output.trim(),commit:commitResult.output.trim(),commitDate:dateResult.output.trim().split(" ")[0]}}catch{}return null}async function runCommandSilent(command,args,cwd,timeoutMs=4000){return new Promise((resolve5)=>{let output=[],settled=!1,child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"]}),timer2=setTimeout(()=>{if(settled)return;settled=!0,child.kill("SIGTERM"),resolve5({success:!1,output:`Timed out after ${timeoutMs}ms`})},timeoutMs);child.stdout?.on("data",(data)=>{output.push(data.toString())}),child.stderr?.on("data",(data)=>{output.push(data.toString())}),child.on("close",(code)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve5({success:code===0,output:output.join("")})}),child.on("error",(err)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve5({success:!1,output:err.message})})})}function detectFromBinaryPath(path3){let resolved=path3;try{resolved=__require("fs").realpathSync(path3)}catch{}if(resolved.includes(".bun"))return"bun";if(resolved.includes("node_modules"))return"npm";if(path3===join34(LOCAL_BIN2,"genie")||resolved.startsWith(GENIE_BIN))return"source";return null}async function detectInstallationType(){let whichResult=await runCommandSilent("which",["genie"]);if(whichResult.success){let detected=detectFromBinaryPath(whichResult.output.trim());if(detected)return detected}if(genieConfigExists())try{let config=await loadGenieConfig();if(config.installMethod)return config.installMethod}catch{}if(existsSync29(join34(GENIE_SRC,".git")))return"source";return(await runCommandSilent("which",["bun"])).success?"bun":"npm"}async function updateViaBun(channel){try{__require("fs").unlinkSync(join34(homedir28(),".bun","install","global","bun.lock"))}catch{}if(log(`Updating via bun (channel: ${channel})...`),!(await runCommand("bun",["add","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via bun"),!1;return console.log(),success(`Genie CLI updated via bun (${channel})!`),!0}async function updateViaNpm(channel){if(log(`Updating via npm (channel: ${channel})...`),!(await runCommand("npm",["install","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via npm"),!1;return console.log(),success(`Genie CLI updated via npm (${channel})!`),!0}async function detectGlobalInstalls(){let found=new Set,[npmResult,bunResult]=await Promise.all([runCommandSilent("npm",["list","-g","@automagik/genie"]),runCommandSilent("bun",["pm","ls","-g"])]);if(npmResult.success&&!npmResult.output.includes("(empty)"))found.add("npm");if(bunResult.success&&bunResult.output.includes("@automagik/genie"))found.add("bun");return found}async function updateSource(){if(!existsSync29(GENIE_SRC))error(`Source install path not found: ${GENIE_SRC}`),console.error(" Detection picked the source-install path, but the directory does not exist."),console.error(" This usually means a stale install hint (config or ~/.genie/src/.git) is"),console.error(" pointing somewhere genuine. Either:"),console.error(` 1. Re-clone the source: git clone https://github.com/automagik-dev/genie ${GENIE_SRC}`),console.error(" 2. Update via package manager instead: genie update --next --via bun"),console.error(" 3. Inspect detection: genie doctor --update-detection"),process.exit(1);if(!existsSync29(join34(GENIE_SRC,".git")))error(`Source install path is not a git checkout: ${GENIE_SRC}`),console.error(` ${GENIE_SRC} exists but has no .git/. Cannot run \`git fetch\` from it.`),console.error(` Either delete ${GENIE_SRC} and re-clone, or update via package manager:`),console.error(" genie update --next --via bun"),process.exit(1);let beforeInfo=await getGitInfo(GENIE_SRC);if(beforeInfo)console.log(`Current: \x1B[2m${beforeInfo.branch}@${beforeInfo.commit} (${beforeInfo.commitDate})\x1B[0m`),console.log();if(log("Fetching latest changes..."),!(await runCommand("git",["fetch","origin"],GENIE_SRC)).success)error("Failed to fetch from origin"),process.exit(1);if(log("Resetting to origin/main..."),!(await runCommand("git",["reset","--hard","origin/main"],GENIE_SRC)).success)error("Failed to reset to origin/main"),process.exit(1);console.log();let afterInfo=await getGitInfo(GENIE_SRC);if(beforeInfo&&afterInfo&&beforeInfo.commit===afterInfo.commit){success("Already up to date!"),console.log();return}if(log("Installing dependencies..."),!(await runCommand("bun",["install"],GENIE_SRC)).success)error("Failed to install dependencies"),process.exit(1);if(console.log(),log("Building..."),!(await runCommand("bun",["run","build"],GENIE_SRC)).success)error("Failed to build"),process.exit(1);console.log(),log("Installing binaries...");try{await mkdir5(GENIE_BIN,{recursive:!0}),await mkdir5(LOCAL_BIN2,{recursive:!0});let binaries=["genie.js","term.js"],names=["genie","term"];for(let i2=0;i2<binaries.length;i2++){let src=join34(GENIE_SRC,"dist",binaries[i2]),binDest=join34(GENIE_BIN,binaries[i2]),linkDest=join34(LOCAL_BIN2,names[i2]);await copyFile(src,binDest),await chmod(binDest,493),await symlinkOrCopy(binDest,linkDest)}for(let legacy of["claudio.js","claudio"]){let legacyBin=join34(GENIE_BIN,legacy),legacyLink=join34(LOCAL_BIN2,legacy);try{await unlink2(legacyBin)}catch{}try{await unlink2(legacyLink)}catch{}}success("Binaries installed")}catch(err){error(`Failed to install binaries: ${err}`),process.exit(1)}if(console.log(),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),success("Genie CLI updated successfully!"),console.log(),afterInfo)console.log(`Version: \x1B[36m${afterInfo.branch}@${afterInfo.commit}\x1B[0m (${afterInfo.commitDate})`),console.log()}async function symlinkOrCopy(src,dest){let{symlink:symlink2,unlink:unlink3}=await import("fs/promises");try{if(existsSync29(dest))await unlink3(dest);await symlink2(src,dest)}catch{await copyFile(src,dest)}}var FRAMEWORK_MARKER_FILES=new Set([".orphaned_at"]);function copyDirSync(src,dest){mkdirSync15(dest,{recursive:!0});for(let entry2 of readdirSync9(src,{withFileTypes:!0})){if(FRAMEWORK_MARKER_FILES.has(entry2.name))continue;let srcPath=join34(src,entry2.name),destPath=join34(dest,entry2.name);if(entry2.isDirectory())copyDirSync(srcPath,destPath);else copyFileSync3(srcPath,destPath)}}async function resolveGlobalPkgDir(installType){if(installType==="bun"){let bunPath=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync29(bunPath))return bunPath}if(installType==="npm"){let npmRootResult=await runCommandSilent("npm",["root","-g"]);if(npmRootResult.success){let npmPath=join34(npmRootResult.output.trim(),"@automagik","genie");if(existsSync29(npmPath))return npmPath}}let bunFallback=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync29(bunFallback))return bunFallback;let npmRootFallback=await runCommandSilent("npm",["root","-g"]);if(npmRootFallback.success){let npmPath=join34(npmRootFallback.output.trim(),"@automagik","genie");if(existsSync29(npmPath))return npmPath}return null}function updatePluginRegistry(claudePlugins,cacheDir,version){let registryPath=join34(claudePlugins,"installed_plugins.json");try{if(!existsSync29(registryPath))return;let registry=JSON.parse(readFileSync20(registryPath,"utf-8")),entries=registry.plugins?.["genie@automagik"];if(!Array.isArray(entries))return;for(let entry2 of entries)if(entry2.scope==="user")entry2.installPath=cacheDir,entry2.version=version,entry2.lastUpdated=new Date().toISOString();writeFileSync13(registryPath,JSON.stringify(registry,null,2))}catch(err){log(`Registry update failed (non-fatal): ${err}`)}}function syncTmuxConf(tmuxScriptsSrc){mkdirSync15(GENIE_HOME2,{recursive:!0});let tmuxConfSrc=join34(tmuxScriptsSrc,"genie.tmux.conf"),tmuxConfDest=join34(GENIE_HOME2,"tmux.conf");if(existsSync29(tmuxConfSrc))try{copyFileSync3(tmuxConfSrc,tmuxConfDest),success(`Installed tmux config to ${tmuxConfDest}`);try{let{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));execSync3(`${tmuxBin2()} -L genie source-file '${tmuxConfDest}'`,{stdio:"ignore"}),success("Reloaded genie tmux server configuration")}catch{}}catch{}let tuiConfSrc=join34(tmuxScriptsSrc,"tui-tmux.conf"),tuiConfDest=join34(GENIE_HOME2,"tui-tmux.conf");if(existsSync29(tuiConfSrc))try{copyFileSync3(tuiConfSrc,tuiConfDest),success(`Installed TUI tmux config to ${tuiConfDest}`)}catch{}let themeSrc=join34(tmuxScriptsSrc,".generated.theme.conf"),themeDest=join34(GENIE_HOME2,".generated.theme.conf");if(existsSync29(themeSrc))try{copyFileSync3(themeSrc,themeDest),success(`Installed tmux theme to ${themeDest}`)}catch{}let osc52Src=join34(tmuxScriptsSrc,"osc52-copy.sh"),osc52Dest=join34(GENIE_HOME2,"scripts","osc52-copy.sh");if(existsSync29(osc52Src))try{copyFileSync3(osc52Src,osc52Dest),chmodSync2(osc52Dest,493),success(`Installed OSC 52 clipboard helper to ${osc52Dest}`)}catch{}}function syncTmuxScripts(globalPkgDir){let tmuxScriptsSrc=join34(globalPkgDir,"scripts","tmux");if(!existsSync29(tmuxScriptsSrc))return;let scriptsDir=join34(GENIE_HOME2,"scripts");mkdirSync15(scriptsDir,{recursive:!0});let scriptCount=0;for(let entry2 of readdirSync9(tmuxScriptsSrc))if(entry2.endsWith(".sh")||entry2==="genie.tmux.conf"||entry2==="tui-tmux.conf"||entry2===".generated.theme.conf"){let src=join34(tmuxScriptsSrc,entry2),dest=join34(scriptsDir,entry2);copyFileSync3(src,dest);try{chmodSync2(dest,entry2.endsWith(".sh")?493:420)}catch{}scriptCount++}if(scriptCount>0)success(`Refreshed ${scriptCount} tmux scripts at ${scriptsDir}`);syncTmuxConf(tmuxScriptsSrc)}function syncMarketplaceVersion(claudePlugins,version){let marketplacePath=join34(claudePlugins,"marketplaces","automagik",".claude-plugin","marketplace.json");try{if(!existsSync29(marketplacePath))return;let data=JSON.parse(readFileSync20(marketplacePath,"utf-8"));if(Array.isArray(data.plugins)){for(let plugin of data.plugins)if(plugin.name==="genie")plugin.version=version}writeFileSync13(marketplacePath,JSON.stringify(data,null,2)),success(`Updated marketplace.json to v${version}`)}catch(err){log(`Marketplace version update failed (non-fatal): ${err}`)}}function syncPluginPackageVersion(claudePlugins,version){let pkgPath=join34(claudePlugins,"marketplaces","automagik","plugins","genie","package.json");try{if(!existsSync29(pkgPath))return;let data=JSON.parse(readFileSync20(pkgPath,"utf-8"));data.version=version,writeFileSync13(pkgPath,JSON.stringify(data,null,2)),success(`Updated plugin package.json to v${version}`)}catch(err){log(`Plugin package.json update failed (non-fatal): ${err}`)}}function syncSkillsSymlink(claudePlugins,version){let skillsLink=join34(claudePlugins,"marketplaces","automagik","plugins","genie","skills"),cacheSkills=join34("..","..","..","..","cache","automagik","genie",version,"skills");try{let{symlinkSync,unlinkSync:unlinkSync9,lstatSync:lstatSync2}=__require("fs");try{lstatSync2(skillsLink),unlinkSync9(skillsLink)}catch{}symlinkSync(cacheSkills,skillsLink),success(`Skills symlink \u2192 cache/${version}/skills`)}catch(err){log(`Skills symlink update failed (non-fatal): ${err}`)}}async function syncPlugin(installType){log("Syncing Claude Code plugin...");let globalPkgDir=await resolveGlobalPkgDir(installType);if(!globalPkgDir)return log("Could not find installed package \u2014 skipping plugin sync"),{skippedReason:"installed package not found"};let pluginSrc=join34(globalPkgDir,"plugins","genie");if(!existsSync29(pluginSrc))return log("Plugin source not found in package \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"plugin source not found in package"};let version;try{version=JSON.parse(readFileSync20(join34(globalPkgDir,"package.json"),"utf-8")).version}catch{return log("Could not read package version \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"could not read package version"}}let claudePlugins=join34(homedir28(),".claude","plugins"),cacheDir=join34(claudePlugins,"cache","automagik","genie",version);try{if(existsSync29(cacheDir))rmSync3(cacheDir,{recursive:!0,force:!0});copyDirSync(pluginSrc,cacheDir);let skillsSrc=join34(globalPkgDir,"skills");if(existsSync29(skillsSrc)&&!existsSync29(join34(cacheDir,"skills")))copyDirSync(skillsSrc,join34(cacheDir,"skills"))}catch(err){return error(`Failed to copy plugin: ${err}`),{version,globalPkgDir,cacheDir,skippedReason:`failed to copy plugin: ${err}`}}return updatePluginRegistry(claudePlugins,cacheDir,version),syncMarketplaceVersion(claudePlugins,version),syncPluginPackageVersion(claudePlugins,version),syncSkillsSymlink(claudePlugins,version),syncTmuxScripts(globalPkgDir),success(`Plugin synced to v${version}`),{version,globalPkgDir,cacheDir}}async function resolveChannel(options){if(options.next)return"next";if(options.stable)return"latest";if(genieConfigExists())try{let config=await loadGenieConfig();if(config.updateChannel)return config.updateChannel}catch{}return"latest"}async function persistChannel(channel){try{let config=await loadGenieConfig();config.updateChannel=channel,await saveGenieConfig(config)}catch{}}async function updateCommand(options={}){console.log(),console.log(`${colorize("\x1B[1m","\x1B[0m","\uD83E\uDDDE Genie CLI Update")}`),console.log(`${colorize("\x1B[2m","\x1B[0m","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`),console.log();let cleanupSkipList=buildCleanupSkipList(options),noRestart=options.restart===!1||isTruthyEnv(process.env.GENIE_UPDATE_NO_RESTART),noVerify=options.verify===!1||isTruthyEnv(process.env.GENIE_UPDATE_NO_VERIFY),channel=await resolveChannel(options);if(options.next||options.stable)await persistChannel(channel);let latestVersion=await fetchLatestVersion(channel);if(shortCircuitIfCurrent(VERSION,latestVersion)){success(`Already up to date (v${normalizeVersion(VERSION)}, channel ${channel})`),console.log();return}let installType=await detectInstallationType();if(log(`Detected installation: ${installType}`),log(`Channel: ${channel}${channel==="next"?" (dev builds)":" (stable)"}`),latestVersion)log(`Update available: ${normalizeVersion(VERSION)} \u2192 ${normalizeVersion(latestVersion)}`);else log(`Registry version unavailable (proceeding with reinstall of channel ${channel})`);if(console.log(),installType==="unknown")error("No Genie CLI installation found"),console.log(),console.log("Install method not configured. Please reinstall genie:"),console.log(`${colorize("\x1B[36m","\x1B[0m"," curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash")}`),console.log(),process.exit(1);if(!shouldAutoConfirm(options)){let proceedQuestion=latestVersion?`Update v${normalizeVersion(VERSION)} \u2192 v${normalizeVersion(latestVersion)}?`:`Reinstall channel "${channel}"?`;if(!await promptConfirm(proceedQuestion)){console.log(),log("Update declined."),console.log();return}}if(installType==="source"){await updateSource();return}let globalInstalls=await detectGlobalInstalls(),primaryMethod=installType;if(!(primaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))process.exit(1);let secondaryMethod=primaryMethod==="bun"?"npm":"bun";if(globalInstalls.has(secondaryMethod)){if(console.log(),log(`Also updating ${secondaryMethod}-global install...`),!(secondaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))error(`Secondary update via ${secondaryMethod} failed (non-blocking)`)}let plugin=await syncPlugin(installType),cleanupReport=await runLegacyCleanupSafe(cleanupSkipList);await runPostUpdateMaintenanceSafe({...options,noRestart,noVerify},{channel,installType,primaryMethod,globalInstalls:[...globalInstalls].sort(),plugin,latestVersion,cliVersion:VERSION},cleanupReport)}function buildCleanupSkipList(options){let skipList=parseSkipCleanupFlag(options.skipCleanup);if(options.sidecarCleanup===!1)skipList.add("nats-reply-sidecar"),log("--no-sidecar-cleanup (no-op for genie, retained for cross-CLI portability)");return skipList}async function runLegacyCleanupSafe(skipList){try{return await cleanupLegacyArtifacts(skipList)}catch(err){let msg=err instanceof Error?err.message:String(err);return log(`Legacy artifact cleanup failed (non-fatal): ${msg}`),{entries:[]}}}function printPostUpdateMaintenanceIntro(){console.log(),log("Running post-update maintenance..."),console.log(" Purpose: make first launch after update fast and collect upgrade health signals."),console.log(" Checks: runtime partitions, watchdog status, session backfill drift, zombie rows, team config orphans."),console.log(" PG policy: read-only; uses an already-running pgserve when available and will not auto-start it."),console.log(" Legend: [ok]=healthy, [fix]=fixed, [--]=skipped/non-blocking, [!!]=operator action needed.")}async function runMaintenanceWithCapturedLines(maintenanceLines){let{runPostUpdateMaintenance:runPostUpdateMaintenance2}=await Promise.resolve().then(() => (init_doctor(),exports_doctor));await withTemporaryEnv("GENIE_PG_NO_AUTOSTART","1",()=>runPostUpdateMaintenance2({log:(line)=>{maintenanceLines.push(line),console.log(line)}}))}function printDiagnosticsSummary(diagnostics){if(log("Post-update diagnostics captured."),console.log(` Report: ${diagnostics.path}`),console.log(" Include this file when opening a GitHub issue; it contains install metadata, step output,"),console.log(" local process state, and recent scheduler/TUI log signals."),diagnostics.signals.length===0)return;console.log(" Recent scheduler signals:");for(let signal of diagnostics.signals.slice(0,3)){let errorDetail=signal.lastError?` \u2014 ${signal.lastError}`:"";console.log(` ${signal.level}:${signal.event} \xD7${signal.count}${errorDetail}`)}}async function capturePostUpdateDiagnostics(diagnosticsCtx,maintenance,extras){if(!diagnosticsCtx)return;try{let diagnostics=await collectUpdateDiagnostics(diagnosticsCtx,maintenance,extras);printDiagnosticsSummary(diagnostics)}catch(err){let msg=err instanceof Error?err.message:String(err);log(`Post-update diagnostics capture failed (non-fatal): ${msg}`)}}function printVerifyBanner(result2){console.log();for(let line of formatVerifyBanner(result2))console.log(` ${line}`);console.log()}async function runPostUpdateMaintenanceSafe(options,diagnosticsCtx,cleanupReport){if(options.noRestart){log("--no-restart: skipping maintenance and verify probe.");let verify2=await runVerifyProbe({cliVersion:VERSION,skipReason:"no-restart"});printVerifyBanner(verify2),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome:"completed",durationMs:0,lines:[]},{verify:verify2,cleanups:cleanupReport});return}if(options.skipMaintenance||isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)){log("Skipping post-update maintenance (requested).");let verify2=await runVerifyProbe({cliVersion:VERSION,skipReason:"no-restart"});printVerifyBanner(verify2),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome:"completed",durationMs:0,lines:[]},{verify:verify2,cleanups:cleanupReport});return}let startedAt=Date.now(),maintenanceLines=[],outcome="completed",maintenanceError;try{printPostUpdateMaintenanceIntro(),await runMaintenanceWithCapturedLines(maintenanceLines),success(`Post-update maintenance complete (${formatDuration2(Date.now()-startedAt)}).`)}catch(err){outcome="failed",maintenanceError=err instanceof Error?err.message:String(err),error(`Post-update maintenance skipped: ${maintenanceError}`)}await restartServeIfStaleSafe();let verify=options.noVerify?await runVerifyProbe({cliVersion:VERSION,skipReason:"no-verify-flag"}):await runVerifyProbe({cliVersion:VERSION});if(printVerifyBanner(verify),await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome,durationMs:Date.now()-startedAt,lines:maintenanceLines,error:maintenanceError},{verify,cleanups:cleanupReport}),(verify.kind==="health-unreachable"||verify.kind==="daemon-stale-inode")&&!options.noVerify)process.exitCode=1}async function restartServeIfStaleSafe(){try{await restartServeIfStale()}catch(err){let msg=err instanceof Error?err.message:String(err);log(`Stale-serve restart skipped (non-fatal): ${msg}`)}}init_version();init_trust();init_trust();init_trust();import{execSync as execSync4}from"child_process";import{existsSync as existsSync31,mkdirSync as mkdirSync16,readFileSync as readFileSync22,renameSync as renameSync6,writeFileSync as writeFileSync14}from"fs";import{dirname as dirname13,isAbsolute,join as join36,resolve as resolve5}from"path";function resolveOriginUrl(repoRoot){try{return execSync4("git config --get remote.origin.url",{cwd:repoRoot,encoding:"utf-8"}).trim()||null}catch{return null}}function findRepoRoot(start){let current=start;while(current!=="/"){if(existsSync31(join36(current,".git")))return current;current=dirname13(current)}return null}function writeTrustFile(path3,file){let dir=dirname13(path3);if(!existsSync31(dir))mkdirSync16(dir,{recursive:!0});let sorted={version:1,entries:[...file.entries].sort((a,b2)=>a.path.localeCompare(b2.path))},serialized=`${JSON.stringify(sorted,null,2)}
|
|
3990
3990
|
`,tmpPath=`${path3}.tmp.${process.pid}`;writeFileSync14(tmpPath,serialized,"utf-8"),renameSync6(tmpPath,path3)}function runTrustList(trustPath){let current=readTrustFile(trustPath);if(current.entries.length===0){console.log(`(trust file empty: ${trustPath})`);return}console.log(`Trusted hooks (${current.entries.length}, from ${trustPath}):`);for(let entry2 of current.entries){let scope=entry2.scope==="repo"?`repo:${entry2.repoRemoteUrl??"?"}`:entry2.scope;if(console.log(` ${entry2.path}`),console.log(` scope: ${scope}`),console.log(` sha256: ${entry2.sha256}`),console.log(` trusted: ${entry2.trustedAt}`),entry2.capabilities&&entry2.capabilities.length>0)console.log(` caps: ${entry2.capabilities.join(", ")}`);if(entry2.note)console.log(` note: ${entry2.note}`)}}function resolveTrustScope(filePath,options){if(options.repo){let repoRoot=findRepoRoot(dirname13(filePath));if(!repoRoot)console.error(`Error: --repo passed but no .git directory found above ${filePath}`),process.exit(1);let remote=resolveOriginUrl(repoRoot);if(!remote)console.error(`Error: --repo passed but ${repoRoot} has no remote.origin.url`),process.exit(1);return{scope:"repo",repoRemoteUrl:remote}}if(options.team)return{scope:"team"};return{scope:"global"}}async function confirmTrustOrAbort(yes){if(yes||!process.stdin.isTTY)return;process.stdout.write("Confirm? (y/N) ");let buf=Buffer.alloc(8),read=0;try{read=(await import("fs")).readSync(0,buf,0,buf.length,null)}catch{console.error("Error: cannot read confirmation; pass --yes to trust non-interactively"),process.exit(1)}let answer=buf.subarray(0,read).toString().trim().toLowerCase();if(answer!=="y"&&answer!=="yes")console.log("Aborted."),process.exit(1)}async function runTrustAdd(target,options,trustPath){let filePath=isAbsolute(target)?target:resolve5(process.cwd(),target);if(!existsSync31(filePath))console.error(`Error: file not found: ${filePath}`),process.exit(1);if(!filePath.endsWith(".ts"))console.error(`Error: trust target must be a .ts file: ${filePath}`),process.exit(1);let{scope,repoRemoteUrl}=resolveTrustScope(filePath,options),sha=sha256OfFile(filePath),source=readFileSync22(filePath,"utf-8"),capabilities=parseCapabilities(source);if(console.log(`About to trust: ${filePath}`),console.log(` scope: ${scope==="repo"?`repo:${repoRemoteUrl}`:scope}`),console.log(` sha256: ${sha}`),capabilities.length>0)console.log(` caps: ${capabilities.join(", ")}`);if(options.note)console.log(` note: ${options.note}`);await confirmTrustOrAbort(options.yes??!1);let current=readTrustFile(trustPath),newEntry={path:filePath,sha256:sha,scope,repoRemoteUrl,trustedAt:new Date().toISOString(),note:options.note,capabilities:capabilities.length>0?capabilities:void 0},next={version:1,entries:[...current.entries.filter((e)=>e.path!==filePath),newEntry]};writeTrustFile(trustPath,next),console.log(`Trusted ${filePath} \u2192 ${trustPath}`)}async function trustAction(target,options){let trustPath=defaultTrustPath();if(!target)return runTrustList(trustPath);await runTrustAdd(target,options,trustPath)}function registerHookTrustCommand(parent){parent.command("trust [path]").description("Add a .ts hook file to the trust allowlist (or list current entries when [path] is omitted)").option("--repo","Scope to current repo (pinned to remote.origin.url)").option("--global","Scope globally (default)").option("--team <name>","Scope to a specific team directory").option("--note <text>","Free-form note saved with the entry").option("--yes","Skip the interactive confirmation prompt").action(trustAction)}import{appendFileSync as appendFileSync3,chmodSync as chmodSync3,mkdirSync as mkdirSync17,statSync as statSync8}from"fs";import{connect as connect2}from"net";import{homedir as homedir30}from"os";import{dirname as dirname14,join as join37}from"path";var PATTERNS=[{kind:"gh-token",re:/gh[ps]_[A-Za-z0-9]{30,}/g},{kind:"sk-token",re:/sk-[A-Za-z0-9-]{20,}/g},{kind:"glpat",re:/glpat-[A-Za-z0-9_-]{20,}/g},{kind:"hex",re:/\b[a-f0-9]{40,}\b/g}];function redactTokenShapes(text){if(text==null)return null;if(process.env.GENIE_HOOK_REDACTION==="off")return String(text);let out=String(text);for(let{kind,re}of PATTERNS)out=out.replace(re,`[REDACTED:${kind}]`);return out}function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook.sock")}function fallbackLogPath2(){let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
|
|
3991
3991
|
`)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function ensureLogPermissions(path3){let st;try{st=statSync8(path3)}catch{return}let mode=st.mode&511;if(mode===384)return;try{chmodSync3(path3,384),process.stderr.write(`[genie-hook] tightened ${path3} permissions ${mode.toString(8)} -> 600
|
|
3992
3992
|
`)}catch{}}function appendFallback(record){let path3=fallbackLogPath2();try{mkdirSync17(dirname14(path3),{recursive:!0}),ensureLogPermissions(path3);let safe={...record,command:redactTokenShapes(record.command)},writeFresh=!1;try{if(statSync8(path3).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(safe)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260507.1",
|
|
4
4
|
"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260507.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"
|
package/skills/trace/SKILL.md
CHANGED
|
@@ -89,6 +89,7 @@ The orchestrator then hands the report to `/fix`.
|
|
|
89
89
|
- Never fix during trace — investigation only, always separate from correction.
|
|
90
90
|
- Always reproduce before theorizing — if the failure can't be reproduced, the report must say so.
|
|
91
91
|
- Evidence required — every root cause claim must include file paths, line numbers, and a causal chain.
|
|
92
|
+
- **Verify exports before citing them.** Every function name in a "Recommended correction" must be confirmed via `grep -n "export.*<name>"` against the actual file. Never cite a function name from memory or from a stale convention — the codebase evolves and refactors rename or delete public API regularly. A trace report that hallucinates a function name (e.g. claiming `resolveAgentId` exists when only `getAgentByName` does) sends `/fix` down a path that fails type-check and wastes a fix loop. Closes #1674 Bug 5.
|
|
92
93
|
- Hand off to `/fix` — trace produces a report, `/fix` applies the correction. Never combine them.
|
|
93
94
|
- Read-only tools — trace subagent uses Read, Bash, Glob, Grep. No Write, no Edit.
|
|
94
95
|
- If root cause spans multiple systems, report each separately with confidence levels.
|