@automagik/genie 4.260505.1 → 4.260505.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.
@@ -1,4 +1,5 @@
1
1
  // @bun
2
- var __require=import.meta.require;import{appendFileSync,mkdirSync,statSync}from"fs";import{connect}from"net";import{homedir}from"os";import{dirname,join}from"path";function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join(homedir(),".genie");return join(home,"hook.sock")}function fallbackLogPath(){let home=process.env.GENIE_HOME??join(homedir(),".genie");return join(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
3
- `)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function appendFallback(record){let path=fallbackLogPath();try{mkdirSync(dirname(path),{recursive:!0});let writeFresh=!1;try{if(statSync(path).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(record)}
4
- `;if(writeFresh)__require("fs").writeFileSync(path,line);else appendFileSync(path,line)}catch{}}function buildFrame(payload){let header=Buffer.alloc(4);return header.writeUInt32BE(payload.length,0),Buffer.concat([header,payload])}function parseStep(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES)return{kind:"error",reason:`oversized frame ${declared}`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}async function roundtrip(socketPath,payload,timeoutMs){return new Promise((resolve)=>{let settled=!1,finish=(result)=>{if(settled)return;settled=!0,resolve(result)},timer=setTimeout(()=>{try{sock.destroy()}catch{}finish({reply:null,reason:`timeout after ${timeoutMs}ms`})},timeoutMs);timer.unref();let sock=connect(socketPath);sock.once("connect",()=>{sock.write(buildFrame(payload))});let acc=Buffer.alloc(0),length=-1;sock.on("data",(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let step=parseStep(acc,length);if(step.kind==="incomplete"){length=step.length;return}clearTimeout(timer);try{sock.destroy()}catch{}if(step.kind==="done")finish({reply:step.body.toString("utf-8")});else finish({reply:null,reason:step.reason})}),sock.once("error",(err)=>{clearTimeout(timer);let code=err.code??"unknown";finish({reply:null,reason:`connect error: ${code}`})}),sock.once("end",()=>{if(!settled)clearTimeout(timer),finish({reply:null,reason:"socket closed before reply"})})})}async function runDispatchClient(){let payload=readStdinSync();if(payload.length===0)return 0;let socketPath=process.env.GENIE_HOOK_SOCK??defaultSocketPath(),timeoutMs=Number.parseInt(process.env.GENIE_HOOK_TIMEOUT_MS??"",10),effectiveTimeout=Number.isFinite(timeoutMs)&&timeoutMs>0?timeoutMs:DEFAULT_TIMEOUT_MS,result=await roundtrip(socketPath,payload,effectiveTimeout);if(result.reply!==null){if(result.reply.length>0)process.stdout.write(result.reply);return 0}let summary=summarizePayload(payload),agentId=process.env.GENIE_AGENT_ID??process.env.GENIE_AGENT_NAME??null;return appendFallback({ts:new Date().toISOString(),event:summary.event,tool:summary.tool,command:summary.command,agent_id:agentId,reason:result.reason??"unknown"}),0}if(import.meta.main)runDispatchClient().then((code)=>{process.exit(code)}).catch(()=>{process.exit(0)});export{runDispatchClient};
2
+ var __require=import.meta.require;import{appendFileSync,chmodSync,mkdirSync,statSync}from"fs";import{connect}from"net";import{homedir}from"os";import{dirname,join}from"path";var PATTERNS=[{kind:"gh-token",re:/gh[ps]_[A-Za-z0-9]{30,}/g},{kind:"sk-token",re:/sk-[A-Za-z0-9-]{20,}/g},{kind:"glpat",re:/glpat-[A-Za-z0-9_-]{20,}/g},{kind:"hex",re:/\b[a-f0-9]{40,}\b/g}];function redactTokenShapes(text){if(text==null)return null;if(process.env.GENIE_HOOK_REDACTION==="off")return String(text);let out=String(text);for(let{kind,re}of PATTERNS)out=out.replace(re,`[REDACTED:${kind}]`);return out}function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join(homedir(),".genie");return join(home,"hook.sock")}function fallbackLogPath(){let home=process.env.GENIE_HOME??join(homedir(),".genie");return join(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
3
+ `)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function ensureLogPermissions(path){let st;try{st=statSync(path)}catch{return}let mode=st.mode&511;if(mode===384)return;try{chmodSync(path,384),process.stderr.write(`[genie-hook] tightened ${path} permissions ${mode.toString(8)} -> 600
4
+ `)}catch{}}function appendFallback(record){let path=fallbackLogPath();try{mkdirSync(dirname(path),{recursive:!0}),ensureLogPermissions(path);let safe={...record,command:redactTokenShapes(record.command)},writeFresh=!1;try{if(statSync(path).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(safe)}
5
+ `;if(writeFresh)__require("fs").writeFileSync(path,line,{mode:384});else appendFileSync(path,line,{mode:384})}catch{}}function buildFrame(payload){let header=Buffer.alloc(4);return header.writeUInt32BE(payload.length,0),Buffer.concat([header,payload])}function parseStep(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES)return{kind:"error",reason:`oversized frame ${declared}`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}async function roundtrip(socketPath,payload,timeoutMs){return new Promise((resolve)=>{let settled=!1,finish=(result)=>{if(settled)return;settled=!0,resolve(result)},timer=setTimeout(()=>{try{sock.destroy()}catch{}finish({reply:null,reason:`timeout after ${timeoutMs}ms`})},timeoutMs);timer.unref();let sock=connect(socketPath);sock.once("connect",()=>{sock.write(buildFrame(payload))});let acc=Buffer.alloc(0),length=-1;sock.on("data",(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let step=parseStep(acc,length);if(step.kind==="incomplete"){length=step.length;return}clearTimeout(timer);try{sock.destroy()}catch{}if(step.kind==="done")finish({reply:step.body.toString("utf-8")});else finish({reply:null,reason:step.reason})}),sock.once("error",(err)=>{clearTimeout(timer);let code=err.code??"unknown";finish({reply:null,reason:`connect error: ${code}`})}),sock.once("end",()=>{if(!settled)clearTimeout(timer),finish({reply:null,reason:"socket closed before reply"})})})}async function runDispatchClient(){let payload=readStdinSync();if(payload.length===0)return 0;let socketPath=process.env.GENIE_HOOK_SOCK??defaultSocketPath(),timeoutMs=Number.parseInt(process.env.GENIE_HOOK_TIMEOUT_MS??"",10),effectiveTimeout=Number.isFinite(timeoutMs)&&timeoutMs>0?timeoutMs:DEFAULT_TIMEOUT_MS,result=await roundtrip(socketPath,payload,effectiveTimeout);if(result.reply!==null){if(result.reply.length>0)process.stdout.write(result.reply);return 0}let summary=summarizePayload(payload),agentId=process.env.GENIE_AGENT_ID??process.env.GENIE_AGENT_NAME??null;return appendFallback({ts:new Date().toISOString(),event:summary.event,tool:summary.tool,command:summary.command,agent_id:agentId,reason:result.reason??"unknown"}),0}if(import.meta.main)runDispatchClient().then((code)=>{process.exit(code)}).catch(()=>{process.exit(0)});export{runDispatchClient};
package/dist/genie.js CHANGED
@@ -2126,9 +2126,9 @@ server.listen(PORT, '127.0.0.1', () => {
2126
2126
  process.on('SIGTERM', () => { server.close(); process.exit(0); });
2127
2127
  process.on('SIGINT', () => { server.close(); process.exit(0); });
2128
2128
  `,{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){return crypto.randomUUID()}async function capturePanePid2(paneId){if(paneId==="inline")return null;try{let{execSync:execSync10}=__require("child_process"),output=execSync10(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 validateSpawnedPane(paneId,expectedPid){if(paneId==="inline")return;let{execSync:execSync10}=__require("child_process"),panes;try{panes=execSync10(genieTmuxCmd("list-panes -a -F '#{pane_id}'"),{encoding:"utf-8"})}catch{throw new SpawnPaneVanishedError(paneId,expectedPid,"tmux_query_failed")}if(!panes.split(`
2129
- `).map((line)=>line.trim()).includes(paneId))throw new SpawnPaneVanishedError(paneId,expectedPid,"pane_not_in_list");if(expectedPid!==null&&expectedPid>0)try{process.kill(expectedPid,0)}catch(err){if(err.code==="ESRCH")throw new SpawnPaneVanishedError(paneId,expectedPid,"pid_dead")}}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;if(!ctx.agentIdentityId)throw Error(`registerSpawnWorker: missing agentIdentityId for workerId=${JSON.stringify(ctx.workerId)} (team=${JSON.stringify(ctx.validated.team)}). The spawn pipeline MUST call registry.findOrCreateAgent before register() \u2014 see wish retire-session-names-id-only Group 3.`);let workerEntry={id:ctx.agentIdentityId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role??ctx.workerId,skill:ctx.validated.skill,team:ctx.validated.team,customName:ctx.workerId,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,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(ctx.claudeSessionId)console.log(` Session: ${ctx.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:mkdirSync19,writeFileSync:writeFileSync17}=__require("fs"),{join:join51}=__require("path"),{homedir:homedir34}=__require("os"),dir=join51(homedir34(),".genie","spawn-scripts");mkdirSync19(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join51(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync17(scriptPath,`#!/bin/sh
2129
+ `).map((line)=>line.trim()).includes(paneId))throw new SpawnPaneVanishedError(paneId,expectedPid,"pane_not_in_list");if(expectedPid!==null&&expectedPid>0)try{process.kill(expectedPid,0)}catch(err){if(err.code==="ESRCH")throw new SpawnPaneVanishedError(paneId,expectedPid,"pid_dead")}}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;if(!ctx.agentIdentityId)throw Error(`registerSpawnWorker: missing agentIdentityId for workerId=${JSON.stringify(ctx.workerId)} (team=${JSON.stringify(ctx.validated.team)}). The spawn pipeline MUST call registry.findOrCreateAgent before register() \u2014 see wish retire-session-names-id-only Group 3.`);let workerEntry={id:ctx.agentIdentityId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role??ctx.workerId,skill:ctx.validated.skill,team:ctx.validated.team,customName:ctx.workerId,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,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(ctx.claudeSessionId)console.log(` Session: ${ctx.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:chmodSync4,mkdirSync:mkdirSync19,writeFileSync:writeFileSync17}=__require("fs"),{join:join51}=__require("path"),{homedir:homedir34}=__require("os"),dir=join51(homedir34(),".genie","spawn-scripts");mkdirSync19(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join51(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync17(scriptPath,`#!/bin/sh
2130
2130
  exec ${fullCommand}
2131
- `,{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:execSync10}=__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=execSync10(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync10(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:execSync10}=__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 execSync10(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.sessionName??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync10(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync10(`${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 execSync10(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync10(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync10(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 execSync10(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync10(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync10}=__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{execSync10(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,role,tolerateReadinessTimeout){if(paneId==="inline")return;let result2=await waitForAgentReady(paneId);if(result2.ready){console.log(` \u2713 Agent ready (${(result2.elapsedMs/1000).toFixed(1)}s)`);return}if(tolerateReadinessTimeout){console.log(` \u26A0 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 proceeding anyway`);return}throw console.error(` \u2717 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 failing strict mode`),new AgentReadinessTimeoutError(role,paneId,result2.elapsedMs)}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),emitFailed=(reason,extra={})=>{recordAuditEvent("worker",ctx.workerId,"worker.spawn.failed",getActor(),{reason,worker_id:ctx.workerId,agent_role:ctx.validated.role??ctx.workerId,cwd:ctx.cwd,executor_id:ctx.executorId,...extra}).catch(()=>{})},paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){let msg=err instanceof Error?err.message:"unknown error";return emitFailed("createTmuxPane_threw",{error:msg}),console.error(`Failed to create tmux pane: ${msg}`),process.exit(1)}let pid=await capturePanePid2(paneId);try{validateSpawnedPane(paneId,pid)}catch(err){let reason=err instanceof SpawnPaneVanishedError?err.reason:"pane_validation_threw";throw emitFailed("pane_vanished",{pane_id:paneId,pid,vanish_reason:reason,error:err instanceof Error?err.message:String(err)}),err}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);await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry);try{await awaitAgentReadiness(paneId,ctx.validated.role??ctx.workerId,ctx.tolerateReadinessTimeout)}catch(err){let reason=err instanceof AgentReadinessTimeoutError?"readiness_timeout":"readiness_probe_threw";throw emitFailed(reason,{pane_id:paneId,pid,error:err instanceof Error?err.message:String(err)}),err}if(ctx.executorId)await updateExecutorState(ctx.executorId,"running").catch(()=>{});if(ctx.agentIdentityId)await update(ctx.agentIdentityId,{state:"idle"}).catch(()=>{});return recordAuditEvent("worker",ctx.workerId,"worker.spawn.ok",getActor(),{worker_id:ctx.workerId,agent_role:ctx.validated.role??ctx.workerId,pane_id:paneId,pid,cwd:ctx.cwd,executor_id:ctx.executorId}).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`
2131
+ `,{mode:448}),chmodSync4(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:execSync10}=__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=execSync10(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync10(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:execSync10}=__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 execSync10(cmd,{encoding:"utf-8"}).trim()}if(ctx.validated.newWindow){let session=ctx.sessionOverride??teamWindow?.sessionName??ctx.validated.team,cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",sessionExists2=!1;try{execSync10(`${tmuxPrefix}has-session -t ${shellQuote2(`=${session}`)}`,{stdio:"ignore"}),sessionExists2=!0}catch{sessionExists2=!1}if(!sessionExists2)execSync10(`${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 execSync10(cmd,{encoding:"utf-8"}).trim()}if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync10(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync10(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 execSync10(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync10(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync10}=__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{execSync10(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,role,tolerateReadinessTimeout){if(paneId==="inline")return;let result2=await waitForAgentReady(paneId);if(result2.ready){console.log(` \u2713 Agent ready (${(result2.elapsedMs/1000).toFixed(1)}s)`);return}if(tolerateReadinessTimeout){console.log(` \u26A0 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 proceeding anyway`);return}throw console.error(` \u2717 Agent readiness timeout (${Math.round(result2.elapsedMs/1000)}s) \u2014 failing strict mode`),new AgentReadinessTimeoutError(role,paneId,result2.elapsedMs)}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),emitFailed=(reason,extra={})=>{recordAuditEvent("worker",ctx.workerId,"worker.spawn.failed",getActor(),{reason,worker_id:ctx.workerId,agent_role:ctx.validated.role??ctx.workerId,cwd:ctx.cwd,executor_id:ctx.executorId,...extra}).catch(()=>{})},paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){let msg=err instanceof Error?err.message:"unknown error";return emitFailed("createTmuxPane_threw",{error:msg}),console.error(`Failed to create tmux pane: ${msg}`),process.exit(1)}let pid=await capturePanePid2(paneId);try{validateSpawnedPane(paneId,pid)}catch(err){let reason=err instanceof SpawnPaneVanishedError?err.reason:"pane_validation_threw";throw emitFailed("pane_vanished",{pane_id:paneId,pid,vanish_reason:reason,error:err instanceof Error?err.message:String(err)}),err}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);await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry);try{await awaitAgentReadiness(paneId,ctx.validated.role??ctx.workerId,ctx.tolerateReadinessTimeout)}catch(err){let reason=err instanceof AgentReadinessTimeoutError?"readiness_timeout":"readiness_probe_threw";throw emitFailed(reason,{pane_id:paneId,pid,error:err instanceof Error?err.message:String(err)}),err}if(ctx.executorId)await updateExecutorState(ctx.executorId,"running").catch(()=>{});if(ctx.agentIdentityId)await update(ctx.agentIdentityId,{state:"idle"}).catch(()=>{});return recordAuditEvent("worker",ctx.workerId,"worker.spawn.ok",getActor(),{worker_id:ctx.workerId,agent_role:ctx.validated.role??ctx.workerId,pane_id:paneId,pid,cwd:ctx.cwd,executor_id:ctx.executorId}).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`
2132
2132
  SELECT s.id, s.total_turns, COALESCE(s.claude_session_id, e.claude_session_id) as csid
2133
2133
  FROM sessions s
2134
2134
  LEFT JOIN executors e ON e.id = s.executor_id
@@ -2141,7 +2141,7 @@ Agent "${ctx.workerId}" SDK session ended.`),ctx.workerId}async function launchI
2141
2141
  Agent "${ctx.workerId}" session ended.`),process.exit(result2.status??0)}async function findDeadResumable(team,role,isAliveFn=isPaneAliveOrDead){let prefiltered=(await list()).filter((w)=>w.role===role&&w.team===team&&w.provider==="claude"&&w.transport==="tmux"),candidate=null;for(let w of prefiltered)if((await getCurrentExecutor(w.id))?.claudeSessionId){candidate=w;break}if(!candidate)return null;if(await isAliveFn(candidate.paneId)){if(candidate.kind==="permanent")throw new OwnerSpawnCollisionError(role,team,candidate.id,candidate.paneId,candidate.state);return null}return candidate}async function rejectDuplicateRole(team,role){let existing=await list();for(let w of existing){if(w.team!==team)continue;let matchesRole=w.role===role,matchesName=w.id===role;if(!matchesRole&&!matchesName)continue;if(await classifyCollisionLiveness(w)==="alive"){let collisionLabel=matchesName&&!matchesRole?`name "${w.id}"`:`role "${role}"`;console.error(`Error: Worker with ${collisionLabel} already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})
2142
2142
  Use a different --role name for a second worker, e.g.: --role ${role}-2`),process.exit(1)}await unregister(w.id)}}async function classifyCollisionLiveness(w){if(!await resolveWorkerLivenessByTransport(w))return"dead";if(w.session&&/^%\d+$/.test(w.paneId)){if(await getPaneSession(w.paneId)!==w.session)return"recycled"}return"alive"}async function getPaneSession(paneId){try{return(await executeTmux2(`display-message -t '${paneId}' -p '#{session_name}'`)).trim()||null}catch{return null}}async function resolveNativeTeam(team,_repoPath,options){let leaderName=await resolveLeaderName(team),sanitizedTeam=sanitizeTeamName(team),leaderAgent=await getAgentByName(leaderName,sanitizedTeam).catch(()=>null),parentSessionId;if(leaderAgent&&!options.isIdentitySpawn)parentSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId;if(!parentSessionId)parentSessionId=await discoverClaudeParentSessionId()??`genie-${team}`;await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=options.color??await assignColor(team),nativeTeam;if(options.provider==="claude")nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:options.role??"general-purpose",planModeRequired:options.planMode,permissionMode:options.permissionMode,agentName:options.role};return{parentSessionId,spawnColor,nativeTeam}}function autoSyncDisabled(options){return options.noAutoSync===!0||options.autoSync===!1||process.env.GENIE_DISABLE_AUTO_SYNC==="1"}function isWithinAgentsRoot(agentsRoot,agentDir){let rel=relative(agentsRoot,agentDir);return rel!==""&&!rel.startsWith("..")&&!isAbsolute2(rel)}async function tryAutoRegisterAgent(name,options){if(autoSyncDisabled(options))return null;let workspace=_spawnAutoSyncDeps.findWorkspace();if(!workspace)return null;let agentsRoot=join50(workspace.root,"agents"),agentDir=resolvePath(agentsRoot,name);if(!isWithinAgentsRoot(agentsRoot,agentDir))return null;let agentsMdPath=join50(agentDir,"AGENTS.md");if(!_spawnAutoSyncDeps.existsSync(agentsMdPath))return null;let frontmatter;try{frontmatter=_spawnAutoSyncDeps.parseFrontmatter(_spawnAutoSyncDeps.readFileSync(agentsMdPath,"utf-8"))}catch{return null}if(frontmatter.name!==name)return null;if(typeof frontmatter.description!=="string"||frontmatter.description.trim().length===0)return null;try{if(await _spawnAutoSyncDeps.syncSingleAgentByName(workspace.root,name)==="not-found")return null}catch{return null}let resolved=await _spawnAutoSyncDeps.resolveAgent(name);if(!resolved)return null;return _spawnAutoSyncDeps.stderr(`Auto-registered agent '${name}' from ${agentDir}`),resolved}async function resolveAgentForSpawn(name,options){let resolved=await _spawnAutoSyncDeps.resolveAgent(name)??await tryAutoRegisterAgent(name,options);if(!resolved)console.error(`Error: Agent "${name}" not found in directory or built-ins.`),console.error(` Register with: genie dir add ${name} --dir <path>`),console.error(" Or use a built-in: engineer, reviewer, qa, fix, ..."),process.exit(1);let entry2=resolved.entry,identityPath=null;if(resolved.builtin)identityPath=resolveBuiltinAgentPath(name);else if(entry2.dir)identityPath=_spawnAutoSyncDeps.loadIdentity(entry2);if(!identityPath)identityPath=resolveBuiltinAgentPath(name);let repoPath=resolveAgentWorkingDir(entry2,options.cwd),model=options.model;if(!model){let ctx=buildSpawnResolveContext(name,entry2);model=resolveField(entry2,"model",ctx)}return{entry:entry2,repoPath,identityPath,model}}function buildSpawnResolveContext(agentName,_entry){let ctx={};try{let ws=findWorkspace();if(ws){let wsConfig=getWorkspaceConfig(ws.root);ctx.workspaceDefaults=wsConfig.agents?.defaults}}catch{}if(agentName.includes("/")){let parentName=agentName.split("/")[0];try{let{readFileSync:readFileSync28,existsSync:existsSync43}=__require("fs"),{join:join51}=__require("path"),ws=findWorkspace();if(ws){let parentAgentsMd=join51(ws.root,"agents",parentName,"AGENTS.md");if(existsSync43(parentAgentsMd)){let{parseFrontmatter:parseFrontmatter3}=(init_frontmatter(),__toCommonJS(exports_frontmatter)),parentFm=parseFrontmatter3(readFileSync28(parentAgentsMd,"utf-8"));ctx.parent={name:parentName,fields:parentFm}}}}catch{}}return ctx}function resolveAgentWorkingDir(entry2,explicitCwd){if(explicitCwd)return explicitCwd;if(entry2.dir)return entry2.dir;let repo=entry2.repo;if(repo&&__require("fs").existsSync(repo))return repo;return process.cwd()}function buildDirectoryPermissionSpawnParams(entry2){let permissions=entry2.permissions?.allow?.length||entry2.permissions?.deny?.length?{allow:entry2.permissions.allow,deny:entry2.permissions.deny}:void 0;return{...permissions?{permissions}:{},...entry2.disallowedTools?{disallowedTools:entry2.disallowedTools}:{}}}async function buildSpawnParams2(name,team,options,agent,preassignedSessionId,agentTemplate){let resolvedProvider=options.provider??agent.entry.provider??"claude",params={provider:resolvedProvider,team,role:name,agentTemplate:agentTemplate??name,skill:options.skill,extraArgs:options.extraArgs,model:agent.model,systemPromptFile:agent.identityPath??void 0,promptMode:agent.entry.promptMode,initialPrompt:options.prompt??options.initialPrompt,newWindow:options.newWindow,windowTarget:options.window,...buildDirectoryPermissionSpawnParams(agent.entry)},isIdentitySpawn=options.role!==void 0&&options.role!==name,{parentSessionId,spawnColor,nativeTeam}=await resolveNativeTeam(team,agent.repoPath,{...options,provider:resolvedProvider,role:name,isIdentitySpawn});if(nativeTeam)params.nativeTeam=nativeTeam;try{let{injectTeamHooks:injectTeamHooks2}=await Promise.resolve().then(() => (init_inject(),exports_inject));if(await injectTeamHooks2(team))console.log(` Hooks: injected genie hook dispatch into team "${team}"`)}catch(err){console.warn(`Warning: could not inject hooks for team "${team}": ${err instanceof Error?err.message:err}`)}if(params.provider==="codex")try{let{injectCodexHooks:injectCodexHooks2}=await Promise.resolve().then(() => (init_codex_inject(),exports_codex_inject));if(await injectCodexHooks2())console.log(" Hooks: injected genie hook dispatch into ~/.codex/config.toml")}catch(err){console.warn(`Warning: could not inject codex hooks: ${err instanceof Error?err.message:err}`)}if(params.provider==="claude")params.sessionId=preassignedSessionId??crypto.randomUUID();if(params.provider==="claude"){if(await startOtelReceiver())params.otelPort=getOtelPort(),params.otelLogPrompts=!0}return{params,parentSessionId,spawnColor}}async function maybeStartOtelRelay(nt,validated,insideTmux){if(!nt?.enabled&&validated.provider==="codex"&&insideTmux)return ensureCodexOtelConfig(),await ensureOtelRelay(validated.team);return!1}function buildSdkRuntimeExtra(options){let extra={};if(options.sdkMaxTurns!=null)extra.maxTurns=options.sdkMaxTurns;if(options.sdkMaxBudget!=null)extra.maxBudgetUsd=options.sdkMaxBudget;if(options.sdkEffort)extra.effort=options.sdkEffort;if(options.sdkResume)extra.resume=options.sdkResume;return Object.keys(extra).length>0?extra:void 0}async function dispatchSpawn(ctx,validated,options,agent,insideTmux){if(validated.provider==="claude-sdk"){let streamFormat=options.streamFormat??"text",streamOpts=options.stream||options.sdkStream?{stream:!0,streamFormat}:void 0;return await launchSdkSpawn(ctx,agent.entry.permissions,streamOpts,agent.entry.sdk,buildSdkRuntimeExtra(options))}if(insideTmux)return await launchTmuxSpawn(ctx);return await launchInlineSpawn(ctx)}async function resolveTeamName3(opts){if(opts.explicitTeam)return opts.explicitTeam;if(opts.entryTeam)return opts.entryTeam;let env=opts.env??process.env;if(env.GENIE_TEAM)return env.GENIE_TEAM;return await(opts.discover??discoverTeamName)()??null}async function pickParallelShortId(baseName,team,uuid){if(!UUID_REGEX.test(uuid))throw Error(`pickParallelShortId: expected a well-formed UUID (8-4-4-4-12 hex), got ${JSON.stringify(uuid)}`);let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2();for(let k=4;k<=uuid.length;k++){let slice=uuid.slice(0,k),id=`${baseName}-${slice}`;if((await sql`
2143
2143
  SELECT id FROM agents WHERE id = ${id} LIMIT 1
2144
- `).length===0)return slice}return uuid}async function resolveSpawnIdentity(name,team,uuidFactory=()=>crypto.randomUUID(),isAliveFn=(agent)=>resolveWorkerLivenessByTransport(agent)){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(name))return{kind:"canonical",workerId:uuidFactory(),sessionUuid:uuidFactory()};let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
2144
+ `).length===0)return slice}return uuid}async function resolveSpawnIdentity(name,team,uuidFactory=()=>crypto.randomUUID(),isAliveFn=(agent)=>resolveWorkerLivenessByTransport(agent)){if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(name))return{kind:"canonical",workerId:name,sessionUuid:uuidFactory()};let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
2145
2145
  SELECT id, pane_id, team FROM agents WHERE id = ${name} LIMIT 1
2146
2146
  `;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}}let alive=!1;if(existing.pane_id)try{alive=await isAliveFn({id:existing.id,paneId:existing.pane_id})}catch(err){let message=err instanceof Error?err.message:String(err);if(err instanceof TmuxUnreachableError||message.includes("no server running")||message.includes("server exited")||message.includes("error connecting"))alive=!1;else throw err}if(!alive)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){let executor=await getCurrentExecutor(deadResumable.id),sessionShort=executor?.claudeSessionId?.slice(0,8)??"unknown";console.log(`Resuming existing session for "${effectiveRole}" (session: ${sessionShort}...)`);try{return await resumeAgent(deadResumable),{team,teamWasExplicit,resumed:deadResumable.id}}catch(err){if(err instanceof ResumePaneVanishedError){if(console.warn(`\u26A0 Resume of "${effectiveRole}" produced a dead pane (${err.reason}). Clearing stale executor and falling through to fresh spawn.`),executor?.id)await updateExecutorState(executor.id,"terminated").catch(()=>{});return{team,teamWasExplicit}}throw err}}return{team,teamWasExplicit}}async function resolveTeamAndResumeOrExit(effectiveRole,options,agent){try{return await resolveTeamAndResume(effectiveRole,options,agent)}catch(err){if(err instanceof OwnerSpawnCollisionError)return console.error(`Error: owner agent "${err.ownerName}" already alive in team "${err.team}" (id: ${err.conflictId}, pane: ${err.conflictPaneId}, state: ${err.conflictState}).
2147
2147
  Owner identities are exclusive \u2014 to spawn a separate worker under the same template, use --role <custom-name>:
@@ -3985,9 +3985,10 @@ module.exports = {
3985
3985
  `);for(let r of rows){let id=r.id.padEnd(31),st=r.status.padEnd(10),at=(r.appliedAt||"-").padEnd(24),from=r.appliedFrom||"-";process.stdout.write(`${id} ${st} ${at} ${from}
3986
3986
  `)}return}let result2=await migrate({quiet:options.quiet,dryRun:options.dryRun});process.exit(result2.ok?0:1)}init_setup();init_shortcuts();import{existsSync as existsSync26}from"fs";import{homedir as homedir26}from"os";import{join as join32}from"path";async function shortcutsShowCommand(){displayShortcuts();let home=homedir26(),tmuxConf=join32(home,".tmux.conf"),zshrc=join32(home,".zshrc"),bashrc=join32(home,".bashrc");if(console.log("Installation status:"),isShortcutsInstalled(tmuxConf))console.log(" \x1B[32m\u2713\x1B[0m tmux.conf");else console.log(" \x1B[33m-\x1B[0m tmux.conf");let shellRc=existsSync26(zshrc)?zshrc:bashrc;if(isShortcutsInstalled(shellRc))console.log(` \x1B[32m\u2713\x1B[0m ${shellRc.replace(home,"~")}`);else console.log(` \x1B[33m-\x1B[0m ${shellRc.replace(home,"~")}`);console.log(),console.log("Run \x1B[36mgenie shortcuts install\x1B[0m to install shortcuts."),console.log("Run \x1B[36mgenie shortcuts uninstall\x1B[0m to remove shortcuts."),console.log()}async function shortcutsInstallCommand(){await installShortcuts()}async function shortcutsUninstallCommand(){await uninstallShortcuts()}init_esm14();init_claude_settings();init_genie_config2();import{existsSync as existsSync27,lstatSync,rmSync as rmSync2,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir27}from"os";import{join as join33}from"path";var ORCHESTRATION_RULES_PATH=join33(homedir27(),".claude","rules","genie-orchestration.md"),LOCAL_BIN=join33(homedir27(),".local","bin"),SYMLINKS=["genie","term"];function isGenieSymlink(path3){try{if(!existsSync27(path3))return!1;if(!lstatSync(path3).isSymbolicLink())return!1;return!0}catch{return!1}}function removeSymlinks(){let removed=[];for(let name of SYMLINKS){let symlinkPath=join33(LOCAL_BIN,name);if(isGenieSymlink(symlinkPath))try{unlinkSync8(symlinkPath),removed.push(name)}catch{}}return removed}function tryRemoveStep(label,successMsg,fn){console.log(`\x1B[2m${label}\x1B[0m`);try{fn(),console.log(` \x1B[32m+\x1B[0m ${successMsg}`)}catch(error){let message=error instanceof Error?error.message:String(error);console.log(` \x1B[33m!\x1B[0m ${label.replace("...","")} failed: ${message}`)}}function performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir){if(hasHookScript)tryRemoveStep("Removing hook script...","Hook script removed",()=>removeHookScript());if(existingSymlinks.length>0){console.log("\x1B[2mRemoving symlinks...\x1B[0m");let removed=removeSymlinks();if(removed.length>0)console.log(` \x1B[32m+\x1B[0m Removed: ${removed.join(", ")}`)}if(existsSync27(ORCHESTRATION_RULES_PATH))tryRemoveStep("Removing orchestration rules...","Orchestration rules removed (~/.claude/rules/genie-orchestration.md)",()=>unlinkSync8(ORCHESTRATION_RULES_PATH));if(hasGenieDir)tryRemoveStep("Removing genie directory...","Directory removed",()=>rmSync2(genieDir,{recursive:!0,force:!0}))}async function uninstallCommand(){console.log(),console.log("\x1B[1m\x1B[33m Uninstall Genie CLI\x1B[0m"),console.log();let genieDir=getGenieDir(),hasGenieDir=existsSync27(genieDir),hasHookScript=hookScriptExists(),hasOrchestrationRules=existsSync27(ORCHESTRATION_RULES_PATH),existingSymlinks=SYMLINKS.filter((name)=>isGenieSymlink(join33(LOCAL_BIN,name)));if(console.log("\x1B[2mThis will remove:\x1B[0m"),hasHookScript)console.log(" \x1B[31m-\x1B[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)");if(hasOrchestrationRules)console.log(" \x1B[31m-\x1B[0m Orchestration rules (~/.claude/rules/genie-orchestration.md)");if(hasGenieDir)console.log(` \x1B[31m-\x1B[0m Genie directory (${contractPath(genieDir)})`);if(existingSymlinks.length>0)console.log(` \x1B[31m-\x1B[0m Symlinks from ~/.local/bin: ${existingSymlinks.join(", ")}`);if(console.log(),!hasGenieDir&&!hasHookScript&&!hasOrchestrationRules&&existingSymlinks.length===0){console.log("\x1B[33mNothing to uninstall.\x1B[0m"),console.log();return}if(!await esm_default4({message:"Are you sure you want to uninstall Genie CLI?",default:!1})){console.log(),console.log("\x1B[2mUninstall cancelled.\x1B[0m"),console.log();return}console.log(),performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir),console.log(),console.log("\x1B[32m+\x1B[0m Genie CLI uninstalled."),console.log(),console.log("\x1B[2mNote: If you installed via npm/bun, also run:\x1B[0m"),console.log(" \x1B[36mbun remove -g @automagik/genie\x1B[0m"),console.log(" \x1B[2mor\x1B[0m"),console.log(" \x1B[36mnpm uninstall -g @automagik/genie\x1B[0m"),console.log()}init_genie_config2();import{execSync as execSync3,spawn as spawn3}from"child_process";import{chmodSync as chmodSync2,closeSync as closeSync3,copyFileSync as copyFileSync3,existsSync as existsSync28,mkdirSync as mkdirSync15,openSync as openSync3,readFileSync as readFileSync19,readSync as readSync2,readdirSync as readdirSync9,rmSync as rmSync3,statSync as statSync7,writeFileSync as writeFileSync13}from"fs";import{chmod,copyFile,mkdir as mkdir5,unlink as unlink2}from"fs/promises";import{homedir as homedir28}from"os";import{join as join34}from"path";var GENIE_HOME2=process.env.GENIE_HOME||join34(homedir28(),".genie"),GENIE_SRC=join34(GENIE_HOME2,"src"),GENIE_BIN=join34(GENIE_HOME2,"bin"),LOCAL_BIN2=join34(homedir28(),".local","bin"),TRUTHY=new Set(["1","true","yes","on"]),UPDATE_DIAGNOSTIC_SCHEMA_VERSION=1;function log(message){console.log(`\x1B[32m\u25B8\x1B[0m ${message}`)}function success(message){console.log(`\x1B[32m\u2714\x1B[0m ${message}`)}function error(message){console.log(`\x1B[31m\u2716\x1B[0m ${message}`)}function formatDuration2(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function isTruthyEnv(value){return value!==void 0&&TRUTHY.has(value.trim().toLowerCase())}async function withTemporaryEnv(key,value,fn){let previous=process.env[key];process.env[key]=value;try{return await fn()}finally{if(previous===void 0)delete process.env[key];else process.env[key]=previous}}function safeExec(command,timeoutMs=1500){try{return execSync3(command,{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:timeoutMs}).trim()}catch(err){let stdout=err.stdout;if(typeof stdout==="string"&&stdout.trim())return stdout.trim();return""}}function safeRead(path3,maxChars=4000){try{let value=readFileSync19(path3,"utf-8");if(value.length<=maxChars)return value;return value.slice(value.length-maxChars)}catch{return null}}function tailLines(path3,maxBytes=64000,maxLines=200){let fd=null;try{let stat4=statSync7(path3),bytesToRead=Math.min(stat4.size,maxBytes),buffer2=Buffer.alloc(bytesToRead);return fd=openSync3(path3,"r"),readSync2(fd,buffer2,0,bytesToRead,Math.max(0,stat4.size-bytesToRead)),buffer2.toString("utf-8").split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).slice(-maxLines)}catch{return[]}finally{if(fd!==null)try{closeSync3(fd)}catch{}}}function summarizeJsonlSignals(path3){let signals2=new Map;for(let line of tailLines(path3))try{let event=JSON.parse(line),level=typeof event.level==="string"?event.level:"unknown";if(level!=="error"&&level!=="warn")continue;let name=typeof event.event==="string"?event.event:"unknown",key=`${level}:${name}`,existing=signals2.get(key)??{level,event:name,count:0};if(existing.count++,typeof event.timestamp==="string")existing.lastTimestamp=event.timestamp;if(typeof event.error==="string")existing.lastError=event.error;signals2.set(key,existing)}catch{}return[...signals2.values()].sort((a,b2)=>b2.count-a.count).slice(0,10)}async function collectUpdateDiagnostics(ctx,maintenance){let logsDir=join34(GENIE_HOME2,"logs");mkdirSync15(logsDir,{recursive:!0});let generatedAt=new Date().toISOString(),safeStamp=generatedAt.replace(/[:.]/g,"-"),path3=join34(logsDir,`update-diagnostics-${safeStamp}.json`),schedulerLog=join34(logsDir,"scheduler.log"),tuiCrashLog=join34(logsDir,"tui-crash.log"),signals2=summarizeJsonlSignals(schedulerLog),diagnostics={schemaVersion:UPDATE_DIAGNOSTIC_SCHEMA_VERSION,generatedAt,update:ctx,runtime:{platform:process.platform,arch:process.arch,cwd:process.cwd(),node:process.version,bun:(await runCommandSilent("bun",["--version"])).output.trim()||null,npm:(await runCommandSilent("npm",["--version"])).output.trim()||null,genie:{which:(await runCommandSilent("which",["genie"])).output.trim()||null,tuiDisabled:isTruthyEnv(process.env.GENIE_TUI_DISABLE),updateSkipMaintenance:isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)}},paths:{genieHome:GENIE_HOME2,logsDir,servePid:safeRead(join34(GENIE_HOME2,"serve.pid"),200),pgservePort:safeRead(join34(GENIE_HOME2,"pgserve.port"),200),schedulerLog,tuiCrashLog},processSnapshot:{genie:safeExec("ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r | rg -i 'dist/genie.js|/src/genie.ts|pgserve|postgres -D .*\\.genie/data/pgserve|tmux -L genie-tui|bun' || true",2000)||null,tuiTmux:safeExec("tmux -L genie-tui ls 2>/dev/null || true",1000)||null},maintenance:{...maintenance,pgAutostartDisabled:!0,legend:{"[ok]":"healthy","[fix]":"fixed during maintenance","[--]":"skipped/non-blocking","[!!]":"operator action needed; update still completed"}},recentLogSignals:{scheduler:signals2,schedulerTail:tailLines(schedulerLog,32000,80),tuiCrashTail:tailLines(tuiCrashLog,32000,80)}};return writeFileSync13(path3,`${JSON.stringify(diagnostics,null,2)}
3987
3987
  `),{path:path3,signals:signals2}}async function runCommand(command,args,cwd){return new Promise((resolve4)=>{let output=[],child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}});child.stdout?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stdout.write(str2)}),child.stderr?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stderr.write(str2)}),child.on("close",(code)=>{resolve4({success:code===0,output:output.join("")})}),child.on("error",(err)=>{error(err.message),resolve4({success:!1,output:err.message})})})}async function getGitInfo(cwd){try{let branchResult=await runCommandSilent("git",["rev-parse","--abbrev-ref","HEAD"],cwd),commitResult=await runCommandSilent("git",["rev-parse","--short","HEAD"],cwd),dateResult=await runCommandSilent("git",["log","-1","--format=%ci"],cwd);if(branchResult.success&&commitResult.success&&dateResult.success)return{branch:branchResult.output.trim(),commit:commitResult.output.trim(),commitDate:dateResult.output.trim().split(" ")[0]}}catch{}return null}async function runCommandSilent(command,args,cwd,timeoutMs=4000){return new Promise((resolve4)=>{let output=[],settled=!1,child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"]}),timer2=setTimeout(()=>{if(settled)return;settled=!0,child.kill("SIGTERM"),resolve4({success:!1,output:`Timed out after ${timeoutMs}ms`})},timeoutMs);child.stdout?.on("data",(data)=>{output.push(data.toString())}),child.stderr?.on("data",(data)=>{output.push(data.toString())}),child.on("close",(code)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve4({success:code===0,output:output.join("")})}),child.on("error",(err)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve4({success:!1,output:err.message})})})}function detectFromBinaryPath(path3){let resolved=path3;try{resolved=__require("fs").realpathSync(path3)}catch{}if(resolved.includes(".bun"))return"bun";if(resolved.includes("node_modules"))return"npm";if(path3===join34(LOCAL_BIN2,"genie")||resolved.startsWith(GENIE_BIN))return"source";return null}async function detectInstallationType(){let whichResult=await runCommandSilent("which",["genie"]);if(whichResult.success){let detected=detectFromBinaryPath(whichResult.output.trim());if(detected)return detected}if(genieConfigExists())try{let config=await loadGenieConfig();if(config.installMethod)return config.installMethod}catch{}if(existsSync28(join34(GENIE_SRC,".git")))return"source";return(await runCommandSilent("which",["bun"])).success?"bun":"npm"}async function updateViaBun(channel){try{__require("fs").unlinkSync(join34(homedir28(),".bun","install","global","bun.lock"))}catch{}if(log(`Updating via bun (channel: ${channel})...`),!(await runCommand("bun",["add","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via bun"),!1;return console.log(),success(`Genie CLI updated via bun (${channel})!`),!0}async function updateViaNpm(channel){if(log(`Updating via npm (channel: ${channel})...`),!(await runCommand("npm",["install","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via npm"),!1;return console.log(),success(`Genie CLI updated via npm (${channel})!`),!0}async function detectGlobalInstalls(){let found=new Set,[npmResult,bunResult]=await Promise.all([runCommandSilent("npm",["list","-g","@automagik/genie"]),runCommandSilent("bun",["pm","ls","-g"])]);if(npmResult.success&&!npmResult.output.includes("(empty)"))found.add("npm");if(bunResult.success&&bunResult.output.includes("@automagik/genie"))found.add("bun");return found}async function updateSource(){if(!existsSync28(GENIE_SRC))error(`Source install path not found: ${GENIE_SRC}`),console.error(" Detection picked the source-install path, but the directory does not exist."),console.error(" This usually means a stale install hint (config or ~/.genie/src/.git) is"),console.error(" pointing somewhere genuine. Either:"),console.error(` 1. Re-clone the source: git clone https://github.com/automagik-dev/genie ${GENIE_SRC}`),console.error(" 2. Update via package manager instead: genie update --next --via bun"),console.error(" 3. Inspect detection: genie doctor --update-detection"),process.exit(1);if(!existsSync28(join34(GENIE_SRC,".git")))error(`Source install path is not a git checkout: ${GENIE_SRC}`),console.error(` ${GENIE_SRC} exists but has no .git/. Cannot run \`git fetch\` from it.`),console.error(` Either delete ${GENIE_SRC} and re-clone, or update via package manager:`),console.error(" genie update --next --via bun"),process.exit(1);let beforeInfo=await getGitInfo(GENIE_SRC);if(beforeInfo)console.log(`Current: \x1B[2m${beforeInfo.branch}@${beforeInfo.commit} (${beforeInfo.commitDate})\x1B[0m`),console.log();if(log("Fetching latest changes..."),!(await runCommand("git",["fetch","origin"],GENIE_SRC)).success)error("Failed to fetch from origin"),process.exit(1);if(log("Resetting to origin/main..."),!(await runCommand("git",["reset","--hard","origin/main"],GENIE_SRC)).success)error("Failed to reset to origin/main"),process.exit(1);console.log();let afterInfo=await getGitInfo(GENIE_SRC);if(beforeInfo&&afterInfo&&beforeInfo.commit===afterInfo.commit){success("Already up to date!"),console.log();return}if(log("Installing dependencies..."),!(await runCommand("bun",["install"],GENIE_SRC)).success)error("Failed to install dependencies"),process.exit(1);if(console.log(),log("Building..."),!(await runCommand("bun",["run","build"],GENIE_SRC)).success)error("Failed to build"),process.exit(1);console.log(),log("Installing binaries...");try{await mkdir5(GENIE_BIN,{recursive:!0}),await mkdir5(LOCAL_BIN2,{recursive:!0});let binaries=["genie.js","term.js"],names=["genie","term"];for(let i2=0;i2<binaries.length;i2++){let src=join34(GENIE_SRC,"dist",binaries[i2]),binDest=join34(GENIE_BIN,binaries[i2]),linkDest=join34(LOCAL_BIN2,names[i2]);await copyFile(src,binDest),await chmod(binDest,493),await symlinkOrCopy(binDest,linkDest)}for(let legacy of["claudio.js","claudio"]){let legacyBin=join34(GENIE_BIN,legacy),legacyLink=join34(LOCAL_BIN2,legacy);try{await unlink2(legacyBin)}catch{}try{await unlink2(legacyLink)}catch{}}success("Binaries installed")}catch(err){error(`Failed to install binaries: ${err}`),process.exit(1)}if(console.log(),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),success("Genie CLI updated successfully!"),console.log(),afterInfo)console.log(`Version: \x1B[36m${afterInfo.branch}@${afterInfo.commit}\x1B[0m (${afterInfo.commitDate})`),console.log()}async function symlinkOrCopy(src,dest){let{symlink:symlink2,unlink:unlink3}=await import("fs/promises");try{if(existsSync28(dest))await unlink3(dest);await symlink2(src,dest)}catch{await copyFile(src,dest)}}function copyDirSync(src,dest){mkdirSync15(dest,{recursive:!0});for(let entry2 of readdirSync9(src,{withFileTypes:!0})){let srcPath=join34(src,entry2.name),destPath=join34(dest,entry2.name);if(entry2.isDirectory())copyDirSync(srcPath,destPath);else copyFileSync3(srcPath,destPath)}}async function resolveGlobalPkgDir(installType){if(installType==="bun"){let bunPath=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync28(bunPath))return bunPath}if(installType==="npm"){let npmRootResult=await runCommandSilent("npm",["root","-g"]);if(npmRootResult.success){let npmPath=join34(npmRootResult.output.trim(),"@automagik","genie");if(existsSync28(npmPath))return npmPath}}let bunFallback=join34(homedir28(),".bun","install","global","node_modules","@automagik","genie");if(existsSync28(bunFallback))return bunFallback;let npmRootFallback=await runCommandSilent("npm",["root","-g"]);if(npmRootFallback.success){let npmPath=join34(npmRootFallback.output.trim(),"@automagik","genie");if(existsSync28(npmPath))return npmPath}return null}function updatePluginRegistry(claudePlugins,cacheDir,version){let registryPath=join34(claudePlugins,"installed_plugins.json");try{if(!existsSync28(registryPath))return;let registry=JSON.parse(readFileSync19(registryPath,"utf-8")),entries=registry.plugins?.["genie@automagik"];if(!Array.isArray(entries))return;for(let entry2 of entries)if(entry2.scope==="user")entry2.installPath=cacheDir,entry2.version=version,entry2.lastUpdated=new Date().toISOString();writeFileSync13(registryPath,JSON.stringify(registry,null,2))}catch(err){log(`Registry update failed (non-fatal): ${err}`)}}function syncTmuxConf(tmuxScriptsSrc){mkdirSync15(GENIE_HOME2,{recursive:!0});let tmuxConfSrc=join34(tmuxScriptsSrc,"genie.tmux.conf"),tmuxConfDest=join34(GENIE_HOME2,"tmux.conf");if(existsSync28(tmuxConfSrc))try{copyFileSync3(tmuxConfSrc,tmuxConfDest),success(`Installed tmux config to ${tmuxConfDest}`);try{let{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));execSync3(`${tmuxBin2()} -L genie source-file '${tmuxConfDest}'`,{stdio:"ignore"}),success("Reloaded genie tmux server configuration")}catch{}}catch{}let tuiConfSrc=join34(tmuxScriptsSrc,"tui-tmux.conf"),tuiConfDest=join34(GENIE_HOME2,"tui-tmux.conf");if(existsSync28(tuiConfSrc))try{copyFileSync3(tuiConfSrc,tuiConfDest),success(`Installed TUI tmux config to ${tuiConfDest}`)}catch{}let themeSrc=join34(tmuxScriptsSrc,".generated.theme.conf"),themeDest=join34(GENIE_HOME2,".generated.theme.conf");if(existsSync28(themeSrc))try{copyFileSync3(themeSrc,themeDest),success(`Installed tmux theme to ${themeDest}`)}catch{}let osc52Src=join34(tmuxScriptsSrc,"osc52-copy.sh"),osc52Dest=join34(GENIE_HOME2,"scripts","osc52-copy.sh");if(existsSync28(osc52Src))try{copyFileSync3(osc52Src,osc52Dest),chmodSync2(osc52Dest,493),success(`Installed OSC 52 clipboard helper to ${osc52Dest}`)}catch{}}function syncTmuxScripts(globalPkgDir){let tmuxScriptsSrc=join34(globalPkgDir,"scripts","tmux");if(!existsSync28(tmuxScriptsSrc))return;let scriptsDir=join34(GENIE_HOME2,"scripts");mkdirSync15(scriptsDir,{recursive:!0});let scriptCount=0;for(let entry2 of readdirSync9(tmuxScriptsSrc))if(entry2.endsWith(".sh")||entry2==="genie.tmux.conf"||entry2==="tui-tmux.conf"||entry2===".generated.theme.conf"){let src=join34(tmuxScriptsSrc,entry2),dest=join34(scriptsDir,entry2);copyFileSync3(src,dest);try{chmodSync2(dest,entry2.endsWith(".sh")?493:420)}catch{}scriptCount++}if(scriptCount>0)success(`Refreshed ${scriptCount} tmux scripts at ${scriptsDir}`);syncTmuxConf(tmuxScriptsSrc)}function syncMarketplaceVersion(claudePlugins,version){let marketplacePath=join34(claudePlugins,"marketplaces","automagik",".claude-plugin","marketplace.json");try{if(!existsSync28(marketplacePath))return;let data=JSON.parse(readFileSync19(marketplacePath,"utf-8"));if(Array.isArray(data.plugins)){for(let plugin of data.plugins)if(plugin.name==="genie")plugin.version=version}writeFileSync13(marketplacePath,JSON.stringify(data,null,2)),success(`Updated marketplace.json to v${version}`)}catch(err){log(`Marketplace version update failed (non-fatal): ${err}`)}}function syncPluginPackageVersion(claudePlugins,version){let pkgPath=join34(claudePlugins,"marketplaces","automagik","plugins","genie","package.json");try{if(!existsSync28(pkgPath))return;let data=JSON.parse(readFileSync19(pkgPath,"utf-8"));data.version=version,writeFileSync13(pkgPath,JSON.stringify(data,null,2)),success(`Updated plugin package.json to v${version}`)}catch(err){log(`Plugin package.json update failed (non-fatal): ${err}`)}}function syncSkillsSymlink(claudePlugins,version){let skillsLink=join34(claudePlugins,"marketplaces","automagik","plugins","genie","skills"),cacheSkills=join34("..","..","..","..","cache","automagik","genie",version,"skills");try{let{symlinkSync,unlinkSync:unlinkSync9,lstatSync:lstatSync2}=__require("fs");try{lstatSync2(skillsLink),unlinkSync9(skillsLink)}catch{}symlinkSync(cacheSkills,skillsLink),success(`Skills symlink \u2192 cache/${version}/skills`)}catch(err){log(`Skills symlink update failed (non-fatal): ${err}`)}}async function syncPlugin(installType){log("Syncing Claude Code plugin...");let globalPkgDir=await resolveGlobalPkgDir(installType);if(!globalPkgDir)return log("Could not find installed package \u2014 skipping plugin sync"),{skippedReason:"installed package not found"};let pluginSrc=join34(globalPkgDir,"plugins","genie");if(!existsSync28(pluginSrc))return log("Plugin source not found in package \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"plugin source not found in package"};let version;try{version=JSON.parse(readFileSync19(join34(globalPkgDir,"package.json"),"utf-8")).version}catch{return log("Could not read package version \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"could not read package version"}}let claudePlugins=join34(homedir28(),".claude","plugins"),cacheDir=join34(claudePlugins,"cache","automagik","genie",version);try{if(existsSync28(cacheDir))rmSync3(cacheDir,{recursive:!0,force:!0});copyDirSync(pluginSrc,cacheDir);let skillsSrc=join34(globalPkgDir,"skills");if(existsSync28(skillsSrc)&&!existsSync28(join34(cacheDir,"skills")))copyDirSync(skillsSrc,join34(cacheDir,"skills"))}catch(err){return error(`Failed to copy plugin: ${err}`),{version,globalPkgDir,cacheDir,skippedReason:`failed to copy plugin: ${err}`}}return updatePluginRegistry(claudePlugins,cacheDir,version),syncMarketplaceVersion(claudePlugins,version),syncPluginPackageVersion(claudePlugins,version),syncSkillsSymlink(claudePlugins,version),syncTmuxScripts(globalPkgDir),success(`Plugin synced to v${version}`),{version,globalPkgDir,cacheDir}}async function resolveChannel(options){if(options.next)return"next";if(options.stable)return"latest";if(genieConfigExists())try{let config=await loadGenieConfig();if(config.updateChannel)return config.updateChannel}catch{}return"latest"}async function persistChannel(channel){try{let config=await loadGenieConfig();config.updateChannel=channel,await saveGenieConfig(config)}catch{}}async function updateCommand(options={}){console.log(),console.log("\x1B[1m\uD83E\uDDDE Genie CLI Update\x1B[0m"),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),console.log();let channel=await resolveChannel(options);if(options.next||options.stable)await persistChannel(channel);let installType=await detectInstallationType();if(log(`Detected installation: ${installType}`),log(`Channel: ${channel}${channel==="next"?" (dev builds)":" (stable)"}`),console.log(),installType==="unknown")error("No Genie CLI installation found"),console.log(),console.log("Install method not configured. Please reinstall genie:"),console.log("\x1B[36m curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash\x1B[0m"),console.log(),process.exit(1);if(installType==="source"){await updateSource();return}let globalInstalls=await detectGlobalInstalls(),primaryMethod=installType;if(!(primaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))process.exit(1);let secondaryMethod=primaryMethod==="bun"?"npm":"bun";if(globalInstalls.has(secondaryMethod)){if(console.log(),log(`Also updating ${secondaryMethod}-global install...`),!(secondaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))error(`Secondary update via ${secondaryMethod} failed (non-blocking)`)}let plugin=await syncPlugin(installType);await runPostUpdateMaintenanceSafe(options,{channel,installType,primaryMethod,globalInstalls:[...globalInstalls].sort(),plugin})}function printPostUpdateMaintenanceIntro(){console.log(),log("Running post-update maintenance..."),console.log(" Purpose: make first launch after update fast and collect upgrade health signals."),console.log(" Checks: runtime partitions, watchdog status, session backfill drift, zombie rows, team config orphans."),console.log(" PG policy: read-only; uses an already-running pgserve when available and will not auto-start it."),console.log(" Legend: [ok]=healthy, [fix]=fixed, [--]=skipped/non-blocking, [!!]=operator action needed.")}async function runMaintenanceWithCapturedLines(maintenanceLines){let{runPostUpdateMaintenance:runPostUpdateMaintenance2}=await Promise.resolve().then(() => (init_doctor(),exports_doctor));await withTemporaryEnv("GENIE_PG_NO_AUTOSTART","1",()=>runPostUpdateMaintenance2({log:(line)=>{maintenanceLines.push(line),console.log(line)}}))}function printDiagnosticsSummary(diagnostics){if(log("Post-update diagnostics captured."),console.log(` Report: ${diagnostics.path}`),console.log(" Include this file when opening a GitHub issue; it contains install metadata, step output,"),console.log(" local process state, and recent scheduler/TUI log signals."),diagnostics.signals.length===0)return;console.log(" Recent scheduler signals:");for(let signal of diagnostics.signals.slice(0,3)){let errorDetail=signal.lastError?` \u2014 ${signal.lastError}`:"";console.log(` ${signal.level}:${signal.event} \xD7${signal.count}${errorDetail}`)}}async function capturePostUpdateDiagnostics(diagnosticsCtx,maintenance){if(!diagnosticsCtx)return;try{let diagnostics=await collectUpdateDiagnostics(diagnosticsCtx,maintenance);printDiagnosticsSummary(diagnostics)}catch(err){let msg=err instanceof Error?err.message:String(err);log(`Post-update diagnostics capture failed (non-fatal): ${msg}`)}}async function runPostUpdateMaintenanceSafe(options={},diagnosticsCtx){if(options.skipMaintenance||isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)){log("Skipping post-update maintenance (requested).");return}let startedAt=Date.now(),maintenanceLines=[],outcome="completed",maintenanceError;try{printPostUpdateMaintenanceIntro(),await runMaintenanceWithCapturedLines(maintenanceLines),success(`Post-update maintenance complete (${formatDuration2(Date.now()-startedAt)}).`)}catch(err){outcome="failed",maintenanceError=err instanceof Error?err.message:String(err),error(`Post-update maintenance skipped: ${maintenanceError}`)}await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome,durationMs:Date.now()-startedAt,lines:maintenanceLines,error:maintenanceError})}init_version();init_trust();init_trust();init_trust();import{execSync as execSync4}from"child_process";import{existsSync as existsSync31,mkdirSync as mkdirSync16,readFileSync as readFileSync22,renameSync as renameSync6,writeFileSync as writeFileSync14}from"fs";import{dirname as dirname13,isAbsolute,join as join36,resolve as resolve5}from"path";function resolveOriginUrl(repoRoot){try{return execSync4("git config --get remote.origin.url",{cwd:repoRoot,encoding:"utf-8"}).trim()||null}catch{return null}}function findRepoRoot(start){let current=start;while(current!=="/"){if(existsSync31(join36(current,".git")))return current;current=dirname13(current)}return null}function writeTrustFile(path3,file){let dir=dirname13(path3);if(!existsSync31(dir))mkdirSync16(dir,{recursive:!0});let sorted={version:1,entries:[...file.entries].sort((a,b2)=>a.path.localeCompare(b2.path))},serialized=`${JSON.stringify(sorted,null,2)}
3988
- `,tmpPath=`${path3}.tmp.${process.pid}`;writeFileSync14(tmpPath,serialized,"utf-8"),renameSync6(tmpPath,path3)}function runTrustList(trustPath){let current=readTrustFile(trustPath);if(current.entries.length===0){console.log(`(trust file empty: ${trustPath})`);return}console.log(`Trusted hooks (${current.entries.length}, from ${trustPath}):`);for(let entry2 of current.entries){let scope=entry2.scope==="repo"?`repo:${entry2.repoRemoteUrl??"?"}`:entry2.scope;if(console.log(` ${entry2.path}`),console.log(` scope: ${scope}`),console.log(` sha256: ${entry2.sha256}`),console.log(` trusted: ${entry2.trustedAt}`),entry2.capabilities&&entry2.capabilities.length>0)console.log(` caps: ${entry2.capabilities.join(", ")}`);if(entry2.note)console.log(` note: ${entry2.note}`)}}function resolveTrustScope(filePath,options){if(options.repo){let repoRoot=findRepoRoot(dirname13(filePath));if(!repoRoot)console.error(`Error: --repo passed but no .git directory found above ${filePath}`),process.exit(1);let remote=resolveOriginUrl(repoRoot);if(!remote)console.error(`Error: --repo passed but ${repoRoot} has no remote.origin.url`),process.exit(1);return{scope:"repo",repoRemoteUrl:remote}}if(options.team)return{scope:"team"};return{scope:"global"}}async function confirmTrustOrAbort(yes){if(yes||!process.stdin.isTTY)return;process.stdout.write("Confirm? (y/N) ");let buf=Buffer.alloc(8),read=0;try{read=(await import("fs")).readSync(0,buf,0,buf.length,null)}catch{console.error("Error: cannot read confirmation; pass --yes to trust non-interactively"),process.exit(1)}let answer=buf.subarray(0,read).toString().trim().toLowerCase();if(answer!=="y"&&answer!=="yes")console.log("Aborted."),process.exit(1)}async function runTrustAdd(target,options,trustPath){let filePath=isAbsolute(target)?target:resolve5(process.cwd(),target);if(!existsSync31(filePath))console.error(`Error: file not found: ${filePath}`),process.exit(1);if(!filePath.endsWith(".ts"))console.error(`Error: trust target must be a .ts file: ${filePath}`),process.exit(1);let{scope,repoRemoteUrl}=resolveTrustScope(filePath,options),sha=sha256OfFile(filePath),source=readFileSync22(filePath,"utf-8"),capabilities=parseCapabilities(source);if(console.log(`About to trust: ${filePath}`),console.log(` scope: ${scope==="repo"?`repo:${repoRemoteUrl}`:scope}`),console.log(` sha256: ${sha}`),capabilities.length>0)console.log(` caps: ${capabilities.join(", ")}`);if(options.note)console.log(` note: ${options.note}`);await confirmTrustOrAbort(options.yes??!1);let current=readTrustFile(trustPath),newEntry={path:filePath,sha256:sha,scope,repoRemoteUrl,trustedAt:new Date().toISOString(),note:options.note,capabilities:capabilities.length>0?capabilities:void 0},next={version:1,entries:[...current.entries.filter((e)=>e.path!==filePath),newEntry]};writeTrustFile(trustPath,next),console.log(`Trusted ${filePath} \u2192 ${trustPath}`)}async function trustAction(target,options){let trustPath=defaultTrustPath();if(!target)return runTrustList(trustPath);await runTrustAdd(target,options,trustPath)}function registerHookTrustCommand(parent){parent.command("trust [path]").description("Add a .ts hook file to the trust allowlist (or list current entries when [path] is omitted)").option("--repo","Scope to current repo (pinned to remote.origin.url)").option("--global","Scope globally (default)").option("--team <name>","Scope to a specific team directory").option("--note <text>","Free-form note saved with the entry").option("--yes","Skip the interactive confirmation prompt").action(trustAction)}import{appendFileSync as appendFileSync3,mkdirSync as mkdirSync17,statSync as statSync8}from"fs";import{connect as connect2}from"net";import{homedir as homedir30}from"os";import{dirname as dirname14,join as join37}from"path";function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook.sock")}function fallbackLogPath2(){let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
3989
- `)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function appendFallback(record){let path3=fallbackLogPath2();try{mkdirSync17(dirname14(path3),{recursive:!0});let writeFresh=!1;try{if(statSync8(path3).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(record)}
3990
- `;if(writeFresh)__require("fs").writeFileSync(path3,line);else appendFileSync3(path3,line)}catch{}}function buildFrame(payload){let header=Buffer.alloc(4);return header.writeUInt32BE(payload.length,0),Buffer.concat([header,payload])}function parseStep(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES)return{kind:"error",reason:`oversized frame ${declared}`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}async function roundtrip(socketPath,payload,timeoutMs){return new Promise((resolve6)=>{let settled=!1,finish=(result2)=>{if(settled)return;settled=!0,resolve6(result2)},timer2=setTimeout(()=>{try{sock.destroy()}catch{}finish({reply:null,reason:`timeout after ${timeoutMs}ms`})},timeoutMs);timer2.unref();let sock=connect2(socketPath);sock.once("connect",()=>{sock.write(buildFrame(payload))});let acc=Buffer.alloc(0),length=-1;sock.on("data",(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let step=parseStep(acc,length);if(step.kind==="incomplete"){length=step.length;return}clearTimeout(timer2);try{sock.destroy()}catch{}if(step.kind==="done")finish({reply:step.body.toString("utf-8")});else finish({reply:null,reason:step.reason})}),sock.once("error",(err)=>{clearTimeout(timer2);let code=err.code??"unknown";finish({reply:null,reason:`connect error: ${code}`})}),sock.once("end",()=>{if(!settled)clearTimeout(timer2),finish({reply:null,reason:"socket closed before reply"})})})}async function runDispatchClient(){let payload=readStdinSync();if(payload.length===0)return 0;let socketPath=process.env.GENIE_HOOK_SOCK??defaultSocketPath(),timeoutMs=Number.parseInt(process.env.GENIE_HOOK_TIMEOUT_MS??"",10),effectiveTimeout=Number.isFinite(timeoutMs)&&timeoutMs>0?timeoutMs:DEFAULT_TIMEOUT_MS,result2=await roundtrip(socketPath,payload,effectiveTimeout);if(result2.reply!==null){if(result2.reply.length>0)process.stdout.write(result2.reply);return 0}let summary=summarizePayload(payload),agentId=process.env.GENIE_AGENT_ID??process.env.GENIE_AGENT_NAME??null;return appendFallback({ts:new Date().toISOString(),event:summary.event,tool:summary.tool,command:summary.command,agent_id:agentId,reason:result2.reason??"unknown"}),0}init_hooks();async function readStdin(){let chunks=[];for await(let chunk of Bun.stdin.stream())chunks.push(Buffer.from(chunk));return Buffer.concat(chunks).toString("utf-8")}async function dispatchAction(){if(process.env.GENIE_SKIP_DB_BOOT="1",process.env.GENIE_HOOK_FORCE_INPROC!=="1"){let code=await runDispatchClient();await drainStdout(),process.exit(code)}let stdin=await readStdin();if(!stdin.trim())process.exit(0);let result2=await dispatch(stdin);if(result2)process.stdout.write(result2);await drainStdout(),process.exit(0)}function drainStdout(){return new Promise((resolve7)=>{process.stdout.write("",()=>resolve7())})}function registerHookNamespace(program2){let hook=program2.command("hook").description("Hook middleware for Claude Code integration");hook.command("dispatch").description("Dispatch a CC hook event (reads JSON from stdin, writes decision to stdout)").action(dispatchAction),registerHookTrustCommand(hook)}init_audit();init_db();init_interactivity();init_otel_receiver();init_target_resolver();init_tmux();init_orchestrator();async function resolveOrcTarget(target){let resolved=await resolveTarget(target);return{paneId:resolved.paneId,session:resolved.session||target,label:formatResolvedLabel(resolved,target)}}async function sendTextChoice(paneId,text){await executeTmux2(`send-keys -t '${paneId}' End`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' ${shellEscape(text)}`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}function findCurrentOption(output){let lines=stripAnsi(output).split(`
3988
+ `,tmpPath=`${path3}.tmp.${process.pid}`;writeFileSync14(tmpPath,serialized,"utf-8"),renameSync6(tmpPath,path3)}function runTrustList(trustPath){let current=readTrustFile(trustPath);if(current.entries.length===0){console.log(`(trust file empty: ${trustPath})`);return}console.log(`Trusted hooks (${current.entries.length}, from ${trustPath}):`);for(let entry2 of current.entries){let scope=entry2.scope==="repo"?`repo:${entry2.repoRemoteUrl??"?"}`:entry2.scope;if(console.log(` ${entry2.path}`),console.log(` scope: ${scope}`),console.log(` sha256: ${entry2.sha256}`),console.log(` trusted: ${entry2.trustedAt}`),entry2.capabilities&&entry2.capabilities.length>0)console.log(` caps: ${entry2.capabilities.join(", ")}`);if(entry2.note)console.log(` note: ${entry2.note}`)}}function resolveTrustScope(filePath,options){if(options.repo){let repoRoot=findRepoRoot(dirname13(filePath));if(!repoRoot)console.error(`Error: --repo passed but no .git directory found above ${filePath}`),process.exit(1);let remote=resolveOriginUrl(repoRoot);if(!remote)console.error(`Error: --repo passed but ${repoRoot} has no remote.origin.url`),process.exit(1);return{scope:"repo",repoRemoteUrl:remote}}if(options.team)return{scope:"team"};return{scope:"global"}}async function confirmTrustOrAbort(yes){if(yes||!process.stdin.isTTY)return;process.stdout.write("Confirm? (y/N) ");let buf=Buffer.alloc(8),read=0;try{read=(await import("fs")).readSync(0,buf,0,buf.length,null)}catch{console.error("Error: cannot read confirmation; pass --yes to trust non-interactively"),process.exit(1)}let answer=buf.subarray(0,read).toString().trim().toLowerCase();if(answer!=="y"&&answer!=="yes")console.log("Aborted."),process.exit(1)}async function runTrustAdd(target,options,trustPath){let filePath=isAbsolute(target)?target:resolve5(process.cwd(),target);if(!existsSync31(filePath))console.error(`Error: file not found: ${filePath}`),process.exit(1);if(!filePath.endsWith(".ts"))console.error(`Error: trust target must be a .ts file: ${filePath}`),process.exit(1);let{scope,repoRemoteUrl}=resolveTrustScope(filePath,options),sha=sha256OfFile(filePath),source=readFileSync22(filePath,"utf-8"),capabilities=parseCapabilities(source);if(console.log(`About to trust: ${filePath}`),console.log(` scope: ${scope==="repo"?`repo:${repoRemoteUrl}`:scope}`),console.log(` sha256: ${sha}`),capabilities.length>0)console.log(` caps: ${capabilities.join(", ")}`);if(options.note)console.log(` note: ${options.note}`);await confirmTrustOrAbort(options.yes??!1);let current=readTrustFile(trustPath),newEntry={path:filePath,sha256:sha,scope,repoRemoteUrl,trustedAt:new Date().toISOString(),note:options.note,capabilities:capabilities.length>0?capabilities:void 0},next={version:1,entries:[...current.entries.filter((e)=>e.path!==filePath),newEntry]};writeTrustFile(trustPath,next),console.log(`Trusted ${filePath} \u2192 ${trustPath}`)}async function trustAction(target,options){let trustPath=defaultTrustPath();if(!target)return runTrustList(trustPath);await runTrustAdd(target,options,trustPath)}function registerHookTrustCommand(parent){parent.command("trust [path]").description("Add a .ts hook file to the trust allowlist (or list current entries when [path] is omitted)").option("--repo","Scope to current repo (pinned to remote.origin.url)").option("--global","Scope globally (default)").option("--team <name>","Scope to a specific team directory").option("--note <text>","Free-form note saved with the entry").option("--yes","Skip the interactive confirmation prompt").action(trustAction)}import{appendFileSync as appendFileSync3,chmodSync as chmodSync3,mkdirSync as mkdirSync17,statSync as statSync8}from"fs";import{connect as connect2}from"net";import{homedir as homedir30}from"os";import{dirname as dirname14,join as join37}from"path";var PATTERNS=[{kind:"gh-token",re:/gh[ps]_[A-Za-z0-9]{30,}/g},{kind:"sk-token",re:/sk-[A-Za-z0-9-]{20,}/g},{kind:"glpat",re:/glpat-[A-Za-z0-9_-]{20,}/g},{kind:"hex",re:/\b[a-f0-9]{40,}\b/g}];function redactTokenShapes(text){if(text==null)return null;if(process.env.GENIE_HOOK_REDACTION==="off")return String(text);let out=String(text);for(let{kind,re}of PATTERNS)out=out.replace(re,`[REDACTED:${kind}]`);return out}function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook.sock")}function fallbackLogPath2(){let home=process.env.GENIE_HOME??join37(homedir30(),".genie");return join37(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
3989
+ `)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function ensureLogPermissions(path3){let st;try{st=statSync8(path3)}catch{return}let mode=st.mode&511;if(mode===384)return;try{chmodSync3(path3,384),process.stderr.write(`[genie-hook] tightened ${path3} permissions ${mode.toString(8)} -> 600
3990
+ `)}catch{}}function appendFallback(record){let path3=fallbackLogPath2();try{mkdirSync17(dirname14(path3),{recursive:!0}),ensureLogPermissions(path3);let safe={...record,command:redactTokenShapes(record.command)},writeFresh=!1;try{if(statSync8(path3).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(safe)}
3991
+ `;if(writeFresh)__require("fs").writeFileSync(path3,line,{mode:384});else appendFileSync3(path3,line,{mode:384})}catch{}}function buildFrame(payload){let header=Buffer.alloc(4);return header.writeUInt32BE(payload.length,0),Buffer.concat([header,payload])}function parseStep(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES)return{kind:"error",reason:`oversized frame ${declared}`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}async function roundtrip(socketPath,payload,timeoutMs){return new Promise((resolve6)=>{let settled=!1,finish=(result2)=>{if(settled)return;settled=!0,resolve6(result2)},timer2=setTimeout(()=>{try{sock.destroy()}catch{}finish({reply:null,reason:`timeout after ${timeoutMs}ms`})},timeoutMs);timer2.unref();let sock=connect2(socketPath);sock.once("connect",()=>{sock.write(buildFrame(payload))});let acc=Buffer.alloc(0),length=-1;sock.on("data",(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let step=parseStep(acc,length);if(step.kind==="incomplete"){length=step.length;return}clearTimeout(timer2);try{sock.destroy()}catch{}if(step.kind==="done")finish({reply:step.body.toString("utf-8")});else finish({reply:null,reason:step.reason})}),sock.once("error",(err)=>{clearTimeout(timer2);let code=err.code??"unknown";finish({reply:null,reason:`connect error: ${code}`})}),sock.once("end",()=>{if(!settled)clearTimeout(timer2),finish({reply:null,reason:"socket closed before reply"})})})}async function runDispatchClient(){let payload=readStdinSync();if(payload.length===0)return 0;let socketPath=process.env.GENIE_HOOK_SOCK??defaultSocketPath(),timeoutMs=Number.parseInt(process.env.GENIE_HOOK_TIMEOUT_MS??"",10),effectiveTimeout=Number.isFinite(timeoutMs)&&timeoutMs>0?timeoutMs:DEFAULT_TIMEOUT_MS,result2=await roundtrip(socketPath,payload,effectiveTimeout);if(result2.reply!==null){if(result2.reply.length>0)process.stdout.write(result2.reply);return 0}let summary=summarizePayload(payload),agentId=process.env.GENIE_AGENT_ID??process.env.GENIE_AGENT_NAME??null;return appendFallback({ts:new Date().toISOString(),event:summary.event,tool:summary.tool,command:summary.command,agent_id:agentId,reason:result2.reason??"unknown"}),0}init_hooks();async function readStdin(){let chunks=[];for await(let chunk of Bun.stdin.stream())chunks.push(Buffer.from(chunk));return Buffer.concat(chunks).toString("utf-8")}async function dispatchAction(){if(process.env.GENIE_SKIP_DB_BOOT="1",process.env.GENIE_HOOK_FORCE_INPROC!=="1"){let code=await runDispatchClient();await drainStdout(),process.exit(code)}let stdin=await readStdin();if(!stdin.trim())process.exit(0);let result2=await dispatch(stdin);if(result2)process.stdout.write(result2);await drainStdout(),process.exit(0)}function drainStdout(){return new Promise((resolve7)=>{process.stdout.write("",()=>resolve7())})}function registerHookNamespace(program2){let hook=program2.command("hook").description("Hook middleware for Claude Code integration");hook.command("dispatch").description("Dispatch a CC hook event (reads JSON from stdin, writes decision to stdout)").action(dispatchAction),registerHookTrustCommand(hook)}init_audit();init_db();init_interactivity();init_otel_receiver();init_target_resolver();init_tmux();init_orchestrator();async function resolveOrcTarget(target){let resolved=await resolveTarget(target);return{paneId:resolved.paneId,session:resolved.session||target,label:formatResolvedLabel(resolved,target)}}async function sendTextChoice(paneId,text){await executeTmux2(`send-keys -t '${paneId}' End`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' ${shellEscape(text)}`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}function findCurrentOption(output){let lines=stripAnsi(output).split(`
3991
3992
  `);for(let line of lines){let match=line.match(/^\s*\u276F\s*(\d+)\./);if(match)return Number.parseInt(match[1],10)}return 1}async function navigateToOption(paneId,targetOption,currentOption){let diff=targetOption-currentOption,key=diff>0?"Down":"Up";for(let i2=0;i2<Math.abs(diff);i2++)await executeTmux2(`send-keys -t '${paneId}' ${key}`),await sleep2(50);await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}async function answerQuestion(target,choice){try{let{paneId,label}=await resolveOrcTarget(target),output=await capturePaneContent(paneId,50),state=detectState(output);if(state.type!=="question"){console.log(`No question pending (state: ${state.type})`);return}if(choice.startsWith("text:")){let text=choice.slice(5);await sendTextChoice(paneId,text),console.log(`Sent feedback: "${text.substring(0,50)}${text.length>50?"...":""}"`)}else if(/^\d+$/.test(choice)){let targetOption=Number.parseInt(choice,10);await navigateToOption(paneId,targetOption,findCurrentOption(output)),console.log(`Selected option ${targetOption} for ${label}`)}else await executeTmux2(`send-keys -t '${paneId}' '${choice}'`),console.log(`Sent '${choice}' to ${label}`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}function shellEscape(str2){return`"${str2.replace(/"/g,"\\\"").replace(/\$/g,"\\$")}"`}function sleep2(ms){return new Promise((resolve7)=>setTimeout(resolve7,ms))}function registerAgentAnswer(parent){parent.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{try{await answerQuestion(name,choice)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}var _brief;async function getBrief(){if(!_brief)_brief=await Promise.resolve().then(() => (init_brief(),exports_brief));return _brief}async function handleBrief(options){let team=options.team??process.env.GENIE_TEAM;if(!team)console.error("Error: --team is required (or set GENIE_TEAM)"),process.exit(1);let agent=options.agent??process.env.GENIE_AGENT_NAME,briefService=await getBrief(),brief=await briefService.generateBrief({team,agent,since:options.since,repoPath:process.cwd()});console.log(briefService.formatBrief(brief))}function registerAgentBrief(parent){parent.command("brief").description("Show startup brief \u2014 aggregated context since last session").option("--team <name>","Team name (default: GENIE_TEAM)").option("--agent <name>","Agent name (default: GENIE_AGENT_NAME)").option("--since <iso>","Start timestamp (default: last executor end)").action(async(options)=>{try{await handleBrief(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_directory();init_agent_sync();init_builtin_agents();init_genie_config2();async function showEntry(name,json2){let resolved=await resolve6(name);if(!resolved)console.error(`Agent "${name}" not found in directory or built-ins.`),process.exit(1);if(json2){console.log(JSON.stringify({...resolved.entry,builtin:resolved.builtin},null,2));return}if(resolved.builtin)console.log(`
3992
3993
  (built-in agent)`);if(console.log(""),console.log(` Name: ${resolved.entry.name}`),console.log(` Dir: ${contractPath(resolved.entry.dir)}`),resolved.entry.repo)console.log(` Repo: ${contractPath(resolved.entry.repo)}`);if(console.log(` Prompt mode: ${resolved.entry.promptMode}`),resolved.entry.model)console.log(` Model: ${resolved.entry.model}`);if(resolved.entry.roles?.length)console.log(` Roles: ${resolved.entry.roles.join(", ")}`);console.log(` Registered: ${resolved.entry.registeredAt}`),console.log("")}function printRegisteredAgentsTable(entries){let termW=process.stdout.columns||120,repoValues=entries.map((e)=>e.repo?contractPath(e.repo):contractPath(e.dir)),maxRepoLen=Math.max(4,...repoValues.map((v)=>v.length)),fixedW=42,repoW=Math.min(maxRepoLen+2,Math.max(30,termW-42-20));console.log(""),console.log("REGISTERED AGENTS"),console.log("-".repeat(Math.max(90,42+repoW+20))),console.log(` ${"NAME".padEnd(22)}${"SCOPE".padEnd(10)}${"REPO".padEnd(repoW)}${"MODEL".padEnd(8)}ROLES`);for(let i2=0;i2<entries.length;i2++){let entry2=entries[i2],repo=repoValues[i2],roles=entry2.roles?.join(", ")||"-";console.log(` ${entry2.name.padEnd(22)}${entry2.scope.padEnd(10)}${repo.padEnd(repoW)}${(entry2.model||"-").padEnd(8)}${roles}`)}console.log("")}function printBuiltinAgentsTable(){console.log("BUILT-IN AGENTS"),console.log("-".repeat(80)),console.log(` ${"NAME".padEnd(22)}${"TYPE".padEnd(10)}${"MODEL".padEnd(8)}DESCRIPTION`);for(let agent of ALL_BUILTINS)console.log(` ${agent.name.padEnd(22)}${agent.category.padEnd(10)}${(agent.model||"-").padEnd(8)}${agent.description}`);console.log("")}async function listEntries(json2,includeBuiltins,_includeArchived){let entries=await ls();if(json2){let result2=entries.map((e)=>({...e,builtin:!1}));if(includeBuiltins)for(let b2 of ALL_BUILTINS)result2.push({name:b2.name,description:b2.description,model:b2.model,category:b2.category,scope:"built-in",builtin:!0});console.log(JSON.stringify(result2,null,2));return}if(entries.length===0&&!includeBuiltins){console.log(`
3993
3994
  No agents registered. Add one with: genie agent register <name> --dir <path>`),console.log(`Use --builtins to also see built-in roles and council members.
@@ -4359,7 +4360,7 @@ Event Throughput (last 60s):`),console.log(` Emitted: ${data.events_emitted_la
4359
4360
  ORDER BY created_at ASC
4360
4361
  `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No snapshots in the given time range.");return}let headers=["Time","Workers","Teams","Tmux","CPU%","Mem MB"],data=rows.map((r)=>[formatRelativeTimestamp(r.created_at),String(r.active_workers??0),String(r.active_teams??0),String(r.tmux_sessions??0),r.cpu_percent!==null?String(r.cpu_percent):"-",r.memory_mb!==null?String(r.memory_mb):"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row2)=>row2[i2]);return Math.max(h.length,...colVals.map((v)=>v.length))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row2 of data)console.log(row2.map((v,i2)=>padRight(v,widths[i2])).join(" | "));console.log(`
4361
4362
  (${rows.length} snapshot${rows.length===1?"":"s"})`)}async function metricsAgentsCommand(options){if(options.json){console.log(JSON.stringify({deprecated:!0,replacement:"genie status",message:"Use `genie status` for live agent state."}));return}console.error("\u26A0\uFE0F `genie metrics agents` is deprecated and will be removed in a future release."),console.error(" Use `genie status` for live agent state.")}function registerMetricsCommands(program2){let metrics=program2.command("metrics").description("Machine metrics \u2014 snapshots, heartbeats, agents");metrics.command("now",{isDefault:!0}).description("Current machine state").option("--json","Output as JSON").action(async(options)=>{await metricsNowCommand(options)}),metrics.command("history").description("Machine snapshot history").option("--since <duration>","Time window (e.g., 1h, 6h, 1d)","1h").option("--json","Output as JSON").action(async(options)=>{await metricsHistoryCommand(options)}),metrics.command("agents").description("[DEPRECATED] Use `genie status` for live agent state").option("--json","Output as JSON").action(async(options)=>{await metricsAgentsCommand(options)})}init_msg();init_term_format();var _taskService4;async function getTaskService4(){if(!_taskService4)_taskService4=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService4}function currentActor2(){return{actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"}}async function handleNotifySet(options){let ts3=await getTaskService4(),actor=currentActor2(),pref=await ts3.setPreference(actor,options.channel,{priorityThreshold:options.priority,isDefault:options.default}),defaultLabel=pref.isDefault?", default":"";console.log(`Notification preference set: ${pref.channel} (threshold: ${pref.priorityThreshold}${defaultLabel}).`)}function printPrefsTable(prefs){console.log(` ${padRight("CHANNEL",15)} ${padRight("THRESHOLD",12)} ${padRight("DEFAULT",10)} ENABLED`),console.log(` ${"\u2500".repeat(45)}`);for(let p of prefs){let dflt=p.isDefault?"yes":"no",enabled=p.enabled?"yes":"no";console.log(` ${padRight(p.channel,15)} ${padRight(p.priorityThreshold,12)} ${padRight(dflt,10)} ${enabled}`)}console.log(`
4362
- ${prefs.length} preference${prefs.length===1?"":"s"}`)}async function handleNotifyList(options){let ts3=await getTaskService4(),actor=currentActor2(),prefs=await ts3.getPreferences(actor);if(options.json){console.log(JSON.stringify(prefs,null,2));return}if(prefs.length===0){console.log("No notification preferences configured.");return}printPrefsTable(prefs)}async function handleNotifyRemove(options){let ts3=await getTaskService4(),actor=currentActor2();if(await ts3.deletePreference(actor,options.channel))console.log(`Removed notification preference for channel: ${options.channel}`);else console.log(`No preference found for channel: ${options.channel}`)}function registerNotifyCommands(program2){let notify=program2.command("notify").description("Notification preference management");notify.command("set").description("Set notification preference for a channel").requiredOption("--channel <channel>","Channel: whatsapp, telegram, email, slack, discord, tmux").option("--priority <priority>","Minimum priority threshold","normal").option("--default","Set as default channel").action(async(options)=>{try{await handleNotifySet(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("list").description("List notification preferences").option("--json","Output as JSON").action(async(options)=>{try{await handleNotifyList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("remove").description("Remove a notification preference").requiredOption("--channel <channel>","Channel to remove").action(async(options)=>{try{await handleNotifyRemove(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_observability();init_term_format();var FLAG_LABELS2={stale_executor:"STALE",missing_session:"NO-SESSION",missing_attribution:"UNATTRIBUTED",high_hook_latency:"SLOW-HOOK",recent_failure:"TOOL-ERR",cost_spike:"COST"};function summarize(snap){let name=snap.customName??snap.role??snap.agentId,namePart=padRight(truncate2(name,28),28),exec3=snap.executorState??"-",execPart=padRight(truncate2(exec3,12),12),tools=`${snap.recentToolCount} tools / ${snap.recentErrorCount} err`,cost=snap.recentCostUsd>0?`$${snap.recentCostUsd.toFixed(2)}`:"$0",last=snap.recentLastToolAt?formatRelativeTimestamp(snap.recentLastToolAt):"-",health=snap.health.flags.length===0?color("green","OK"):color("yellow",snap.health.flags.map((f)=>FLAG_LABELS2[f]).join(","));return` ${namePart} ${execPart} ${padRight(tools,18)} ${padRight(cost,8)} last:${padRight(last,9)} ${health}`}function renderHumanFleet(snaps,includeHarness){if(console.log(""),console.log(color("bold",includeHarness?"AGENTS + HARNESS":"AGENTS")),console.log("-".repeat(72)),snaps.length===0){console.log(color("dim"," (none)")),console.log("");return}for(let snap of snaps)console.log(summarize(snap));let degraded=snaps.filter((s2)=>s2.health.degraded).length;console.log(""),console.log(color("dim",` ${snaps.length} rows, ${degraded} degraded \u2014 schema v${AGENT_OBSERVABILITY_SCHEMA_VERSION}`)),console.log("")}async function observeAgentsCommand(opts={}){let limit=opts.limit?Number.parseInt(opts.limit,10):void 0,snaps=await listAgentObservability({includeHarness:opts.includeHarness===!0,limit:Number.isFinite(limit)?limit:void 0});if(opts.json)console.log(JSON.stringify({_source:{schemaVersion:AGENT_OBSERVABILITY_SCHEMA_VERSION,view:"v_agent_observability"},agents:snaps},null,2));else renderHumanFleet(snaps,opts.includeHarness===!0);if(opts.strict&&snaps.some((s2)=>s2.health.degraded))process.exit(1)}function registerObserveCommands(program2){let observe=program2.command("observe").description("Canonical observability snapshots");observe.command("agents").description("Fleet-level agent observability snapshot").option("--json","Emit machine-readable JSON").option("--include-harness","Include rows whose classification is `harness`").option("--strict","Exit with code 1 when any row is degraded").option("--limit <n>","Maximum rows to return").action(async(options)=>{try{await observeAgentsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`observe: ${message}`),process.exit(2)}}),observe.on("command:*",(operands)=>{let cmd=operands[0],available=observe.commands.map((c)=>c.name()).join(", ");observe.error(`Unknown observe command '${cmd}'. Available: ${available}`)})}init_genie_config2();import{generateKeyPairSync}from"crypto";import{chmodSync as chmodSync3,existsSync as existsSync70,mkdirSync as mkdirSync34,readFileSync as readFileSync45,writeFileSync as writeFileSync32}from"fs";import{hostname as osHostname}from"os";import{dirname as dirname27,join as join82,resolve as resolvePath5}from"path";function keyPaths2(){let home=process.env.GENIE_HOME??join82(process.env.HOME??"/root",".genie"),dir=join82(home,"keys");return{dir,privateKey:join82(dir,"genie-host.ed25519"),publicKey:join82(dir,"genie-host.ed25519.pub"),hostJson:join82(dir,"host.json")}}function assertNotInsideGitRepo(dir){let probe=resolvePath5(dir);for(let depth=0;depth<16;depth++){if(existsSync70(join82(probe,".git")))throw Error(`Refusing to write keys to ${dir} \u2014 it lives inside a git working tree (${join82(probe,".git")}). Set $GENIE_HOME to a path outside any git repo and re-run.`);let parent=dirname27(probe);if(parent===probe)return;probe=parent}}function generateAndPersistKeypair(paths){if(!existsSync70(paths.dir))mkdirSync34(paths.dir,{recursive:!0,mode:448});let{publicKey,privateKey}=generateKeyPairSync("ed25519"),rawPub=publicKey.export({format:"der",type:"spki"}),pubkeyB64Url=rawPub.subarray(rawPub.length-32).toString("base64url");return writeFileSync32(paths.privateKey,privateKey.export({format:"pem",type:"pkcs8"}),{mode:384}),writeFileSync32(paths.publicKey,pubkeyB64Url,{mode:420}),chmodSync3(paths.privateKey,384),{pubkeyB64Url}}function loadExistingPubkey(paths){if(!existsSync70(paths.publicKey))return null;return readFileSync45(paths.publicKey,"utf-8").trim()}function loadHostJson(paths){if(!existsSync70(paths.hostJson))return null;try{return JSON.parse(readFileSync45(paths.hostJson,"utf-8"))}catch{return null}}function writeHostJson(paths,record){writeFileSync32(paths.hostJson,`${JSON.stringify(record,null,2)}
4363
+ ${prefs.length} preference${prefs.length===1?"":"s"}`)}async function handleNotifyList(options){let ts3=await getTaskService4(),actor=currentActor2(),prefs=await ts3.getPreferences(actor);if(options.json){console.log(JSON.stringify(prefs,null,2));return}if(prefs.length===0){console.log("No notification preferences configured.");return}printPrefsTable(prefs)}async function handleNotifyRemove(options){let ts3=await getTaskService4(),actor=currentActor2();if(await ts3.deletePreference(actor,options.channel))console.log(`Removed notification preference for channel: ${options.channel}`);else console.log(`No preference found for channel: ${options.channel}`)}function registerNotifyCommands(program2){let notify=program2.command("notify").description("Notification preference management");notify.command("set").description("Set notification preference for a channel").requiredOption("--channel <channel>","Channel: whatsapp, telegram, email, slack, discord, tmux").option("--priority <priority>","Minimum priority threshold","normal").option("--default","Set as default channel").action(async(options)=>{try{await handleNotifySet(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("list").description("List notification preferences").option("--json","Output as JSON").action(async(options)=>{try{await handleNotifyList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("remove").description("Remove a notification preference").requiredOption("--channel <channel>","Channel to remove").action(async(options)=>{try{await handleNotifyRemove(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_observability();init_term_format();var FLAG_LABELS2={stale_executor:"STALE",missing_session:"NO-SESSION",missing_attribution:"UNATTRIBUTED",high_hook_latency:"SLOW-HOOK",recent_failure:"TOOL-ERR",cost_spike:"COST"};function summarize(snap){let name=snap.customName??snap.role??snap.agentId,namePart=padRight(truncate2(name,28),28),exec3=snap.executorState??"-",execPart=padRight(truncate2(exec3,12),12),tools=`${snap.recentToolCount} tools / ${snap.recentErrorCount} err`,cost=snap.recentCostUsd>0?`$${snap.recentCostUsd.toFixed(2)}`:"$0",last=snap.recentLastToolAt?formatRelativeTimestamp(snap.recentLastToolAt):"-",health=snap.health.flags.length===0?color("green","OK"):color("yellow",snap.health.flags.map((f)=>FLAG_LABELS2[f]).join(","));return` ${namePart} ${execPart} ${padRight(tools,18)} ${padRight(cost,8)} last:${padRight(last,9)} ${health}`}function renderHumanFleet(snaps,includeHarness){if(console.log(""),console.log(color("bold",includeHarness?"AGENTS + HARNESS":"AGENTS")),console.log("-".repeat(72)),snaps.length===0){console.log(color("dim"," (none)")),console.log("");return}for(let snap of snaps)console.log(summarize(snap));let degraded=snaps.filter((s2)=>s2.health.degraded).length;console.log(""),console.log(color("dim",` ${snaps.length} rows, ${degraded} degraded \u2014 schema v${AGENT_OBSERVABILITY_SCHEMA_VERSION}`)),console.log("")}async function observeAgentsCommand(opts={}){let limit=opts.limit?Number.parseInt(opts.limit,10):void 0,snaps=await listAgentObservability({includeHarness:opts.includeHarness===!0,limit:Number.isFinite(limit)?limit:void 0});if(opts.json)console.log(JSON.stringify({_source:{schemaVersion:AGENT_OBSERVABILITY_SCHEMA_VERSION,view:"v_agent_observability"},agents:snaps},null,2));else renderHumanFleet(snaps,opts.includeHarness===!0);if(opts.strict&&snaps.some((s2)=>s2.health.degraded))process.exit(1)}function registerObserveCommands(program2){let observe=program2.command("observe").description("Canonical observability snapshots");observe.command("agents").description("Fleet-level agent observability snapshot").option("--json","Emit machine-readable JSON").option("--include-harness","Include rows whose classification is `harness`").option("--strict","Exit with code 1 when any row is degraded").option("--limit <n>","Maximum rows to return").action(async(options)=>{try{await observeAgentsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`observe: ${message}`),process.exit(2)}}),observe.on("command:*",(operands)=>{let cmd=operands[0],available=observe.commands.map((c)=>c.name()).join(", ");observe.error(`Unknown observe command '${cmd}'. Available: ${available}`)})}init_genie_config2();import{generateKeyPairSync}from"crypto";import{chmodSync as chmodSync4,existsSync as existsSync70,mkdirSync as mkdirSync34,readFileSync as readFileSync45,writeFileSync as writeFileSync32}from"fs";import{hostname as osHostname}from"os";import{dirname as dirname27,join as join82,resolve as resolvePath5}from"path";function keyPaths2(){let home=process.env.GENIE_HOME??join82(process.env.HOME??"/root",".genie"),dir=join82(home,"keys");return{dir,privateKey:join82(dir,"genie-host.ed25519"),publicKey:join82(dir,"genie-host.ed25519.pub"),hostJson:join82(dir,"host.json")}}function assertNotInsideGitRepo(dir){let probe=resolvePath5(dir);for(let depth=0;depth<16;depth++){if(existsSync70(join82(probe,".git")))throw Error(`Refusing to write keys to ${dir} \u2014 it lives inside a git working tree (${join82(probe,".git")}). Set $GENIE_HOME to a path outside any git repo and re-run.`);let parent=dirname27(probe);if(parent===probe)return;probe=parent}}function generateAndPersistKeypair(paths){if(!existsSync70(paths.dir))mkdirSync34(paths.dir,{recursive:!0,mode:448});let{publicKey,privateKey}=generateKeyPairSync("ed25519"),rawPub=publicKey.export({format:"der",type:"spki"}),pubkeyB64Url=rawPub.subarray(rawPub.length-32).toString("base64url");return writeFileSync32(paths.privateKey,privateKey.export({format:"pem",type:"pkcs8"}),{mode:384}),writeFileSync32(paths.publicKey,pubkeyB64Url,{mode:420}),chmodSync4(paths.privateKey,384),{pubkeyB64Url}}function loadExistingPubkey(paths){if(!existsSync70(paths.publicKey))return null;return readFileSync45(paths.publicKey,"utf-8").trim()}function loadHostJson(paths){if(!existsSync70(paths.hostJson))return null;try{return JSON.parse(readFileSync45(paths.hostJson,"utf-8"))}catch{return null}}function writeHostJson(paths,record){writeFileSync32(paths.hostJson,`${JSON.stringify(record,null,2)}
4363
4364
  `,{mode:420})}async function resolveOmniApiKey2(){let envKey=process.env.OMNI_API_KEY;if(envKey)return envKey;return(await loadGenieConfig()).omni?.apiKey}async function callTrustEndpoint(apiUrl,apiKey,method,path3,body){let headers={"Content-Type":"application/json"};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;let res=await fetch(`${apiUrl.replace(/\/+$/,"")}/api/v2/trust${path3}`,{method,headers,body:body===void 0?void 0:JSON.stringify(body)});if(!res.ok){let text=await res.text().catch(()=>"");throw Error(`omni trust ${method} ${path3}: HTTP ${res.status}${text?` \u2014 ${text}`:""}`)}return await res.json()}async function handleHandshake(options){let apiUrl=await resolveOmniApiUrl();if(!apiUrl)throw Error("Omni is not configured. Set OMNI_API_URL or `omni.apiUrl` in your genie config first.\nExample: omni install");let apiKey=await resolveOmniApiKey2();if(!apiKey)throw Error("Omni API key not configured. Set OMNI_API_KEY or `omni.apiKey` in your genie config.");let paths=keyPaths2();assertNotInsideGitRepo(paths.dir);let previousRecord=loadHostJson(paths),pubkey=loadExistingPubkey(paths);if(options.rotate){if(!previousRecord)throw Error("Cannot --rotate: no existing host record at ~/.genie/keys/host.json. Run a plain handshake first.");pubkey=generateAndPersistKeypair(paths).pubkeyB64Url}else if(!pubkey)pubkey=generateAndPersistKeypair(paths).pubkeyB64Url;let hostname2=options.hostname??previousRecord?.hostname??osHostname()??"unknown-host",capabilities={genieVersion:process.env.GENIE_VERSION??"unknown",platform:process.platform,nodeVersion:process.version},{data:host}=await callTrustEndpoint(apiUrl,apiKey,"POST","/handshake",{pubkey,hostname:hostname2,capabilities}),newRecord={hostId:host.id,pubkey:host.pubkey,hostname:host.hostname,registeredAt:new Date().toISOString(),...options.rotate&&previousRecord?{rotatedFrom:previousRecord.hostId}:{}};if(writeHostJson(paths,newRecord),options.rotate&&previousRecord&&previousRecord.hostId!==host.id)try{await callTrustEndpoint(apiUrl,apiKey,"DELETE",`/hosts/${previousRecord.hostId}`)}catch(err){let message=err instanceof Error?err.message:String(err);console.warn(`
4364
4365
  \u26A0 Rotated key registered as ${host.id}, but revoking the old host (${previousRecord.hostId}) failed:
4365
4366
  ${message}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260505.1",
3
+ "version": "4.260505.3",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
5
5
  "type": "module",
6
6
  "bin": {
@@ -98,6 +98,13 @@
98
98
  "access": "public"
99
99
  },
100
100
  "trustedDependencies": ["@biomejs/biome", "bun"],
101
+ "binarySha256": {
102
+ "_comment": "SHA-256 pins for postinstall-downloaded binaries. Keys = exact tarball filename. Bootstrap procedure: docs/_internal/binary-sha-bootstrap.md. Drift detection: .github/workflows/binary-sha-drift.yml.",
103
+ "tmux-3.6a-linux-x86_64.tar.gz": "c0a772a5e6ca8f129b0111d10029a52e02bcbc8352d5a8c0d3de8466a1e59c2e",
104
+ "tmux-3.6a-linux-arm64.tar.gz": "bb5afd9d646df54a7d7c66e198aa22c7d293c7453534f1670f7c540534db8b5e",
105
+ "tmux-3.6a-macos-arm64.tar.gz": "12b5b9f8696e1286897d946649c0a80d0169dd76e018d34476a1fbd34de89a0f",
106
+ "tmux-3.6a-macos-x86_64.tar.gz": "b9b12eaeba43acf5671acf3857d947525440b544185a8db34ea557199a090251"
107
+ },
101
108
  "pgserve": {
102
109
  "persist": true
103
110
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260505.1",
3
+ "version": "4.260505.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"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260505.1",
3
+ "version": "4.260505.3",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -97,6 +97,12 @@ async function main() {
97
97
  return;
98
98
  }
99
99
 
100
+ // Local-compile path is exempt from binarySha256 pinning (we don't download
101
+ // the binary — we build it ourselves from sources we already trust). Log the
102
+ // exact source artifact so operators can verify by other means
103
+ // (sha256sum on the entry, or `genie doctor --verbose` post-install).
104
+ console.error(`[genie] hook-binary: compiling from ${ENTRY} via ${bun}`);
105
+
100
106
  // bun build --compile produces a platform-specific static binary that
101
107
  // includes a copy of the bun runtime + the bundled JS. Targets the user's
102
108
  // current platform; no cross-compile arguments needed.
@@ -10,15 +10,61 @@
10
10
  */
11
11
 
12
12
  import { spawnSync } from 'node:child_process';
13
- import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
13
+ import { createHash } from 'node:crypto';
14
+ import {
15
+ chmodSync,
16
+ copyFileSync,
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ rmSync,
21
+ unlinkSync,
22
+ writeFileSync,
23
+ } from 'node:fs';
14
24
  import { arch, homedir, platform, tmpdir } from 'node:os';
15
- import { join } from 'node:path';
25
+ import { dirname, join, resolve as resolvePath } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
16
27
 
17
28
  const TMUX_VERSION = '3.6a';
18
29
  const GENIE_HOME = process.env.GENIE_HOME || join(homedir(), '.genie');
19
30
  const BIN_DIR = join(GENIE_HOME, 'bin');
20
31
  const TMUX_PATH = join(BIN_DIR, 'tmux');
21
32
 
33
+ // Resolve our own package.json so we can read the binarySha256 pin block.
34
+ const __dirname = dirname(fileURLToPath(import.meta.url));
35
+ const PKG_JSON_PATH = resolvePath(__dirname, '..', 'package.json');
36
+
37
+ /**
38
+ * Look up the pinned SHA-256 for a downloaded asset. Returns:
39
+ * - { kind: 'pinned', sha256 } — pin found, MUST verify
40
+ * - { kind: 'unpinned' } — no pin block present (older install or local dev)
41
+ * - { kind: 'missing-key' } — pin block present but this asset key is absent (treat as fail)
42
+ *
43
+ * Pinning is mandatory in the wished design: a missing key for a downloaded
44
+ * asset is a hard failure, not a soft warning. The only soft case is when
45
+ * binarySha256 is entirely absent (e.g. running this script standalone outside
46
+ * the published package).
47
+ */
48
+ function getPinnedSha(assetName) {
49
+ let pkg;
50
+ try {
51
+ pkg = JSON.parse(readFileSync(PKG_JSON_PATH, 'utf-8'));
52
+ } catch (err) {
53
+ return { kind: 'unpinned', reason: `cannot read ${PKG_JSON_PATH}: ${err.message}` };
54
+ }
55
+ const block = pkg?.binarySha256;
56
+ if (!block || typeof block !== 'object') return { kind: 'unpinned' };
57
+ const sha = block[assetName];
58
+ if (typeof sha === 'string' && /^[a-f0-9]{64}$/i.test(sha)) {
59
+ return { kind: 'pinned', sha256: sha.toLowerCase() };
60
+ }
61
+ return { kind: 'missing-key' };
62
+ }
63
+
64
+ function sha256OfBuffer(buf) {
65
+ return createHash('sha256').update(buf).digest('hex');
66
+ }
67
+
22
68
  function getPlatformAsset() {
23
69
  const os = platform();
24
70
  const cpu = arch();
@@ -73,6 +119,35 @@ async function downloadTmux() {
73
119
  const sizeMB = (buffer.byteLength / 1024 / 1024).toFixed(1);
74
120
  console.error(`[genie] Downloaded ${sizeMB} MB`);
75
121
 
122
+ // SHA-256 verification BEFORE writing to disk and BEFORE chmod +x.
123
+ // Pin-or-fail: a tampered tarball or a download corruption aborts the
124
+ // install with a clear message. Same code path that produced empty
125
+ // bin/bun on 2026-05-05 is the path a tarball-swap attack would exploit.
126
+ const pin = getPinnedSha(asset);
127
+ const actual = sha256OfBuffer(buffer);
128
+ if (pin.kind === 'pinned') {
129
+ if (actual.toLowerCase() !== pin.sha256) {
130
+ console.error(
131
+ `[genie] Error: ${asset} SHA-256 mismatch — expected ${pin.sha256}, got ${actual}. Aborting install.`,
132
+ );
133
+ return false;
134
+ }
135
+ console.error(`[genie] SHA-256 verified: ${actual.slice(0, 12)}…`);
136
+ } else if (pin.kind === 'missing-key') {
137
+ console.error(`[genie] Error: ${asset} has no SHA-256 pin in package.json#binarySha256. Aborting install.`);
138
+ console.error(
139
+ '[genie] Add the expected SHA-256 to the binarySha256 block. See .genie/wishes/dep-hygiene-and-resilience/binary-sha-bootstrap.md.',
140
+ );
141
+ return false;
142
+ } else {
143
+ // Unpinned (e.g. running this script outside a published package). Soft warning;
144
+ // operators running from a clean clone may not have computed pins yet.
145
+ console.error(
146
+ `[genie] Warning: no binarySha256 block found in package.json — skipping SHA verification for ${asset}.`,
147
+ );
148
+ console.error('[genie] This path is for local-dev only; published installs must pin.');
149
+ }
150
+
76
151
  mkdirSync(tempDir, { recursive: true });
77
152
  const tarball = join(tempDir, asset);
78
153
  writeFileSync(tarball, buffer);
@@ -201,7 +201,7 @@ Report orchestrates multiple tools but must **never modify source code** — inv
201
201
 
202
202
  ```bash
203
203
  # Spawn a tracer subagent for investigation
204
- genie agent spawn tracer
204
+ genie agent spawn trace
205
205
  ```
206
206
 
207
207
  Browser dispatch uses direct `agent-browser` commands alongside the trace subagent.
@@ -240,7 +240,7 @@ The report agent:
240
240
  # Agent asks: "What did you see?" → "Engineers show welcome screen but empty prompt"
241
241
 
242
242
  # 2. Run /trace
243
- genie agent spawn tracer
243
+ genie agent spawn trace
244
244
  genie agent send 'Trace: genie work dispatches engineers but they start idle. Check dispatch.ts and protocol-router.ts.' --to tracer
245
245
  # Wait for diagnosis...
246
246
 
@@ -39,7 +39,7 @@ Trace must run in **isolation** — the subagent must not modify any source file
39
39
 
40
40
  ```bash
41
41
  # Spawn a tracer subagent (read-only investigation)
42
- genie agent spawn tracer
42
+ genie agent spawn trace
43
43
  ```
44
44
 
45
45
  ## Task Lifecycle Integration (v4)
@@ -63,7 +63,7 @@ An engineer reports that `genie work` dispatches engineers but they sit idle. Th
63
63
 
64
64
  ```bash
65
65
  # 1. Spawn a tracer (read-only — no code changes)
66
- genie agent spawn tracer
66
+ genie agent spawn trace
67
67
 
68
68
  # 2. Send the symptoms
69
69
  genie agent send 'Trace: genie work dispatches engineers but they start idle at the prompt. No task received. genie wish status shows in_progress but nothing happens. Check dispatch.ts workDispatchCommand and protocol-router.ts sendMessage.' --to tracer