@automagik/genie 4.260416.6 → 4.260417.2

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
@@ -519,11 +519,12 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
519
519
  AND ended_at IS NULL
520
520
  ORDER BY started_at DESC
521
521
  LIMIT 1
522
- `;return rows.length>0?rowToExecutor(rows[0]):null}async function relinkExecutorToAgent(executorId,agentId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function updateClaudeSessionId(executorId,sessionId){await(await getConnection())`UPDATE executors SET claude_session_id = ${sessionId} WHERE id = ${executorId}`}var init_executor_registry=__esm(()=>{init_audit();init_db()});var exports_team_manager={};__export(exports_team_manager,{validateBranchName:()=>validateBranchName,updateTeamConfig:()=>updateTeamConfig,unarchiveTeam:()=>unarchiveTeam,setTeamStatus:()=>setTeamStatus,resolveLeaderName:()=>resolveLeaderName,listTeams:()=>listTeams2,listMembers:()=>listMembers,killTeamMembers:()=>killTeamMembers,hireAgent:()=>hireAgent,getTeam:()=>getTeam,fireAgent:()=>fireAgent,disbandTeam:()=>disbandTeam,createTeam:()=>createTeam,archiveTeam:()=>archiveTeam});import{existsSync as existsSync18}from"fs";import{mkdir as mkdir4,rm as rm2,symlink}from"fs/promises";import{homedir as homedir17}from"os";import path2,{join as join20}from"path";var{$:$3}=globalThis.Bun;function parseMembers(raw){if(Array.isArray(raw))return raw;if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed:[]}catch{return[]}return[]}function rowToTeamConfig(row){let config={name:row.name,repo:row.repo,baseBranch:row.base_branch,worktreePath:row.worktree_path,members:parseMembers(row.members),status:row.status,createdAt:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)};if(row.leader)config.leader=row.leader;if(row.native_team_parent_session_id)config.nativeTeamParentSessionId=row.native_team_parent_session_id;if(row.native_teams_enabled)config.nativeTeamsEnabled=row.native_teams_enabled;if(row.tmux_session_name)config.tmuxSessionName=row.tmux_session_name;if(row.wish_slug)config.wishSlug=row.wish_slug;if(row.spawner)config.spawner=row.spawner;if(row.archived_at)config.archivedAt=row.archived_at instanceof Date?row.archived_at.toISOString():String(row.archived_at);return config}function getGenieDir2(){return process.env.GENIE_HOME??join20(homedir17(),".genie")}function getWorktreeBase(repoPath){let base=loadGenieConfigSync().terminal?.worktreeBase;if(base&&base!==".worktrees"){if(path2.isAbsolute(base))return base;return join20(repoPath,base)}let projectName=path2.basename(repoPath);return join20(getGenieDir2(),"worktrees",projectName)}function validateBranchName(name){let errors3=[];if(/\s/.test(name))errors3.push("contains spaces");if(name.includes(".."))errors3.push('contains ".."');if(name.includes("~"))errors3.push('contains "~"');if(name.includes("^"))errors3.push('contains "^"');if(name.includes(":"))errors3.push('contains ":"');if(name.includes("?"))errors3.push('contains "?"');if(name.includes("*"))errors3.push('contains "*"');if(name.includes("["))errors3.push('contains "["');if(name.includes("\\"))errors3.push('contains "\\"');if(/[\x00-\x1f\x7f]/.test(name))errors3.push("contains control characters");if(name.endsWith(".lock"))errors3.push('ends with ".lock"');if(name.endsWith("/"))errors3.push('ends with "/"');if(name.endsWith("."))errors3.push('ends with "."');if(name.startsWith("-"))errors3.push('starts with "-"');if(errors3.length>0)throw Error(`Invalid team name '${name}': must be a valid git branch name (${errors3.join(", ")})`)}async function killWorkersByName(agentName,teamName){let matches=(await list()).filter((w)=>(w.role===agentName||w.id===agentName)&&(!teamName||w.team===teamName));for(let w of matches){if(w.currentExecutorId)try{await terminateExecutor(w.currentExecutorId),await setCurrentExecutor(w.id,null)}catch{}try{if(w.paneId&&w.paneId!=="inline"){let{execSync:execSync5}=__require("child_process"),{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync5(genieTmuxCmd2(`kill-pane -t ${w.paneId}`),{stdio:"ignore"})}}catch{}await unregister(w.id)}}async function postCloneInit(repoPath,worktreePath){let parentNodeModules=join20(repoPath,"node_modules"),worktreeNodeModules=join20(worktreePath,"node_modules");if(existsSync18(parentNodeModules)&&!existsSync18(worktreeNodeModules))try{await symlink(parentNodeModules,worktreeNodeModules,"dir")}catch{}let initScript=join20(repoPath,".genie","init.sh");if(existsSync18(initScript))try{await $3`bash ${initScript}`.cwd(worktreePath).quiet()}catch{}}async function ensureWorktree(repoPath,branchName,worktreePath,baseBranch){try{await $3`git -C ${repoPath} fetch origin ${baseBranch}`.quiet()}catch{}if(await mkdir4(path2.dirname(worktreePath),{recursive:!0}),existsSync18(worktreePath))return;let branchExists=!1;try{await $3`git -C ${repoPath} rev-parse --verify ${branchName}`.quiet(),branchExists=!0}catch{if(!branchExists)try{await $3`git -C ${repoPath} branch ${branchName} origin/${baseBranch}`.quiet()}catch{try{await $3`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet()}catch{await $3`git -C ${repoPath} branch ${branchName}`.quiet()}}}await $3`git clone --shared --branch ${branchName} ${repoPath} ${worktreePath}`.quiet();try{let userName=(await $3`git -C ${repoPath} config user.name`.quiet()).text().trim(),userEmail=(await $3`git -C ${repoPath} config user.email`.quiet()).text().trim();if(userName)await $3`git -C ${worktreePath} config user.name ${userName}`.quiet();if(userEmail)await $3`git -C ${worktreePath} config user.email ${userEmail}`.quiet()}catch{}await postCloneInit(repoPath,worktreePath)}async function createTeam(name,repo,baseBranch="dev"){validateBranchName(name);let repoPath=path2.resolve(repo),existing=await getTeam(name);if(existing)return existing;let worktreeBase=getWorktreeBase(repoPath),worktreePath=join20(worktreeBase,name);await ensureWorktree(repoPath,name,worktreePath,baseBranch);let now=new Date().toISOString(),config={name,repo:repoPath,baseBranch,worktreePath,members:[],status:"in_progress",createdAt:now};if(isInsideClaudeCode()){config.nativeTeamsEnabled=!0;try{let result2=await registerAsTeamLead(name);config.nativeTeamParentSessionId=result2.sessionId}catch{}}return await(await getConnection())`
522
+ `;return rows.length>0?rowToExecutor(rows[0]):null}async function relinkExecutorToAgent(executorId,agentId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function updateClaudeSessionId(executorId,sessionId){await(await getConnection())`UPDATE executors SET claude_session_id = ${sessionId} WHERE id = ${executorId}`}var init_executor_registry=__esm(()=>{init_audit();init_db()});var exports_team_manager={};__export(exports_team_manager,{validateBranchName:()=>validateBranchName,updateTeamConfig:()=>updateTeamConfig,unarchiveTeam:()=>unarchiveTeam,setTeamStatus:()=>setTeamStatus,resolveLeaderName:()=>resolveLeaderName,listTeams:()=>listTeams2,listMembers:()=>listMembers,killTeamMembers:()=>killTeamMembers,hireAgent:()=>hireAgent,getTeam:()=>getTeam,fireAgent:()=>fireAgent,disbandTeam:()=>disbandTeam,createTeam:()=>createTeam,archiveTeam:()=>archiveTeam});import{existsSync as existsSync18}from"fs";import{mkdir as mkdir4,rm as rm2,symlink}from"fs/promises";import{homedir as homedir17}from"os";import path2,{join as join20}from"path";var{$:$3}=globalThis.Bun;function parseMembers(raw){if(Array.isArray(raw))return raw;if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed:[]}catch{return[]}return[]}function rowToTeamConfig(row){let config={name:row.name,repo:row.repo,baseBranch:row.base_branch,worktreePath:row.worktree_path,members:parseMembers(row.members),status:row.status,createdAt:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)};if(row.leader)config.leader=row.leader;if(row.native_team_parent_session_id)config.nativeTeamParentSessionId=row.native_team_parent_session_id;if(row.native_teams_enabled)config.nativeTeamsEnabled=row.native_teams_enabled;if(row.tmux_session_name)config.tmuxSessionName=row.tmux_session_name;if(row.wish_slug)config.wishSlug=row.wish_slug;if(row.spawner)config.spawner=row.spawner;if(row.archived_at)config.archivedAt=row.archived_at instanceof Date?row.archived_at.toISOString():String(row.archived_at);if(row.parent_team)config.parentTeam=row.parent_team;let allow=parseAllowChildReachback(row.allow_child_reachback);if(allow.length>0)config.allowChildReachback=allow;return config}function parseAllowChildReachback(raw){if(Array.isArray(raw))return raw.filter((x)=>typeof x==="string");if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter((x)=>typeof x==="string"):[]}catch{return[]}return[]}function getGenieDir2(){return process.env.GENIE_HOME??join20(homedir17(),".genie")}function getWorktreeBase(repoPath){let base=loadGenieConfigSync().terminal?.worktreeBase;if(base&&base!==".worktrees"){if(path2.isAbsolute(base))return base;return join20(repoPath,base)}let projectName=path2.basename(repoPath);return join20(getGenieDir2(),"worktrees",projectName)}function validateBranchName(name){let errors3=[];if(/\s/.test(name))errors3.push("contains spaces");if(name.includes(".."))errors3.push('contains ".."');if(name.includes("~"))errors3.push('contains "~"');if(name.includes("^"))errors3.push('contains "^"');if(name.includes(":"))errors3.push('contains ":"');if(name.includes("?"))errors3.push('contains "?"');if(name.includes("*"))errors3.push('contains "*"');if(name.includes("["))errors3.push('contains "["');if(name.includes("\\"))errors3.push('contains "\\"');if(/[\x00-\x1f\x7f]/.test(name))errors3.push("contains control characters");if(name.endsWith(".lock"))errors3.push('ends with ".lock"');if(name.endsWith("/"))errors3.push('ends with "/"');if(name.endsWith("."))errors3.push('ends with "."');if(name.startsWith("-"))errors3.push('starts with "-"');if(errors3.length>0)throw Error(`Invalid team name '${name}': must be a valid git branch name (${errors3.join(", ")})`)}async function killWorkersByName(agentName,teamName){let matches=(await list()).filter((w)=>(w.role===agentName||w.id===agentName)&&(!teamName||w.team===teamName));for(let w of matches){if(w.currentExecutorId)try{await terminateExecutor(w.currentExecutorId),await setCurrentExecutor(w.id,null)}catch{}try{if(w.paneId&&w.paneId!=="inline"){let{execSync:execSync5}=__require("child_process"),{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync5(genieTmuxCmd2(`kill-pane -t ${w.paneId}`),{stdio:"ignore"})}}catch{}await unregister(w.id)}}async function postCloneInit(repoPath,worktreePath){let parentNodeModules=join20(repoPath,"node_modules"),worktreeNodeModules=join20(worktreePath,"node_modules");if(existsSync18(parentNodeModules)&&!existsSync18(worktreeNodeModules))try{await symlink(parentNodeModules,worktreeNodeModules,"dir")}catch{}let initScript=join20(repoPath,".genie","init.sh");if(existsSync18(initScript))try{await $3`bash ${initScript}`.cwd(worktreePath).quiet()}catch{}}async function ensureWorktree(repoPath,branchName,worktreePath,baseBranch){try{await $3`git -C ${repoPath} fetch origin ${baseBranch}`.quiet()}catch{}if(await mkdir4(path2.dirname(worktreePath),{recursive:!0}),existsSync18(worktreePath))return;let branchExists=!1;try{await $3`git -C ${repoPath} rev-parse --verify ${branchName}`.quiet(),branchExists=!0}catch{if(!branchExists)try{await $3`git -C ${repoPath} branch ${branchName} origin/${baseBranch}`.quiet()}catch{try{await $3`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet()}catch{await $3`git -C ${repoPath} branch ${branchName}`.quiet()}}}await $3`git clone --shared --branch ${branchName} ${repoPath} ${worktreePath}`.quiet();try{let userName=(await $3`git -C ${repoPath} config user.name`.quiet()).text().trim(),userEmail=(await $3`git -C ${repoPath} config user.email`.quiet()).text().trim();if(userName)await $3`git -C ${worktreePath} config user.name ${userName}`.quiet();if(userEmail)await $3`git -C ${worktreePath} config user.email ${userEmail}`.quiet()}catch{}await postCloneInit(repoPath,worktreePath)}async function detectSpawnerParentTeam(newTeamName){let envTeam=process.env.GENIE_TEAM,spawnerName=process.env.GENIE_AGENT_NAME;if(!envTeam||!spawnerName)return null;if(spawnerName==="cli")return null;if(envTeam===newTeamName)return null;try{return await getTeam(envTeam)?envTeam:null}catch{return null}}async function createTeam(name,repo,baseBranch="dev"){validateBranchName(name);let repoPath=path2.resolve(repo),existing=await getTeam(name);if(existing)return existing;let worktreeBase=getWorktreeBase(repoPath),worktreePath=join20(worktreeBase,name);await ensureWorktree(repoPath,name,worktreePath,baseBranch);let now=new Date().toISOString(),config={name,repo:repoPath,baseBranch,worktreePath,members:[],status:"in_progress",createdAt:now},promoted=await detectSpawnerParentTeam(name);if(promoted)config.parentTeam=promoted;if(isInsideClaudeCode()){config.nativeTeamsEnabled=!0;try{let result2=await registerAsTeamLead(name);config.nativeTeamParentSessionId=result2.sessionId}catch{}}return await(await getConnection())`
523
523
  INSERT INTO teams (
524
524
  name, repo, base_branch, worktree_path, leader,
525
525
  members, status, native_team_parent_session_id,
526
- native_teams_enabled, tmux_session_name, wish_slug, spawner, created_at
526
+ native_teams_enabled, tmux_session_name, wish_slug, spawner, created_at,
527
+ parent_team, allow_child_reachback
527
528
  ) VALUES (
528
529
  ${config.name}, ${config.repo}, ${config.baseBranch},
529
530
  ${config.worktreePath}, ${config.leader??null},
@@ -531,7 +532,9 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
531
532
  ${config.nativeTeamParentSessionId??null},
532
533
  ${config.nativeTeamsEnabled??!1},
533
534
  ${config.tmuxSessionName??null}, ${config.wishSlug??null},
534
- ${config.spawner??null}, ${config.createdAt}
535
+ ${config.spawner??null}, ${config.createdAt},
536
+ ${config.parentTeam??null},
537
+ ${config.allowChildReachback?JSON.stringify(config.allowChildReachback):null}
535
538
  ) ON CONFLICT (name) DO NOTHING
536
539
  `,recordAuditEvent("team",name,"created",getActor(),{repo:repoPath,baseBranch}).catch(()=>{}),config}async function hireAgent(teamName,agentName){let config=await getTeam(teamName);if(!config)throw Error(`Team "${teamName}" not found.`);let added;if(agentName==="council")added=BUILTIN_COUNCIL_MEMBERS.map((m)=>m.name).filter((n)=>!config.members.includes(n)),config.members.push(...added);else{if(config.members.includes(agentName))return[];config.members.push(agentName),added=[agentName]}return await(await getConnection())`
537
540
  UPDATE teams SET members = ${JSON.stringify(config.members)}
@@ -566,7 +569,9 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
566
569
  native_teams_enabled = ${config.nativeTeamsEnabled??!1},
567
570
  tmux_session_name = ${config.tmuxSessionName??null},
568
571
  wish_slug = ${config.wishSlug??null},
569
- spawner = ${config.spawner??null}
572
+ spawner = ${config.spawner??null},
573
+ parent_team = ${config.parentTeam??null},
574
+ allow_child_reachback = ${config.allowChildReachback?JSON.stringify(config.allowChildReachback):null}
570
575
  WHERE name = ${name}
571
576
  `}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 listTeams2(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((await(await getConnection())`
572
577
  UPDATE teams SET status = ${status}
@@ -1138,14 +1143,15 @@ Your role adapts based on workspace maturity:
1138
1143
  </constraints>
1139
1144
  `,GENIE_SOUL_TEMPLATE,GENIE_HEARTBEAT_TEMPLATE;var init_templates=__esm(()=>{init_defaults();GENIE_SOUL_TEMPLATE=["# Genie Specialist \u2014 Soul","","You are the genie workspace specialist. You guide users through the genie workflow and orchestrate agents.","","## The Genie Pipeline","","Every idea follows this pipeline:","","```","brainstorm \u2192 wish \u2192 work \u2192 review \u2192 ship","```","","1. **Brainstorm** \u2014 Explore the idea. Use `/brainstorm` to think through scope, tradeoffs, and approach.","2. **Wish** \u2014 Structure the idea into an actionable plan with acceptance criteria, execution groups, and validation commands. Use `/wish` to create one.","3. **Work** \u2014 Execute the wish. Use `/work` to dispatch engineers per execution group.","4. **Review** \u2014 Validate the work against wish criteria. Use `/review` to check compliance.","5. **Ship** \u2014 Merge, release, deploy. The pipeline ensures quality before shipping.","","## Genie Commands Reference","","### Agent Management","```bash","genie spawn <name> # Start an agent","genie kill <name> # Force kill an agent","genie stop <name> # Stop (preserves session)","genie resume [name] # Resume a suspended agent","genie ls # List agents with status","genie log [agent] # Unified observability feed","genie read <name> # Read terminal output","genie history <name> # Compressed session history","genie answer <name> <choice> # Answer a prompt for an agent","```","","### Agent Communication","```bash","genie agent send '<msg>' --to <name> # Direct message","genie agent send '<msg>' --broadcast # Team broadcast","genie agent inbox # View inbox","genie agent brief --team <name> # Cold-start summary","```","","### Team Orchestration","```bash","genie team create <name> --repo <path> --wish <slug> # Launch autonomous team","genie team hire <name> --team <team> # Add to team","genie team fire <name> --team <team> # Remove from team","genie team list # List teams","genie team disband <name> # Disband team","```","","### Task & Wish Management","```bash","genie task create --title 'x' # Create task","genie task list # List tasks","genie task status <slug> # Wish group status","genie task done <ref> # Mark done","genie task board # Planning board","```","","### Workspace","```bash","genie init # Initialize workspace","genie init agent <name> # Scaffold new agent","genie serve # Start infrastructure","genie doctor # Diagnostic checks","```","","## Concierge \u2192 Orchestrator Transition","","Detect workspace maturity and adapt:","","**Concierge mode** activates when:","- Workspace has 0-1 agents (just the default genie agent)","- No wishes exist yet","- User appears new to genie","","In concierge mode:","- Explain concepts with examples","- Suggest creating a first agent or brainstorming a first wish","- Walk through the pipeline step by step","","**Orchestrator mode** activates when:","- Workspace has 2+ agents","- Wishes exist with execution groups","- User gives direct commands","","In orchestrator mode:","- Route work to the right agents","- Monitor progress across teams","- Summarize status concisely","- Suggest next pipeline steps based on current state","","## Agent Analysis Capability","","When invoked in a workspace with existing agents (from genie or other systems), analyze their setup:","","### Analysis Process","1. List all directories under `agents/` (and any discovered via tree scan)","2. For each agent directory, check:"," - Has `AGENTS.md`? (identity file with frontmatter)"," - Has `SOUL.md`? (personality and knowledge)"," - Has `HEARTBEAT.md`? (autonomous checklist)"," - Has `.claude/settings.local.json`? (Claude Code config)"," - Frontmatter fields present vs. missing","3. Compare against genie conventions:"," - Missing files \u2192 propose creation with templates"," - Incomplete frontmatter \u2192 propose mini-wizard"," - Non-standard structure \u2192 explain conventions, offer migration","4. Present proposals as a checklist \u2014 never auto-modify",""].join(`
1140
1145
  `),GENIE_HEARTBEAT_TEMPLATE=["# Heartbeat \u2014 Genie Specialist","","Run this checklist on every iteration. Exit early if nothing actionable.","","## Checklist","","### 1. Workspace State Check","Verify workspace health before doing anything else.","- Is `genie serve` running? If not, suggest starting it.","- Are there registered agents? List them with `genie ls`.","- Any agents in error/crashed state? Flag for user attention.","","### 2. Pending Agents Check","Look for agents waiting to be initialized.","- Check `.genie/pending-agents.json` for queued discoveries.","- If pending agents exist, notify the user and offer to initialize them.","- If new `AGENTS.md` files appeared outside `agents/`, flag for import.","","### 3. Wish Status Check","Review active work across the workspace.","- Check `genie task board` for in-progress wishes.","- For each active wish, check execution group progress.","- Flag blocked groups or stale tasks (no progress in 30+ minutes).","- Summarize: X wishes active, Y groups complete, Z blocked.","","### 4. Generate Suggestions","Based on workspace state, suggest the next most valuable action:",'- **Empty workspace** \u2192 "Start with /brainstorm to explore an idea"','- **Has brainstorm, no wish** \u2192 "Ready to structure this? Run /wish"','- **Has wish, no workers** \u2192 "Dispatch workers with /work"','- **Work complete** \u2192 "Time to review: /review"','- **Review passed** \u2192 "Ship it \u2014 merge the PR"','- **Agents from other systems** \u2192 "I can analyze your agents \u2014 want a compatibility report?"',"","### 5. Exit If Nothing Actionable","If workspace is healthy, no pending agents, no active wishes, and no suggestions \u2014 exit.","Don't create busywork. The user will invoke you when needed.",""].join(`
1141
- `)});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync2}from"child_process";import{createHash as createHash3,randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync24}from"fs";import{basename as basename5,join as join27}from"path";function shortPathHash(p){return createHash3("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join27(process.cwd(),"AGENTS.md");if(existsSync24(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName,leaderName,sessionId){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName,leaderName,sessionId})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID6();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0,leaderName,freshId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync2(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync7,readFileSync:readFileSync16,writeFileSync:writeFileSync13}=await import("fs"),{join:join28}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join28(process.env.HOME??"/root",".claude","teams"),teams=readdirSync7(teamsDir);for(let team of teams)try{let configPath2=join28(teamsDir,team,"config.json"),raw=readFileSync16(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync13(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(shouldResume){let continueName=sanitizeTeamName(windowName);await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,continueName,leaderName,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else{let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0,leaderName,sessionId),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join27(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync25}from"fs";import{join as join28}from"path";function getSystemPromptFile(workingDir){let agentsPath=join28(workingDir,"AGENTS.md");if(existsSync25(agentsPath))return agentsPath;return null}async function ensureSession(teamName){let current=await getCurrentSessionName();if(current)return current;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}let sessionName=sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let sessionName=await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return isPaneAlive(match.paneId)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let currentSession=await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:currentSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),{sessionId,shouldResume}=await resolveOrMintLeadSessionId(teamName,workingDir);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,...shouldResume?{continueName:sanitizeTeamName(teamName)}:{sessionId}});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`)}return{created:teamWindow.created,session,window:windowName}}var init_team_auto_spawn=__esm(()=>{init_session();init_claude_native_teams();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,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 resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}async function checkInboxes(deps=defaultDeps){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){deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}try{await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),spawned.push(teamName)}catch(err){let newCount=failures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`)}}return spawned}function startInboxWatcher(deps=defaultDeps){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 defaultDeps,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,spawnFailures;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map});var exports_msg={};__export(exports_msg,{registerSendInboxCommands:()=>registerSendInboxCommands,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile4}from"fs/promises";import{homedir as homedir19}from"os";import{join as join29}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join29(homedir19(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join29(configDir,"teams",sanitized,"config.json");try{let raw=await readFile4(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function resolveSenderTeams(teams,sender){let senderTeams=teams.filter((t)=>t.members.includes(sender));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&&!senderTeams.some((t)=>t.name===leaderTeam.name))senderTeams=[...senderTeams,leaderTeam]}}return senderTeams}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(`
1142
- ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}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)console.error(`Error: ${scopeError}`),process.exit(1);let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").addHelpText("after",`
1146
+ `)});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync2}from"child_process";import{createHash as createHash3,randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync24}from"fs";import{basename as basename5,join as join27}from"path";function shortPathHash(p){return createHash3("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join27(process.cwd(),"AGENTS.md");if(existsSync24(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName,leaderName,sessionId){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName,leaderName,sessionId})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let continueName=sanitizeTeamName(windowName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,shouldResume?continueName:void 0,leaderName,shouldResume?void 0:sessionId);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID6();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0,leaderName,freshId);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workingDir);await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync2(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync7,readFileSync:readFileSync16,writeFileSync:writeFileSync13}=await import("fs"),{join:join28}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join28(process.env.HOME??"/root",".claude","teams"),teams=readdirSync7(teamsDir);for(let team of teams)try{let configPath2=join28(teamsDir,team,"config.json"),raw=readFileSync16(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync13(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let{sessionId,shouldResume}=await resolveOrMintLeadSessionId(windowName,workspaceDir);if(shouldResume){let continueName=sanitizeTeamName(windowName);await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,continueName,leaderName,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else{let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0,leaderName,sessionId),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join27(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync25}from"fs";import{join as join28}from"path";function getSystemPromptFile(workingDir){let agentsPath=join28(workingDir,"AGENTS.md");if(existsSync25(agentsPath))return agentsPath;return null}async function ensureSession(teamName){let current=await getCurrentSessionName();if(current)return current;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}let sessionName=sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let sessionName=await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return isPaneAlive(match.paneId)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let currentSession=await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:currentSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),{sessionId,shouldResume}=await resolveOrMintLeadSessionId(teamName,workingDir);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,...shouldResume?{continueName:sanitizeTeamName(teamName)}:{sessionId}});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`)}return{created:teamWindow.created,session,window:windowName}}var init_team_auto_spawn=__esm(()=>{init_session();init_claude_native_teams();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,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 resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}async function checkInboxes(deps=defaultDeps){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){deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}try{await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),spawned.push(teamName)}catch(err){let newCount=failures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`)}}return spawned}function startInboxWatcher(deps=defaultDeps){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 defaultDeps,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,spawnFailures;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile4}from"fs/promises";import{homedir as homedir19}from"os";import{join as join29}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join29(homedir19(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join29(configDir,"teams",sanitized,"config.json");try{let raw=await readFile4(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
1147
+ ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),suggestion)console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`);else console.error("No reachable leader found \u2014 sender is not bound to any team.")}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
1143
1148
  Examples:
1144
1149
  genie send 'start task #3' --to engineer # Message a specific agent
1145
1150
  genie send 'status update' --to team-lead # Report to team lead
1146
- genie send 'deploy ready' --team my-feature # Message within team context`).action(async(body,options)=>{try{await handleSend(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts3=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),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);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let inbox2=program2.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
1151
+ genie send 'deploy ready' --team my-feature # Message within team context
1152
+ genie send 'hi felipe' --to felipe-3 --bridge # Scope violation \u2192 print relay hint instead of erroring`).action(async(body,options)=>{try{await handleSend(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts3=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),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);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let inbox2=program2.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
1147
1153
  `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},isAgentAlive:async(agentName)=>{let{isAgentAlive:isAgentAlive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isAgentAlive2(agentName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result2=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result2},warn:(msg)=>console.log(msg)}),shutdown2=()=>{console.log(`
1148
- Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _registry,_taskService,_teamManager,_mailbox;var init_msg=__esm(()=>{init_term_format()});function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile5,readdir as readdir3,stat as stat2}from"fs/promises";import{join as join30}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join30(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join30(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join30(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join30(projectDir,"sessions-index.json");try{let content=await readFile5(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir3(projectDir);for(let entry of entries)if(entry.endsWith(".jsonl")&&!entry.startsWith(".")){let filePath=join30(projectDir,entry),stats=await stat2(filePath),sessionId=entry.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats.mtimeMs,messageCount:0,created:stats.birthtime.toISOString(),modified:stats.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry.model=raw.message.model;if(raw.message.usage)entry.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry,raw);if(raw.data)entry.data=raw.data;return entry}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile5(logPath,"utf-8")).split(`
1154
+ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _registry,_taskService,_teamManager,_mailbox,PARENT_CHAIN_MAX_DEPTH=3,DEFAULT_REACHBACK_PREFIXES;var init_msg=__esm(()=>{init_term_format();DEFAULT_REACHBACK_PREFIXES=["council-"]});function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile5,readdir as readdir3,stat as stat2}from"fs/promises";import{join as join30}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join30(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join30(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join30(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join30(projectDir,"sessions-index.json");try{let content=await readFile5(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir3(projectDir);for(let entry of entries)if(entry.endsWith(".jsonl")&&!entry.startsWith(".")){let filePath=join30(projectDir,entry),stats=await stat2(filePath),sessionId=entry.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats.mtimeMs,messageCount:0,created:stats.birthtime.toISOString(),modified:stats.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry.model=raw.message.model;if(raw.message.usage)entry.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry,raw);if(raw.data)entry.data=raw.data;return entry}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile5(logPath,"utf-8")).split(`
1149
1155
  `);for(let line of lines){let entry=parseLogEntry(line);if(entry)entries.push(entry)}}catch{}return entries}async function findLogsForWorkspace(workspacePath,claudeDir){let projectDir=await findClaudeProjectDir(workspacePath,claudeDir);if(!projectDir)return null;let session=await findActiveSession(projectDir);if(!session)return null;return{projectDir,session}}async function getLogsForPane(paneWorkdir,claudeDir){let result2=await findLogsForWorkspace(paneWorkdir,claudeDir);if(!result2)return null;return{logPath:result2.session.fullPath,session:result2.session,projectDir:result2.projectDir}}function extractText(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"&&"text"in item)parts.push(String(item.text));return parts.join(" ")}function convertAssistant(entry,base){let results=[],text=entry.message?extractText(entry.message.content):"";if(text)results.push({...base,role:"assistant",timestamp:entry.timestamp,text,usage:entry.usage?{input:entry.usage.input_tokens,output:entry.usage.output_tokens}:void 0});if(entry.toolCalls)for(let tc of entry.toolCalls)results.push({...base,role:"tool_call",timestamp:entry.timestamp,text:`${tc.name}: ${JSON.stringify(tc.input).slice(0,200)}`,toolCall:{id:tc.id,name:tc.name,input:tc.input}});return results}function claudeEntryToTranscript(entry){let base={provider:"claude",model:entry.model,raw:entry.raw};if(entry.type==="user"&&entry.message){let text=extractText(entry.message.content);return text?[{...base,role:"user",timestamp:entry.timestamp,text}]:[]}if(entry.type==="assistant")return convertAssistant(entry,base);if(entry.type==="system"||entry.type==="progress"){let text=entry.message?extractText(entry.message.content):"";return text?[{...base,role:"system",timestamp:entry.timestamp,text}]:[]}return[]}var claudeTranscriptProvider;var init_claude_logs=__esm(()=>{claudeTranscriptProvider={async discoverLogPath(worker){let workspacePath=worker.worktree||worker.repoPath,projectDir=await findClaudeProjectDir(workspacePath);if(!projectDir)return null;if(worker.claudeSessionId){let directPath=join30(projectDir,`${worker.claudeSessionId}.jsonl`);try{return await access(directPath),directPath}catch{let session=await findSessionById(projectDir,worker.claudeSessionId);if(session?.fullPath)return session.fullPath}}return(await getLogsForPane(workspacePath))?.logPath??null},async readEntries(logPath){return(await readLogFile(logPath)).flatMap(claudeEntryToTranscript)}}});class AppPtyProvider{name="app-pty";transport="process";buildSpawnCommand(ctx){let params=spawnContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let{access:access2,readdir:readdir4}=await import("fs/promises"),{join:join31}=await import("path"),claudeDir=join31(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return{sessionId}}let jsonlName=`${sessionId}.jsonl`;if(executor.repoPath||executor.worktree){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),path3=executor.repoPath??executor.worktree,hash=projectPathToHash2(path3),candidate=join31(claudeDir,hash,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}try{let dirs=await readdir4(claudeDir);for(let dir of dirs){let candidate=join31(claudeDir,dir,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}}catch{}return{sessionId}}async detectState(executor){if(!executor.pid)return"terminated";try{process.kill(executor.pid,0);let metadata=executor.metadata;if(metadata.appPtyState&&typeof metadata.appPtyState==="string")return metadata.appPtyState;return"running"}catch{return"terminated"}}async terminate(executor){if(!executor.pid)return;try{process.kill(executor.pid,"SIGTERM"),await new Promise((r)=>setTimeout(r,500))}catch{}try{process.kill(executor.pid,0),process.kill(executor.pid,"SIGKILL")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
1150
1156
  SELECT a.custom_name, a.team
1151
1157
  FROM executors e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260416.6",
3
+ "version": "4.260417.2",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260416.6",
3
+ "version": "4.260417.2",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260416.6",
3
+ "version": "4.260417.2",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -64,6 +64,30 @@ Execute all phases sequentially. YOU run every command, read every output, and m
64
64
  If this fails, stop and report the error to the user. Council cannot run without a team.
65
65
  3. Record the team name -- you will need it for every subsequent command.
66
66
 
67
+ > **Cross-team reachback.** When `genie team create` runs from inside an
68
+ > existing team session (with `GENIE_TEAM` and a non-`cli` `GENIE_AGENT_NAME`
69
+ > in the environment), the new team automatically records the caller's team
70
+ > as its `parentTeam`. Because the team name starts with `council-`, the
71
+ > default reachback ALLOWLIST lets council members message the parent team's
72
+ > members directly -- no extra configuration required.
73
+ >
74
+ > To refuse cross-team reachback for a specific parent, explicitly set that
75
+ > team's `allowChildReachback = []` (empty array). To allow reachback for
76
+ > other child-team prefixes (e.g. ephemeral sprint teams), set
77
+ > `allowChildReachback = ["sprint-"]`. The chain walk is bounded to 3
78
+ > ancestors and cycle-safe.
79
+ >
80
+ > When you genuinely need to message out of scope (e.g. the parent chain is
81
+ > missing or the ALLOWLIST excludes you), use the escape hatch:
82
+ >
83
+ > ```bash
84
+ > genie send '<message>' --to <recipient> --bridge
85
+ > ```
86
+ >
87
+ > `--bridge` prints an advisory naming the nearest reachable leader plus a
88
+ > ready-to-run relay command, and exits 0 instead of failing. The leader
89
+ > can then relay the message manually.
90
+
67
91
  ### Phase 2: Spawn Members
68
92
 
69
93
  Spawn each selected member. Use the double-dash naming convention (`council--<member>`):
@@ -0,0 +1,12 @@
1
+ -- 036_teams_parent_chain.sql — Cross-team reachback via parentTeam chain.
2
+ -- Adds two nullable columns to the `teams` table:
3
+ -- * parent_team — optional parent team name (FK-less pointer for cycle tolerance)
4
+ -- * allow_child_reachback — ALLOWLIST of child-team-name prefixes that can reach back
5
+ -- Fixes the council-member isolation drift where ephemeral council-<ts> teams could not
6
+ -- reply to members of the caller's home team. Pure additive — zero breaking changes.
7
+
8
+ ALTER TABLE teams ADD COLUMN IF NOT EXISTS parent_team TEXT;
9
+ ALTER TABLE teams ADD COLUMN IF NOT EXISTS allow_child_reachback JSONB;
10
+
11
+ CREATE INDEX IF NOT EXISTS idx_teams_parent_team
12
+ ON teams(parent_team) WHERE parent_team IS NOT NULL;