@automagik/genie 4.260419.2 → 4.260419.3
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
|
@@ -1601,7 +1601,7 @@ process.on('SIGTERM', () => { server.close(); process.exit(0); });
|
|
|
1601
1601
|
process.on('SIGINT', () => { server.close(); process.exit(0); });
|
|
1602
1602
|
`,{mode:420});let{spawn:spawnChild}=__require("child_process");spawnChild("node",[scriptFile],{detached:!0,stdio:"ignore"}).unref();for(let i2=0;i2<30;i2++)if(await new Promise((r)=>setTimeout(r,100)),isRelayAlive(pidFile))return!0;return!1}catch{return!1}}async function generateWorkerId2(team,role){let base=role?`${team}-${role}`:team;if(!(await list()).some((w)=>w.id===base))return base;let suffix=crypto.randomUUID().slice(0,8);return`${base}-${suffix}`}async function capturePanePid2(paneId){if(paneId==="inline")return null;try{let{execSync:execSync9}=__require("child_process"),output=execSync9(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),pid=Number.parseInt(output,10);return pid>0?pid:null}catch{return null}}function resolveExecutorTransport2(provider,spawnTransport){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return spawnTransport==="inline"?"process":"tmux"}async function terminateActiveExecutorWithCleanup(agentIdentityId){try{let currentExec=await getCurrentExecutor(agentIdentityId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let provider=getProvider(currentExec.provider);if(provider)try{await provider.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentityId)}catch{}}async function createAndLinkExecutor2(agentIdentityId,provider,transport,opts){try{let executor=await createExecutor(agentIdentityId,provider,transport,opts);return await setCurrentExecutor(agentIdentityId,executor.id),executor.id}catch{return null}}async function registerSpawnWorker(ctx,paneId,windowInfo){let nt=ctx.validated.nativeTeam,workerEntry={id:ctx.workerId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role,skill:ctx.validated.skill,team:ctx.validated.team,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,claudeSessionId:ctx.claudeSessionId,nativeTeamEnabled:nt?.enabled??!1,nativeAgentId:`${ctx.agentName}@${ctx.validated.team}`,nativeColor:nt?.color??ctx.spawnColor,parentSessionId:nt?.parentSessionId??ctx.parentSessionId,window:windowInfo?.windowName,windowName:windowInfo?.windowName,windowId:windowInfo?.windowId,autoResume:ctx.autoResume===!1?!1:void 0,resumeAttempts:0};await register(workerEntry);let role=ctx.validated.role??ctx.agentName;if(role!=="council")try{await hireAgent(ctx.validated.team,role)}catch{}return workerEntry}async function notifySpawnJoin(ctx,paneId){let nt=ctx.validated.nativeTeam;if(!nt?.enabled)return;await registerNativeMember(ctx.validated.team,{agentName:ctx.agentName,agentType:nt.agentType??ctx.validated.role??"general-purpose",color:nt.color??ctx.spawnColor??"blue",tmuxPaneId:paneId,cwd:ctx.cwd,planModeRequired:nt.planModeRequired});let leaderName=await resolveTeamLeaderName(ctx.validated.team);await writeNativeInbox(ctx.validated.team,leaderName,{from:ctx.agentName,text:`Worker ${ctx.agentName} (${ctx.validated.provider}) joined team ${ctx.validated.team}. cwd: ${ctx.cwd}. Ready for tasks.`,summary:`${ctx.agentName} (${ctx.validated.provider}) joined`,timestamp:new Date().toISOString(),color:nt.color??ctx.spawnColor??"blue",read:!1})}function registerOtelRelayPane(workerId,paneId,agentName,spawnColor,repoPath){let{writeFileSync:wfs}=__require("fs"),{join:pjoin}=__require("path"),{homedir:hdir}=__require("os"),rd=pjoin(hdir(),".genie","relay");wfs(pjoin(rd,`${workerId}-pane`),paneId),wfs(pjoin(rd,`${workerId}-meta`),JSON.stringify({agent:agentName,color:spawnColor,repoPath}))}function printSpawnInfo(ctx,paneId,workerEntry){let nt=ctx.validated.nativeTeam;if(console.log(`Agent "${ctx.workerId}" spawned.`),console.log(` Provider: ${ctx.launch.provider}`),console.log(` Command: ${ctx.fullCommand}`),console.log(` Team: ${ctx.validated.team}`),console.log(` Pane: ${paneId}`),ctx.validated.role)console.log(` Role: ${ctx.validated.role}`);if(ctx.executorId)console.log(` Executor: ${ctx.executorId}`);if(ctx.validated.skill)console.log(` Skill: ${ctx.validated.skill}`);if(workerEntry.claudeSessionId)console.log(` Session: ${workerEntry.claudeSessionId}`);if(console.log(` Layout: ${ctx.layoutMode}`),nt?.enabled)console.log(" Native: enabled"),console.log(` AgentID: ${workerEntry.nativeAgentId}`),console.log(` Color: ${nt.color}`);if(ctx.otelRelayActive)console.log(` OTel: relay on port ${OTEL_RELAY_PORT}`)}function shellQuote2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function writeTmuxLaunchScript(workerId,fullCommand){let{chmodSync:chmodSync3,mkdirSync:mkdirSync10,writeFileSync:writeFileSync12}=__require("fs"),{join:join33}=__require("path"),{homedir:homedir20}=__require("os"),dir=join33(homedir20(),".genie","spawn-scripts");mkdirSync10(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join33(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync12(scriptPath,`#!/bin/sh
|
|
1603
1603
|
exec ${fullCommand}
|
|
1604
|
-
`,{mode:448}),chmodSync3(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync9}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync9(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync9(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync9}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(ctx.validated.windowTarget){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",cmd=`${tmuxPrefix}split-window -d -t ${shellQuote2(ctx.validated.windowTarget)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync9(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.windowId?.split(":")[0]??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync9(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync9(`${tmuxPrefix}new-session -d -s ${shellQuote2(session)} -n home${cwdFlag2}`,{stdio:"ignore"});let cmd=`${tmuxPrefix}new-window -a -d -t ${shellQuote2(`${session}:`)} -n claude${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync9(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync9(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync9(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'
|
|
1604
|
+
`,{mode:448}),chmodSync3(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync9}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync9(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync9(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync9}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(ctx.validated.windowTarget){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",cmd=`${tmuxPrefix}split-window -d -t ${shellQuote2(ctx.validated.windowTarget)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync9(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.windowId?.split(":")[0]??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync9(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync9(`${tmuxPrefix}new-session -d -s ${shellQuote2(session)} -n home${cwdFlag2}`,{stdio:"ignore"});let cmd=`${tmuxPrefix}new-window -a -d -t ${shellQuote2(`${session}:`)} -n claude${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync9(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync9(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync9(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let callerPane=process.env.TMUX_PANE;if(!teamWindow&&!callerPane)throw Error("createTmuxPane: refusing to split with no target \u2014 neither teamWindow nor TMUX_PANE is set. "+"This indicates a missing --team or --window flag, or a caller outside tmux. See ~/.genie/reports/trace-genie-spawn-wrong-window.md");let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:`-t '${callerPane}'`,cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"";if(useLaunchScript){let splitCmd2=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync9(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync9(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync9}=__require("child_process"),session=await getCurrentSessionName()??ctx.validated.team,layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{execSync9(genieTmuxCmd(buildLayoutCommand(layoutTarget,ctx.layoutMode)),{stdio:"ignore"})}catch{}}async function createTmuxExecutor(ctx,paneId,pid,teamWindow){if(!ctx.agentIdentityId||!ctx.executorId)return;await createAndLinkExecutor2(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport2(ctx.validated.provider,"tmux"),{id:ctx.executorId,pid,tmuxSession:ctx.validated.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd,paneColor:ctx.spawnColor})}async function finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry){if(ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(await saveTemplate({id:ctx.validated.role??ctx.workerId,provider:ctx.validated.provider,team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,extraArgs:ctx.extraArgs,nativeTeamEnabled:workerEntry.nativeTeamEnabled,lastSpawnedAt:new Date().toISOString()}),ctx.otelRelayActive&&paneId!=="%0")registerOtelRelayPane(ctx.workerId,paneId,ctx.agentName,ctx.spawnColor,ctx.cwd);if(teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`);printSpawnInfo(ctx,paneId,workerEntry)}async function awaitAgentReadiness(paneId){if(paneId==="inline")return;let result2=await waitForAgentReady(paneId);if(result2.ready)console.log(` \u2713 Agent ready (${(result2.elapsedMs/1000).toFixed(1)}s)`);else console.log(` \u26A0 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 proceeding anyway`)}async function launchTmuxSpawn(ctx){let isolatedSessionSpawn=ctx.validated.newWindow===!0&&Boolean(ctx.sessionOverride),teamWindow=ctx.spawnIntoCurrentWindow||isolatedSessionSpawn?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){return console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}let pid=await capturePanePid2(paneId);if(await createTmuxExecutor(ctx,paneId,pid,teamWindow),await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);if(await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry),await awaitAgentReadiness(paneId),ctx.executorId)await updateExecutorState(ctx.executorId,"running").catch(()=>{});return await update(ctx.workerId,{state:"idle"}).catch(()=>{}),paneId}async function runSdkQuery(ctx,permConfig,streamOpts,sdkConfig,runtimeExtraOptions){let{ClaudeSdkProvider:ClaudeSdkProvider2}=await Promise.resolve().then(() => (init_claude_sdk(),exports_claude_sdk)),{startSession:startSession2,recordTurn:recordTurn2,updateTurnCount:updateTurnCount2,endSession:endSession2}=await Promise.resolve().then(() => exports_sdk_session_capture),{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sdkProvider=new ClaudeSdkProvider2,spawnContext={agentId:ctx.agentIdentityId??ctx.workerId,executorId:ctx.executorId??crypto.randomUUID(),team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,model:ctx.validated.model,systemPrompt:ctx.validated.systemPrompt,systemPromptFile:ctx.validated.systemPromptFile,initialPrompt:ctx.validated.initialPrompt,name:ctx.validated.name},safePgCall=async(_op,fn,fallback)=>{try{let sql=await getConnection2();return await fn(sql)}catch{return fallback}},prompt2=ctx.validated.initialPrompt??`You are ${ctx.validated.role??"an agent"} on team "${ctx.validated.team}". Awaiting instructions.`,resumeSessionId=typeof runtimeExtraOptions?.resume==="string"?runtimeExtraOptions.resume:void 0,dbSessionId=null,turnIndex=0;if(resumeSessionId){let resolvedClaudeSessionId=resumeSessionId,byPgId=await safePgCall("resolve-session-resume",(sql)=>sql`
|
|
1605
1605
|
SELECT s.id, s.total_turns, COALESCE(s.claude_session_id, e.claude_session_id) as csid
|
|
1606
1606
|
FROM sessions s
|
|
1607
1607
|
LEFT JOIN executors e ON e.id = s.executor_id
|
|
@@ -1617,7 +1617,7 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
|
|
|
1617
1617
|
SELECT id FROM agents WHERE id = ${id} LIMIT 1
|
|
1618
1618
|
`).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())`
|
|
1619
1619
|
SELECT id, pane_id, team FROM agents WHERE id = ${name} LIMIT 1
|
|
1620
|
-
`;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 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),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID(),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&&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:join33}=__require("path"),{homedir:homedir20}=__require("os"),{unlinkSync:unlinkSync8}=__require("fs"),relayDir=join33(homedir20(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync8(join33(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(`
|
|
1620
|
+
`;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 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),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID(),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:join33}=__require("path"),{homedir:homedir20}=__require("os"),{unlinkSync:unlinkSync8}=__require("fs"),relayDir=join33(homedir20(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync8(join33(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(`
|
|
1621
1621
|
`)}}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,agentIdentity=await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role);await terminateActiveExecutorWithCleanup(agentIdentity.id);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentIdentity.id,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{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),validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(await 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 entry of dirEntries){let running=statusMap.get(entry.name);entries.push({name:entry.name,dir:entry.dir||"-",status:running?running.state:"offline",team:running?.team||"-",model:entry.model||"-",resumeAttempts:running?.resumeAttempts,maxResumeAttempts:running?.maxResumeAttempts,autoResume:running?.autoResume}),statusMap.delete(entry.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(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_registry();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 homedir20}from"os";import{join as join33}from"path";function getCodexDir(){return join33(homedir20(),".codex")}function getSessionsDir(){return join33(getCodexDir(),"sessions")}function getStateDbPath(){return join33(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(join33(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(join33(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(join33(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=join33(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(`
|
|
1622
1622
|
`),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry=JSON.parse(firstLine);if(entry.type==="session_meta"&&entry.payload?.cwd)return{cwd:entry.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(`
|
|
1623
1623
|
`).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(`
|
|
@@ -3329,14 +3329,14 @@ Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entrie
|
|
|
3329
3329
|
|
|
3330
3330
|
When done:
|
|
3331
3331
|
1. Run: genie done ${slug}#${group}
|
|
3332
|
-
2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",effectiveRole,workPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result2.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref);validateSlug(slug);let wishPath=join50(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync41(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile12(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
|
|
3332
|
+
2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM,role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",effectiveRole,workPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result2.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref);validateSlug(slug);let wishPath=join50(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync41(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile12(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
|
|
3333
3333
|
${diff}
|
|
3334
3334
|
\`\`\``:"(no uncommitted changes found \u2014 review committed changes)"].join(`
|
|
3335
3335
|
`),enrichedReviewContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedReviewContext=enrichContext2({query:`review ${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:reviewContent,wishContext,command:`review ${ref}`,skill:"review",enrichedContext:enrichedReviewContext}),contextFile=await writeContextFile(context);if(console.log(`\uD83D\uDD0D Dispatching review to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`),diff)console.log(` Diff: ${diff.split(`
|
|
3336
3336
|
`).length} lines`);let reviewLeaderTarget=await resolveLeaderTarget(),reviewPrompt=`Review "${ref}". Your context and diff are in the system prompt. Evaluate against acceptance criteria and return SHIP, FIX-FIRST, or BLOCKED with severity-tagged findings.
|
|
3337
3337
|
|
|
3338
3338
|
When done, report your verdict:
|
|
3339
|
-
Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:reviewPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",agentName,reviewPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result2.reason??"unknown"}`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_export_format();import{existsSync as existsSync42,mkdirSync as mkdirSync18,writeFileSync as writeFileSync20}from"fs";import{dirname as dirname10}from"path";async function getSql(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getVersion(){let{VERSION:VERSION2}=await Promise.resolve().then(() => (init_version(),exports_version));return VERSION2}async function getActorName(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}function outputDocument(doc,options){let json2=options.pretty?JSON.stringify(doc,null,2):JSON.stringify(doc);if(options.output){let dir=dirname10(options.output);if(!existsSync42(dir))mkdirSync18(dir,{recursive:!0});writeFileSync20(options.output,`${json2}
|
|
3339
|
+
Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:reviewPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",agentName,reviewPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result2.reason??"unknown"}`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_export_format();import{existsSync as existsSync42,mkdirSync as mkdirSync18,writeFileSync as writeFileSync20}from"fs";import{dirname as dirname10}from"path";async function getSql(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getVersion(){let{VERSION:VERSION2}=await Promise.resolve().then(() => (init_version(),exports_version));return VERSION2}async function getActorName(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}function outputDocument(doc,options){let json2=options.pretty?JSON.stringify(doc,null,2):JSON.stringify(doc);if(options.output){let dir=dirname10(options.output);if(!existsSync42(dir))mkdirSync18(dir,{recursive:!0});writeFileSync20(options.output,`${json2}
|
|
3340
3340
|
`);let tables=Object.keys(doc.data),rows=Object.values(doc.data).reduce((sum,arr)=>sum+arr.length,0);if(console.log(`Exported ${tables.length} tables (${rows} rows) to ${options.output}`),doc.skippedTables.length>0)console.log(`Skipped tables (not found): ${doc.skippedTables.join(", ")}`)}else console.log(json2)}function autoOutputName(){let d=new Date;return`genie-backup-${`${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}`}.json`}async function exportGroup(sql,group,filter){let tables=GROUP_TABLES[group],{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(filter)data[table]=[...await sql.unsafe(`SELECT * FROM ${table} WHERE ${filter.column} = $1`,[filter.value])];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}async function exportBoards(sql,name){let tables=GROUP_TABLES.boards,{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(name&&table==="boards")data[table]=[...await sql`SELECT * FROM boards WHERE name = ${name}`];else if(table==="task_types")data[table]=[...await sql`SELECT * FROM task_types WHERE is_builtin = false`];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}var TASK_JOIN_ALIASES={task_tags:"tt",task_actors:"ta",task_dependencies:"td",task_stage_log:"tsl"};async function resolveProjectId2(sql,projectName){let projects=await sql`SELECT id FROM projects WHERE name = ${projectName}`;if(projects.length===0)throw Error(`Project not found: ${projectName}`);return projects[0].id}function stripEphemeralFields(rows){return rows.map((r)=>{let{checkout_run_id,execution_locked_at,session_id,pane_id,...rest}=r;return rest})}async function exportTaskTable(sql,table,projectId){let alias=TASK_JOIN_ALIASES[table];if(table==="tasks"){let rows=projectId?[...await sql.unsafe("SELECT * FROM tasks WHERE project_id = $1",[projectId])]:[...await sql`SELECT * FROM tasks`];return stripEphemeralFields(rows)}if(alias&&projectId)return[...await sql.unsafe(`SELECT ${alias}.* FROM ${table} ${alias} JOIN tasks t ON ${alias}.task_id = t.id WHERE t.project_id = $1`,[projectId])];return[...await sql.unsafe(`SELECT * FROM ${table}`)]}async function exportTasks(sql,projectName){let tables=GROUP_TABLES.tasks,{available,skipped}=await detectTables(sql,tables),data={},projectId=projectName?await resolveProjectId2(sql,projectName):null;for(let table of available)data[table]=await exportTaskTable(sql,table,projectId);return{data,skipped}}async function exportSchedules(sql,name){let{available,skipped}=await detectTables(sql,["schedules"]),data={};if(available.includes("schedules"))if(name)data.schedules=[...await sql`SELECT * FROM schedules WHERE name = ${name}`];else data.schedules=[...await sql`SELECT * FROM schedules`];return{data,skipped}}async function exportTags(sql){let{available,skipped}=await detectTables(sql,["tags"]),data={};if(available.includes("tags"))data.tags=[...await sql`SELECT * FROM tags WHERE name NOT LIKE 'test-%'`];return{data,skipped}}async function exportAll(sql){let allSkipped=[],allData={};for(let group of ALL_GROUPS){let result2;switch(group){case"boards":result2=await exportBoards(sql);break;case"tasks":result2=await exportTasks(sql);break;case"tags":result2=await exportTags(sql);break;case"schedules":result2=await exportSchedules(sql);break;default:result2=await exportGroup(sql,group);break}Object.assign(allData,result2.data),allSkipped.push(...result2.skipped)}return{data:allData,skipped:allSkipped}}async function runExport(groups,type2,exportFn,options){let sql=await getSql(),[version,actor]=await Promise.all([getVersion(),getActorName()]),doc=createExportDocument(type2,groups,version,actor),{data,skipped}=await exportFn(sql);doc.data=data,doc.skippedTables=skipped,outputDocument(doc,options)}function registerExportCommands(program2){let exp=program2.command("export").description("Export genie data as JSON").option("--output <file>","Write to file instead of stdout").option("-o <file>","Alias for --output").option("--pretty","Pretty-print JSON").action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts=(cmd)=>cmd.option("--output <file>","Write to file instead of stdout").option("--pretty","Pretty-print JSON");sharedOpts(exp.command("all").description("Full backup (all present tables)")).action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("boards [name]").description("Export boards, templates, and task types")).action(async(name,options)=>{try{await runExport(["boards"],"partial",(sql)=>exportBoards(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tasks").description("Export tasks with deps, actors, and stage log").option("--project <name>","Filter by project name")).action(async(options)=>{try{await runExport(["tasks"],"partial",(sql)=>exportTasks(sql,options.project),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tags").description("Export tags")).action(async(options)=>{try{await runExport(["tags"],"partial",(sql)=>exportTags(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("projects").description("Export projects")).action(async(options)=>{try{await runExport(["projects"],"partial",(sql)=>exportGroup(sql,"projects"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("schedules [name]").description("Export schedules with run_spec")).action(async(name,options)=>{try{await runExport(["schedules"],"partial",(sql)=>exportSchedules(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("agents").description("Export agents, templates, and checkpoints")).action(async(options)=>{try{await runExport(["agents"],"partial",(sql)=>exportGroup(sql,"agents"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("comms").description("Export conversations, messages, mailbox")).action(async(options)=>{try{await runExport(["comms"],"partial",(sql)=>exportGroup(sql,"comms"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("config").description("Export OS config (graceful skip if missing)")).action(async(options)=>{try{await runExport(["config"],"partial",(sql)=>exportGroup(sql,"config"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_history();init_export_format();import{readFileSync as readFileSync26}from"fs";var IMPORT_LEVELS=[["schedules","sessions","projects","agent_templates","agent_checkpoints","tags","task_types","notification_preferences","os_config","golden_images","warm_pool","instances"],["triggers","boards","board_templates","agents","conversations"],["tasks","runs","messages","conversation_members","mailbox","team_chat"],["task_tags","task_actors","task_dependencies","task_stage_log","heartbeats","machine_snapshots"]],SELF_REFERENTIAL_COLUMNS={tasks:"parent_id",messages:"reply_to_id",conversations:"parent_message_id"};function getTableLevel(table){for(let i2=0;i2<IMPORT_LEVELS.length;i2++)if(IMPORT_LEVELS[i2].includes(table))return i2;return-1}function sortByImportOrder(tables){return[...tables].sort((a,b2)=>{let la=getTableLevel(a),lb=getTableLevel(b2);return(la===-1?999:la)-(lb===-1?999:lb)})}function getPrimaryKey(table){return{task_tags:["task_id","tag_id"],task_actors:["task_id","actor_type","actor_id","role"],task_dependencies:["task_id","depends_on_id"],conversation_members:["conversation_id","actor_type","actor_id"],notification_preferences:["actor_type","actor_id","channel"]}[table]??["id"]}var VALID_TABLES=new Set(Object.values(GROUP_TABLES).flat());function assertValidTable(name){if(!VALID_TABLES.has(name))throw Error(`Invalid table name: "${name}" is not in the schema whitelist`)}var VALID_COLUMN_RE=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function assertValidColumnName(name){if(!VALID_COLUMN_RE.test(name))throw Error(`Invalid column name: "${name.slice(0,60)}" contains disallowed characters. Column names must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`)}async function getSql2(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getActorName2(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables2(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}async function detectConflicts(sql,table,rows){if(rows.length===0)return[];assertValidTable(table);let pk=getPrimaryKey(table);if(pk.length===1){let key=pk[0],ids=rows.map((r)=>r[key]),existing=await sql.unsafe(`SELECT ${key} FROM ${table} WHERE ${key} = ANY($1)`,[ids]),existingSet=new Set(existing.map((r)=>String(r[key])));return rows.filter((r)=>existingSet.has(String(r[key])))}let conflicts=[];for(let row of rows){let conditions=pk.map((col,i2)=>`${col} = $${i2+1}`).join(" AND "),values2=pk.map((col)=>row[col]);if((await sql.unsafe(`SELECT 1 FROM ${table} WHERE ${conditions} LIMIT 1`,values2)).length>0)conflicts.push(row)}return conflicts}function prepareRow(row,table,selfRefUpdates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],entries=Object.entries(row),columns=entries.map(([k])=>k),values2=entries.map(([,v])=>v);for(let col of columns)assertValidColumnName(col);if(selfRefCol&&row[selfRefCol]!=null){let idx=columns.indexOf(selfRefCol);if(idx!==-1){let originalSelfRef=values2[idx];values2[idx]=null;let pk=getPrimaryKey(table);selfRefUpdates.push({pk:pk.length===1?row[pk[0]]:pk.map((k)=>row[k]),value:originalSelfRef})}}return{columns,values:values2,quotedCols:columns.map((c)=>`"${c}"`).join(", "),placeholders:values2.map((_,i2)=>`$${i2+1}`).join(", ")}}async function insertOneRow(tx,table,row,prepared,mode){assertValidTable(table);let{quotedCols,placeholders,values:values2}=prepared,pk=getPrimaryKey(table);if(mode==="overwrite"){let pkCondition=pk.map((col,i2)=>`"${col}" = $${values2.length+i2+1}`).join(" AND "),pkValues=pk.map((col)=>row[col]);await tx.unsafe(`DELETE FROM ${table} WHERE ${pkCondition}`,pkValues),await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}else if(mode==="merge"){let onConflict=pk.map((c)=>`"${c}"`).join(", ");await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders}) ON CONFLICT (${onConflict}) DO NOTHING`,values2)}else await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}async function updateSelfRefs(tx,table,updates){assertValidTable(table);let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],pk=getPrimaryKey(table);if(pk.length!==1)return;for(let{pk:pkVal,value}of updates)await tx.unsafe(`UPDATE ${table} SET "${selfRefCol}" = $1 WHERE "${pk[0]}" = $2`,[value,pkVal])}async function insertRows(tx,table,rows,mode){if(rows.length===0)return 0;let selfRefUpdates=[];for(let row of rows){let prepared=prepareRow(row,table,selfRefUpdates);await insertOneRow(tx,table,row,prepared,mode)}if(selfRefUpdates.length>0)await updateSelfRefs(tx,table,selfRefUpdates);return rows.length}function parseExportFile(filePath){let raw=readFileSync26(filePath,"utf-8"),parsed;try{parsed=JSON.parse(raw)}catch{throw Error(`Invalid JSON in ${filePath}`)}let validation=validateExportDocument(parsed);if(!validation.valid)throw Error(`Invalid export document: ${validation.error}`);return validation.doc}async function filterTablesByGroup(allTables,groupFilter){if(!groupFilter||groupFilter.length===0)return allTables;let{GROUP_TABLES:GROUP_TABLES2}=await Promise.resolve().then(() => (init_export_format(),exports_export_format)),allowedTables=new Set;for(let group of groupFilter){let tables=GROUP_TABLES2[group];if(tables)for(let t of tables)allowedTables.add(t);else console.warn(`Warning: Unknown group "${group}", skipping`)}return allTables.filter((t)=>allowedTables.has(t))}async function checkConflicts(sql,tables,data){for(let table of tables){let rows=data[table];if(!rows||rows.length===0)continue;let conflicts=await detectConflicts(sql,table,rows);if(conflicts.length>0){let pk=getPrimaryKey(table),ids=conflicts.slice(0,5).map((r)=>pk.map((k)=>r[k]).join(",")).join("; ");throw Error(`Conflict in table "${table}": ${conflicts.length} existing row(s) (e.g., ${ids}). Use --merge or --overwrite to resolve.`)}}}async function runImport(filePath,mode,groupFilter){let doc=parseExportFile(filePath),tablesToImport=await filterTablesByGroup(Object.keys(doc.data),groupFilter);if(tablesToImport.length===0){console.log("No tables to import.");return}tablesToImport=sortByImportOrder(tablesToImport);let sql=await getSql2(),{available}=await detectTables2(sql,tablesToImport),skippedTables=tablesToImport.filter((t)=>!available.includes(t));if(tablesToImport=available,skippedTables.length>0)console.log(`Skipping tables not in database: ${skippedTables.join(", ")}`);if(mode==="fail")await checkConflicts(sql,tablesToImport,doc.data);let totalInserted=0,tableStats={};await sql.begin(async(tx)=>{for(let table of tablesToImport){let rows=doc.data[table];if(!rows||rows.length===0)continue;let count=await insertRows(tx,table,rows,mode);tableStats[table]=count,totalInserted+=count}});let actor=await getActorName2(),{recordAuditEvent:recordAuditEvent3}=await Promise.resolve().then(() => (init_audit(),exports_audit));await recordAuditEvent3("import",filePath,"import_complete",actor,{mode,tables:tableStats,totalRows:totalInserted,skippedTables,sourceVersion:doc.version,sourceDate:doc.exportedAt}),console.log(`Import complete: ${totalInserted} rows across ${Object.keys(tableStats).length} tables`);for(let[table,count]of Object.entries(tableStats))if(count>0)console.log(` ${table}: ${count} rows`);if(skippedTables.length>0)console.log(`Skipped (not in DB): ${skippedTables.join(", ")}`)}function registerImportCommands(program2){program2.command("import <file>").description("Import genie data from JSON export").option("--fail","Abort on any conflict (default)").option("--merge","Skip existing rows, import new ones").option("--overwrite","Replace existing rows with imported data").option("--groups <list>","Comma-separated groups to import (e.g., boards,tags)").action(async(file,options)=>{try{let mode="fail";if(options.overwrite)mode="overwrite";else if(options.merge)mode="merge";let groupFilter=options.groups?.split(",").map((g)=>g.trim());await runImport(file,mode,groupFilter)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_esm14();import{existsSync as existsSync46,mkdirSync as mkdirSync21,symlinkSync,writeFileSync as writeFileSync23}from"fs";import{basename as basename10,join as join55,relative as relative4,resolve as resolve9,sep as sep2}from"path";import{cpSync,existsSync as existsSync44,mkdirSync as mkdirSync19,renameSync as renameSync3,rmSync as rmSync4}from"fs";import{join as join52,relative as relative3}from"path";var import_ignore=__toESM(require_ignore(),1);import{existsSync as existsSync43,readFileSync as readFileSync27,readdirSync as readdirSync10,statSync as statSync4}from"fs";import{join as join51,relative as relative2}from"path";var GENIEIGNORE_DEFAULTS=`node_modules
|
|
3341
3341
|
.git
|
|
3342
3342
|
.genie/worktrees
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260419.
|
|
3
|
+
"version": "4.260419.3",
|
|
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"
|