@automagik/genie 4.260420.7 → 4.260420.8

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
@@ -1677,7 +1677,7 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
1677
1677
  SELECT id FROM agents WHERE id = ${id} LIMIT 1
1678
1678
  `).length===0)return slice}return uuid}async function resolveSpawnIdentity(name,team,uuidFactory=()=>crypto.randomUUID(),isAliveFn=(agent)=>resolveWorkerLivenessByTransport(agent)){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
1679
1679
  SELECT id, pane_id, team FROM agents WHERE id = ${name} LIMIT 1
1680
- `;if(rows.length===0)return{kind:"canonical",workerId:name,sessionUuid:uuidFactory()};let existing=rows[0];if(existing.team!==null&&existing.team!==team){let sessionUuid2=uuidFactory(),shortId2=await pickParallelShortId(name,team,sessionUuid2);return{kind:"parallel",workerId:`${name}-${shortId2}`,sessionUuid:sessionUuid2,canonicalId:name}}if(!(existing.pane_id?await isAliveFn({id:existing.id,paneId:existing.pane_id}):!1))return{kind:"canonical",workerId:name,sessionUuid:uuidFactory()};let sessionUuid=uuidFactory(),shortId=await pickParallelShortId(name,team,sessionUuid);return{kind:"parallel",workerId:`${name}-${shortId}`,sessionUuid,canonicalId:name}}async function resolveTeamAndResume(effectiveRole,options,agent){let teamWasExplicit=Boolean(options.team),team=await resolveTeamName3({explicitTeam:options.team,entryTeam:agent.entry?.team});if(!team){let candidates=await findTeamsContainingAgent(effectiveRole);if(candidates.length===1)team=candidates[0];else if(candidates.length>1)return console.error(`Error: agent "${effectiveRole}" is a member of multiple teams (${candidates.join(", ")}). Pass --team <name> to disambiguate.`),process.exit(1)}if(!team){if(await get2(effectiveRole))team=sanitizeTeamName(effectiveRole)}if(!team)return console.error(`Error: --team is required for agent "${effectiveRole}" (or set GENIE_TEAM, run inside a genie session, or register the agent in a team config).`),process.exit(1);let deadResumable=await findDeadResumable(team,effectiveRole);if(deadResumable)return console.log(`Resuming existing session for "${effectiveRole}" (session: ${deadResumable.claudeSessionId?.slice(0,8)}...)`),await resumeAgent(deadResumable),{team,teamWasExplicit,resumed:deadResumable.id};return{team,teamWasExplicit}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),{team,teamWasExplicit,resumed}=await resolveTeamAndResume(effectiveRole,options,agent);if(resumed)return resumed;let explicitRole=options.role!==void 0&&options.role!==name,identity=null;if(!explicitRole)identity=await resolveSpawnIdentity(name,team),effectiveRole=identity.workerId;else await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam(team);if(teamConfig?.worktreePath&&!agent.entry?.dir)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams2(effectiveRole,team,options,agent,identity?.sessionUuid);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let nt=params.nativeTeam,agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=identity?.workerId??await generateWorkerId2(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX||options.session),now=new Date().toISOString(),otelRelayActive=await maybeStartOtelRelay(nt,validated,insideTmux),fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&!process.env.GENIE_TEAM&&insideTmux&&!options.session,sessionOverride:options.session,autoResume:options.autoResume,agentIdentityId:agentIdentity.id,executorId};return recordAuditEvent("worker",workerId,"spawn",getActor(),{name,team:validated.team,provider:validated.provider}).catch(()=>{}),await dispatchSpawn(ctx,validated,options,agent,insideTmux)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync9}=__require("child_process"),currentPane=execSync9(genieTmuxCmd("display-message -p '#{pane_id}'"),{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync9(genieTmuxCmd(`kill-pane -t ${w.paneId}`),{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join35}=__require("path"),{homedir:homedir23}=__require("os"),{unlinkSync:unlinkSync9}=__require("fs"),relayDir=join35(homedir23(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync9(join35(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie agent list` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`),recordAuditEvent("worker",w.id,"kill",getActor(),{name}).catch(()=>{})}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.id)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`),recordAuditEvent("worker",w.id,"stop",getActor(),{name}).catch(()=>{})}else console.error(`Failed to stop agent "${w.id}".`),process.exit(1)}async function isResumeEligible(w){if(!w.claudeSessionId)return!1;if(w.state==="done")return!1;let paneAlive=await isPaneAlive(w.paneId);if((w.state==="suspended"||w.state==="error")&&!paneAlive)return!0;if(!paneAlive&&(w.state==="working"||w.state==="idle"||w.state==="spawning"))return!0;return!1}async function resumeAllAgents(opts={}){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w,opts)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){let resumeOpts={resetAttempts:!options.noResetAttempts};if(options.all)return resumeAllAgents(resumeOpts);if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w,resumeOpts)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
1680
+ `;if(rows.length===0)return{kind:"canonical",workerId:name,sessionUuid:uuidFactory()};let existing=rows[0];if(existing.team!==null&&existing.team!==team){let sessionUuid2=uuidFactory(),shortId2=await pickParallelShortId(name,team,sessionUuid2);return{kind:"parallel",workerId:`${name}-${shortId2}`,sessionUuid:sessionUuid2,canonicalId:name}}if(!(existing.pane_id?await isAliveFn({id:existing.id,paneId:existing.pane_id}):!1))return{kind:"canonical",workerId:name,sessionUuid:uuidFactory()};let sessionUuid=uuidFactory(),shortId=await pickParallelShortId(name,team,sessionUuid);return{kind:"parallel",workerId:`${name}-${shortId}`,sessionUuid,canonicalId:name}}async function resolveTeamAndResume(effectiveRole,options,agent){let teamWasExplicit=Boolean(options.team),team=await resolveTeamName3({explicitTeam:options.team,entryTeam:agent.entry?.team});if(!team){let candidates=await findTeamsContainingAgent(effectiveRole);if(candidates.length===1)team=candidates[0];else if(candidates.length>1)return console.error(`Error: agent "${effectiveRole}" is a member of multiple teams (${candidates.join(", ")}). Pass --team <name> to disambiguate.`),process.exit(1)}if(!team){if(await get2(effectiveRole))team=sanitizeTeamName(effectiveRole)}if(!team)return console.error(`Error: --team is required for agent "${effectiveRole}" (or set GENIE_TEAM, run inside a genie session, or register the agent in a team config).`),process.exit(1);let deadResumable=await findDeadResumable(team,effectiveRole);if(deadResumable)return console.log(`Resuming existing session for "${effectiveRole}" (session: ${deadResumable.claudeSessionId?.slice(0,8)}...)`),await resumeAgent(deadResumable),{team,teamWasExplicit,resumed:deadResumable.id};return{team,teamWasExplicit}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),{team,teamWasExplicit,resumed}=await resolveTeamAndResume(effectiveRole,options,agent);if(resumed)return resumed;let explicitRole=options.role!==void 0&&options.role!==name,identity=null;if(!explicitRole)identity=await resolveSpawnIdentity(name,team),effectiveRole=identity.workerId;else await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam(team);if(teamConfig?.worktreePath&&!agent.entry?.dir)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams2(effectiveRole,team,options,agent,identity?.sessionUuid);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let nt=params.nativeTeam,agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=identity?.workerId??await generateWorkerId2(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX||options.session),now=new Date().toISOString(),otelRelayActive=await maybeStartOtelRelay(nt,validated,insideTmux),fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&!process.env.GENIE_TEAM&&insideTmux&&!options.session,sessionOverride:options.session,autoResume:options.autoResume,agentIdentityId:agentIdentity.id,executorId};return recordAuditEvent("worker",workerId,"spawn",getActor(),{name,team:validated.team,provider:validated.provider}).catch(()=>{}),await dispatchSpawn(ctx,validated,options,agent,insideTmux)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync9}=__require("child_process"),currentPane=execSync9(genieTmuxCmd("display-message -p '#{pane_id}'"),{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync9(genieTmuxCmd(`kill-pane -t ${w.paneId}`),{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join35}=__require("path"),{homedir:homedir23}=__require("os"),{unlinkSync:unlinkSync9}=__require("fs"),relayDir=join35(homedir23(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync9(join35(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie agent list` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`),recordAuditEvent("worker",w.id,"kill",getActor(),{name}).catch(()=>{})}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}if(!w.currentExecutorId)console.error(`Cannot stop agent "${w.id}" \u2014 no active executor linked.`),console.error(" The agent may have already exited, or was spawned without"),console.error(" executor tracking (e.g. native Claude Code teammate)."),console.error(` To remove the agent row, use: genie kill ${w.id}`),process.exit(1);let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.currentExecutorId)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`),recordAuditEvent("worker",w.id,"stop",getActor(),{name}).catch(()=>{})}else console.error(`Failed to stop agent "${w.id}" \u2014 executor ${w.currentExecutorId} not found in executors table.`),console.error(" This indicates a stale current_executor_id FK. Try:"),console.error(` genie kill ${w.id} # force remove the agent row`),process.exit(1)}async function isResumeEligible(w){if(!w.claudeSessionId)return!1;if(w.state==="done")return!1;let paneAlive=await isPaneAlive(w.paneId);if((w.state==="suspended"||w.state==="error")&&!paneAlive)return!0;if(!paneAlive&&(w.state==="working"||w.state==="idle"||w.state==="spawning"))return!0;return!1}async function resumeAllAgents(opts={}){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w,opts)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){let resumeOpts={resetAttempts:!options.noResetAttempts};if(options.all)return resumeAllAgents(resumeOpts);if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w,resumeOpts)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
1681
1681
  `)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){if(!agent.claudeSessionId)throw new MissingResumeSessionError(agent.id);let params=await buildResumeParams(agent,template,agent.claudeSessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:agent.claudeSessionId??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id));if(resetAttempts)await update(agent.id,{resumeAttempts:0});let params=await buildFullResumeParams(agent,template),resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentIdentity=await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId:agentIdentity.id,executorId},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:agent.claudeSessionId,team:agent.team}).catch(()=>{}),console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${agent.claudeSessionId}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAlive(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.role||w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running?running.state:"offline",team:running?.team||"-",model:entry2.model||"-",resumeAttempts:running?.resumeAttempts,maxResumeAttempts:running?.maxResumeAttempts,autoResume:running?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var UUID_REGEX;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_ensure_tmux();init_executor_registry();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile10,readdir as readdir4}from"fs/promises";import{homedir as homedir23}from"os";import{join as join35}from"path";function getCodexDir(){return join35(homedir23(),".codex")}function getSessionsDir(){return join35(getCodexDir(),"sessions")}function getStateDbPath(){return join35(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir4(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join35(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join35(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join35(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir4(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join35(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile10(filePath,"utf-8"),nlIdx=content.indexOf(`
1682
1682
  `),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry2=JSON.parse(firstLine);if(entry2.type==="session_meta"&&entry2.payload?.cwd)return{cwd:entry2.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query2=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query2.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query:query2}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile10(logPath,"utf-8")}catch{return[]}return content.split(`
1683
1683
  `).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result2=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result2=result2.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260420.7",
3
+ "version": "4.260420.8",
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.260420.7",
3
+ "version": "4.260420.8",
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.260420.7",
3
+ "version": "4.260420.8",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -133,3 +133,15 @@ bind -n DoubleClick1Status choose-tree -s
133
133
  bind -n DoubleClick1StatusDefault choose-tree -s
134
134
  bind -n DoubleClick1StatusLeft choose-tree -s
135
135
  bind -n DoubleClick1StatusRight choose-tree -s
136
+
137
+ # Disable tmux 3.3+ default right-click context menus (issue #1153).
138
+ # Agent panes are shells, but the menus still confuse the "tmux is invisible"
139
+ # mental model. Keeping the agent server in lockstep with the TUI server.
140
+ unbind -n MouseDown3Pane
141
+ unbind -n MouseDown3Status
142
+ unbind -n MouseDown3StatusLeft
143
+ unbind -n MouseDown3StatusRight
144
+ unbind -n MouseDown3StatusDefault
145
+ unbind -n M-MouseDown3Pane
146
+ unbind -n M-MouseDown3Status
147
+ unbind -n M-MouseDown3StatusLeft
@@ -43,3 +43,15 @@ set -g pane-active-border-style "fg=#7b2ff7"
43
43
  # TUI keybindings are set in the root table by serve.ts
44
44
  set -g prefix None
45
45
  unbind C-b
46
+
47
+ # Disable tmux 3.3+ default right-click context menus (issue #1153).
48
+ # The TUI renders its own UI — display-menu overlays corrupt that render
49
+ # and aren't redrawn when dismissed, leaving stale glyphs behind.
50
+ unbind -n MouseDown3Pane
51
+ unbind -n MouseDown3Status
52
+ unbind -n MouseDown3StatusLeft
53
+ unbind -n MouseDown3StatusRight
54
+ unbind -n MouseDown3StatusDefault
55
+ unbind -n M-MouseDown3Pane
56
+ unbind -n M-MouseDown3Status
57
+ unbind -n M-MouseDown3StatusLeft