@automagik/genie 4.260428.2 → 4.260428.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js
CHANGED
|
@@ -1093,7 +1093,7 @@ Run 'genie agent list' to list agents.`)}async function resolveTarget(target,opt
|
|
|
1093
1093
|
`),recentLines=lines.slice(-linesToAnalyze).join(`
|
|
1094
1094
|
`),cleanOutput=stripAnsi(recentLines),baseState={timestamp:Date.now(),rawOutput:recentLines},permissionMatch=getFirstMatch(cleanOutput,permissionPatterns);if(permissionMatch)return{...baseState,type:"permission",detail:permissionMatch.type.replace("_permission",""),confidence:0.9};let hasPlanApproval=hasMatch(cleanOutput,questionPatterns.filter((p)=>p.type==="claude_code_plan_approval")),cleanMenuLines=stripAnsi(lines.slice(-15).join(`
|
|
1095
1095
|
`)),questionMatches=matchPatterns(cleanMenuLines,questionPatterns),questionState=detectQuestionState(questionMatches,hasPlanApproval,baseState);if(questionState)return questionState;let ynMatch=questionMatches.find((m)=>m.type==="yes_no_question");if(ynMatch)return{...baseState,type:"question",options:["Yes","No"],detail:`default: ${ynMatch.extracted?.default||"y"}`,confidence:0.85};let errorMatch=getFirstMatch(cleanOutput,errorPatterns);if(errorMatch)return{...baseState,type:"error",detail:errorMatch.extracted?.message||errorMatch.match[0],confidence:0.8};let toolMatch=getFirstMatch(cleanOutput,toolUsePatterns);if(toolMatch)return{...baseState,type:"tool_use",detail:`${toolMatch.type}: ${toolMatch.extracted?.command||toolMatch.extracted?.file||toolMatch.extracted?.query||""}`,confidence:0.75};if(hasMatch(cleanOutput,workingPatterns))return{...baseState,type:"working",confidence:0.7};if(hasMatch(cleanOutput,completionPatterns))return{...baseState,type:"complete",confidence:0.6};let cleanLastLines=stripAnsi(lines.slice(-5).join(`
|
|
1096
|
-
`));if(hasMatch(cleanLastLines,idlePatterns))return{...baseState,type:"idle",confidence:0.7};let trimmedLast=cleanLastLines.trim();if(trimmedLast.endsWith(">")||trimmedLast.match(/>\s*$/))return{...baseState,type:"idle",detail:"prompt detected",confidence:0.65};return{...baseState,type:"unknown",confidence:minConfidence}}var init_state_detector=__esm(()=>{init_patterns()});var init_orchestrator=__esm(()=>{init_patterns();init_state_detector()});
|
|
1096
|
+
`));if(hasMatch(cleanLastLines,idlePatterns))return{...baseState,type:"idle",confidence:0.7};let trimmedLast=cleanLastLines.trim();if(trimmedLast.endsWith(">")||trimmedLast.match(/>\s*$/))return{...baseState,type:"idle",detail:"prompt detected",confidence:0.65};return{...baseState,type:"unknown",confidence:minConfidence}}var init_state_detector=__esm(()=>{init_patterns()});var init_orchestrator=__esm(()=>{init_patterns();init_state_detector()});function rng(){return crypto.getRandomValues(rnds8)}var rnds8;var init_rng=__esm(()=>{rnds8=new Uint8Array(16)});function unsafeStringify(arr,offset=0){return(byteToHex[arr[offset+0]]+byteToHex[arr[offset+1]]+byteToHex[arr[offset+2]]+byteToHex[arr[offset+3]]+"-"+byteToHex[arr[offset+4]]+byteToHex[arr[offset+5]]+"-"+byteToHex[arr[offset+6]]+byteToHex[arr[offset+7]]+"-"+byteToHex[arr[offset+8]]+byteToHex[arr[offset+9]]+"-"+byteToHex[arr[offset+10]]+byteToHex[arr[offset+11]]+byteToHex[arr[offset+12]]+byteToHex[arr[offset+13]]+byteToHex[arr[offset+14]]+byteToHex[arr[offset+15]]).toLowerCase()}var byteToHex;var init_stringify=__esm(()=>{byteToHex=[];for(let i2=0;i2<256;++i2)byteToHex.push((i2+256).toString(16).slice(1))});function v4(options,buf,offset){if(!buf&&!options&&crypto.randomUUID)return crypto.randomUUID();return _v4(options,buf,offset)}function _v4(options,buf,offset){options=options||{};let rnds=options.random??options.rng?.()??rng();if(rnds.length<16)throw Error("Random bytes length must be >= 16");if(rnds[6]=rnds[6]&15|64,rnds[8]=rnds[8]&63|128,buf){if(offset=offset||0,offset<0||offset+16>buf.length)throw RangeError(`UUID byte range ${offset}:${offset+15} is out of buffer bounds`);for(let i2=0;i2<16;++i2)buf[offset+i2]=rnds[i2];return buf}return unsafeStringify(rnds)}var v4_default;var init_v4=__esm(()=>{init_rng();init_stringify();v4_default=v4});var init_dist_node=__esm(()=>{init_v4()});var exports_mailbox={};__export(exports_mailbox,{toNativeInboxMessage:()=>toNativeInboxMessage,subscribeDelivery:()=>subscribeDelivery,send:()=>send,readOutbox:()=>readOutbox,markRead:()=>markRead,markFailed:()=>markFailed,markEscalated:()=>markEscalated,markDelivered:()=>markDelivered,inbox:()=>inbox,getUnread:()=>getUnread,getRetryable:()=>getRetryable,getById:()=>getById});function generateMessageId(){return`msg-${v4_default()}`}function rowToMessage(row){return{id:row.id,from:row.from_worker,to:row.to_worker,body:row.body,createdAt:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at),read:row.read,deliveredAt:row.delivered_at?row.delivered_at instanceof Date?row.delivered_at.toISOString():String(row.delivered_at):null}}function normalizeWorkerIds(worker){let values2=Array.isArray(worker)?worker:[worker];return[...new Set(values2.map((value)=>value.trim()).filter(Boolean))]}async function send(repoPath,from,to,body){let sql=await getConnection(),id=generateMessageId(),now=new Date().toISOString(),span=isWideEmitEnabled()?startSpan("mailbox.delivery",{from,to,channel:"tmux",message_id:id},{source_subsystem:"mailbox",ctx:getAmbient()??void 0,repo_path:repoPath,agent:from}):null,outcome="queued";try{await sql`
|
|
1097
1097
|
INSERT INTO mailbox (id, from_worker, to_worker, body, repo_path, read, delivered_at, created_at)
|
|
1098
1098
|
VALUES (${id}, ${from}, ${to}, ${body}, ${repoPath}, false, ${null}, ${now})
|
|
1099
1099
|
`,outcome="queued"}catch(err){if(outcome="rejected",span)endSpan(span,{outcome:"rejected",body_excerpt:body.slice(0,256)},{source_subsystem:"mailbox",repo_path:repoPath,agent:from});throw err}let message={id,from,to,body,createdAt:now,read:!1,deliveredAt:null};try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:message.id,from,to},source:"mailbox",timestamp:message.createdAt})}catch{}if(span)endSpan(span,{outcome,body_excerpt:body.slice(0,256),message_id:id},{source_subsystem:"mailbox",repo_path:repoPath,agent:from});return message}async function inbox(repoPath,workerId){let sql=await getConnection(),workerIds=normalizeWorkerIds(workerId);if(workerIds.length===0)return[];return(await sql`
|
|
@@ -1134,7 +1134,7 @@ Run 'genie agent list' to list agents.`)}async function resolveTarget(target,opt
|
|
|
1134
1134
|
SET delivery_status = 'escalated'
|
|
1135
1135
|
WHERE id = ${messageId}
|
|
1136
1136
|
RETURNING id
|
|
1137
|
-
`).length>0}async function subscribeDelivery(callback){let listener=await(await getConnection()).listen("genie_mailbox_delivery",(payload)=>{let[toWorker,messageId]=payload.split(":");if(toWorker&&messageId)callback(toWorker,messageId)});return async()=>{await listener.unlisten()}}var init_mailbox=__esm(()=>{
|
|
1137
|
+
`).length>0}async function subscribeDelivery(callback){let listener=await(await getConnection()).listen("genie_mailbox_delivery",(payload)=>{let[toWorker,messageId]=payload.split(":");if(toWorker&&messageId)callback(toWorker,messageId)});return async()=>{await listener.unlisten()}}var init_mailbox=__esm(()=>{init_dist_node();init_db();init_emit();init_trace_context()});var exports_brief={};__export(exports_brief,{generateBrief:()=>generateBrief,formatBrief:()=>formatBrief});async function resolveSince(options){if(options.since)return options.since;if(options.agent){let rows=await(await getConnection())`
|
|
1138
1138
|
SELECT e.ended_at FROM executors e
|
|
1139
1139
|
JOIN agents a ON e.agent_id = a.id
|
|
1140
1140
|
WHERE a.custom_name = ${options.agent}
|
|
@@ -1481,7 +1481,7 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
|
|
|
1481
1481
|
AND actor_id = ${actor.actorId}
|
|
1482
1482
|
AND channel = ${channel}
|
|
1483
1483
|
`).count>0}async function listTasksForActor(actor,filters={}){let sql=await getConnection(),conditions=[],values2=[],paramIdx=1;if(filters.projectName)conditions.push(`t.project_id = (SELECT id FROM projects WHERE name = $${paramIdx++})`),values2.push(filters.projectName);else if(filters.allProjects);else conditions.push(`t.repo_path = $${paramIdx++}`),values2.push(filters.repoPath??getRepoPath());if(conditions.push(`ta.actor_type = $${paramIdx++}`),values2.push(actor.actorType),conditions.push(`ta.actor_id = $${paramIdx++}`),values2.push(actor.actorId),filters.stage)conditions.push(`t.stage = $${paramIdx++}`),values2.push(filters.stage);if(filters.status)conditions.push(`t.status = $${paramIdx++}`),values2.push(filters.status);else if(!filters.includeArchived)conditions.push("t.status != 'archived'");if(filters.priority)conditions.push(`t.priority = $${paramIdx++}`),values2.push(filters.priority);let limit=filters.limit??100,offset=filters.offset??0;values2.push(limit,offset);let query=`SELECT DISTINCT t.* FROM tasks t JOIN task_actors ta ON ta.task_id = t.id WHERE ${conditions.join(" AND ")} ORDER BY t.created_at DESC LIMIT $${paramIdx++} OFFSET $${paramIdx++}`;return(await sql.unsafe(query,values2)).map(mapTask)}var init_task_service=__esm(()=>{init_genie_tokens();init_audit();init_db()});import{createHash as createHash3}from"crypto";function parseRoutingHeader(text){if(!text)return null;let match=text.split(`
|
|
1484
|
-
`)[0].trim().match(/^\[(.+)\]$/);if(!match)return null;let pairs2=match[1].split(/\s+/),fields={};for(let pair of pairs2){let colonIdx=pair.indexOf(":");if(colonIdx<=0)continue;let key=pair.slice(0,colonIdx),value=pair.slice(colonIdx+1);if(key&&value)fields[key]=value}for(let field of REQUIRED_FIELDS)if(!fields[field])return null;if(fields.type!=="dm"&&fields.type!=="group")return null;return{channel:fields.channel,instance:fields.instance,chat:fields.chat,msg:fields.msg,from:fields.from,type:fields.type,thread:fields.thread,replyTo:fields.replyTo}}function shortHash(input){return createHash3("sha256").update(input).digest("hex").slice(0,8)}function resolveSessionKey(agentName,header){let chatId=shortHash(`${header.channel}-${header.instance}-${header.chat}`),base=`${agentName}-${chatId}`;if(header.thread)return`${base}-${header.thread}`;return base}var REQUIRED_FIELDS;var init_routing_header=__esm(()=>{REQUIRED_FIELDS=["channel","instance","chat","msg","from","type"]});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync4}from"child_process";import{createHash as createHash4,randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync31}from"fs";import{basename as basename5,join as join36}from"path";function shortPathHash(p){return createHash4("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join36(process.cwd(),"AGENTS.md");if(existsSync31(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,leaderName,sessionId,resume){return buildTeamLeadCommand(teamName,{systemPromptFile,leaderName,sessionId,resume})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"running",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let sessionId=randomUUID6(),shouldResume=!1;if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,sessionId,!1);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,sessionId,shouldResume);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID6();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,freshId,!1);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let sessionId=randomUUID6(),shouldResume=!1;await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let sessionId=randomUUID6(),shouldResume=!1;await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync4(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync10,readFileSync:readFileSync20,writeFileSync:writeFileSync13}=await import("fs"),{join:join37}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join37(process.env.HOME??"/root",".claude","teams"),teams=readdirSync10(teamsDir);for(let team of teams)try{let configPath2=join37(teamsDir,team,"config.json"),raw=readFileSync20(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync13(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let sessionId=randomUUID6(),suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,leaderName,sessionId,!1),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join36(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_should_resume={};__export(exports_should_resume,{shouldResume:()=>shouldResume,emitBootPassEvent:()=>emitBootPassEvent,classifyBootPass:()=>classifyBootPass,bootPassEventType:()=>bootPassEventType,bootPassDecisions:()=>bootPassDecisions,BOOT_PASS_CONCURRENCY_CAP:()=>BOOT_PASS_CONCURRENCY_CAP});async function readAgentResumeRow(agentId){return(await(await getConnection())`
|
|
1484
|
+
`)[0].trim().match(/^\[(.+)\]$/);if(!match)return null;let pairs2=match[1].split(/\s+/),fields={};for(let pair of pairs2){let colonIdx=pair.indexOf(":");if(colonIdx<=0)continue;let key=pair.slice(0,colonIdx),value=pair.slice(colonIdx+1);if(key&&value)fields[key]=value}for(let field of REQUIRED_FIELDS)if(!fields[field])return null;if(fields.type!=="dm"&&fields.type!=="group")return null;return{channel:fields.channel,instance:fields.instance,chat:fields.chat,msg:fields.msg,from:fields.from,type:fields.type,thread:fields.thread,replyTo:fields.replyTo}}function shortHash(input){return createHash3("sha256").update(input).digest("hex").slice(0,8)}function resolveSessionKey(agentName,header){let chatId=shortHash(`${header.channel}-${header.instance}-${header.chat}`),base=`${agentName}-${chatId}`;if(header.thread)return`${base}-${header.thread}`;return base}var REQUIRED_FIELDS;var init_routing_header=__esm(()=>{REQUIRED_FIELDS=["channel","instance","chat","msg","from","type"]});var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync as spawnSync4}from"child_process";import{createHash as createHash4,randomUUID as randomUUID5}from"crypto";import{existsSync as existsSync31}from"fs";import{basename as basename5,join as join36}from"path";function shortPathHash(p){return createHash4("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join36(process.cwd(),"AGENTS.md");if(existsSync31(agentsPath))return agentsPath;return null}async function resolveSessionLeaderName(teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}async function ensureNativeTeamForLeader(teamName,cwd,sessionId){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:basename5(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,leaderName,sessionId,resume){return buildTeamLeadCommand(teamName,{systemPromptFile,leaderName,sessionId,resume})}async function registerSessionInRegistry(sessionName,windowName,workspaceDir){try{let target=`${sessionName}:${windowName}`,paneId=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_id}'`)).trim(),now=new Date().toISOString(),sanitized=sanitizeTeamName(windowName),leaderName=await resolveSessionLeaderName(windowName),sanitizedLeader=sanitizeTeamName(leaderName);await register({id:`${sanitized}-${sanitizedLeader}`,paneId,session:sessionName,team:windowName,role:leaderName,worktree:null,startedAt:now,state:"working",lastStateChange:now,repoPath:workspaceDir,provider:"claude",transport:"tmux",nativeTeamEnabled:!0,nativeAgentId:`${sanitizedLeader}@${sanitized}`});let agentIdentity=await findOrCreateAgent(leaderName,sanitized,leaderName),pid=null;try{let pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"running",repoPath:workspaceDir})}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename5(cwd));if(!await findWindowByName(sessionName,baseName))return baseName;if(await getWindowEnv(`${sessionName}:${baseName}`,"GENIE_CWD")===cwd)return baseName;return`${baseName}-${shortPathHash(cwd)}`}async function createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName){let sessionId=randomUUID5(),shouldResume=!1;if(await ensureNativeTeamForLeader(windowName,workspaceDir,sessionId),console.log(`Native team "${windowName}" ready at ~/.claude/teams/${sanitizeTeamName(windowName)}/`),console.log(`Creating session "${sessionName}"...`),!await createSession(sessionName))console.error(`Failed to create session "${sessionName}"`),process.exit(1);let firstWindow=(await listWindows(sessionName))[0];if(!firstWindow)console.error(`Failed to find initial window in session "${sessionName}"`),process.exit(1);await executeTmux2(`rename-window -t ${shellQuote(firstWindow.id)} ${shellQuote(windowName)}`),await executeTmux2(`set-window-option -t ${shellQuote(firstWindow.id)} automatic-rename off`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workspaceDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workspaceDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let agentName=basename5(workspaceDir),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,sessionId,!1);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,workspaceDir,systemPromptFile,leaderName,sessionId,shouldResume){let cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,sessionId,shouldResume);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),shouldResume){await new Promise((r)=>setTimeout(r,3000));let afterCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(afterCmd)){console.log("Resume failed unexpectedly, starting fresh session...");let freshId=randomUUID5();await ensureNativeTeamForLeader(windowName,workspaceDir,freshId);let freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,leaderName,freshId,!1);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile,leaderName){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir);let sessionId=randomUUID5(),shouldResume=!1;await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume),console.log(`Started Claude Code as ${basename5(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}else{let target=`${sessionName}:${windowName}`,currentCmd=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();if(["bash","zsh","sh","fish"].includes(currentCmd)){console.log(`Claude Code not running in "${windowName}", relaunching...`);let sessionId=randomUUID5(),shouldResume=!1;await ensureNativeTeamForLeader(windowName,workingDir,sessionId);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,workingDir,systemPromptFile,leaderName,sessionId,shouldResume);try{let sanitized=sanitizeTeamName(windowName),leaderName2=await resolveSessionLeaderName(windowName),agentIdentity=await findOrCreateAgent(leaderName2,sanitized,leaderName2);await terminateActiveExecutor(agentIdentity.id)}catch{}await registerSessionInRegistry(sessionName,windowName,workingDir)}}await executeTmux2(`select-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),console.log(`Focused team window "${windowName}"`)}function sanitizeWindowName(name){return name.replace(/\./g,"-")}async function deriveWindowName(sessionName,workspaceDir,team){if(team)return sanitizeWindowName(team);if(await findSessionByName(sessionName))return sanitizeWindowName(await resolveWindowName(sessionName,workspaceDir));return sanitizeWindowName(basename5(workspaceDir))}async function handleReset(sessionName,windowName){let existing=await findSessionByName(sessionName);if(existing){let windows=await listWindows(existing.id);console.log(`Resetting session "${sessionName}"...`),await killSession(existing.id),await Promise.all(windows.map((w)=>deleteNativeTeam(w.name)))}else await deleteNativeTeam(windowName)}function attachToWindow(sessionName,windowName){console.log("Attaching...");let target=`${sessionName}:${windowName}`,cmd=process.env.TMUX?"switch-client":"attach",{genieTmuxPrefix:genieTmuxPrefix2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper)),{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));spawnSync4(tmuxBin2(),[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function reconcileLeaderConfigs(){try{let{readdirSync:readdirSync10,readFileSync:readFileSync20,writeFileSync:writeFileSync13}=await import("fs"),{join:join37}=await import("path"),{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamsDir=join37(process.env.HOME??"/root",".claude","teams"),teams=readdirSync10(teamsDir);for(let team of teams)try{let configPath2=join37(teamsDir,team,"config.json"),raw=readFileSync20(configPath2,"utf-8"),config=JSON.parse(raw);if(config.leadAgentId?.startsWith("team-lead@")){let actualLeader=await resolveLeaderName2(team),sanitized=sanitizeTeamName(team);config.leadAgentId=`${sanitizeTeamName(actualLeader)}@${sanitized}`,writeFileSync13(configPath2,JSON.stringify(config,null,2)),console.log(`[reconcile] Updated leadAgentId for team "${team}": ${config.leadAgentId}`)}}catch{}}catch{}}async function launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName){let sessionId=randomUUID5(),suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir,sessionId);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,leaderName,sessionId,!1),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}async function sessionCommand(options={}){await reconcileStaleSpawns(),await reconcileLeaderConfigs();let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename5(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team),leaderName=await resolveSessionLeaderName(windowName);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default4({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join36(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName);else if(process.env.TMUX)await launchInsideTmux(windowName,workspaceDir,systemPromptFile,leaderName);else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile,leaderName),attachToWindow(sessionName,windowName)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}var init_session=__esm(()=>{init_esm14();init_agent_registry();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_should_resume={};__export(exports_should_resume,{shouldResume:()=>shouldResume,emitBootPassEvent:()=>emitBootPassEvent,classifyBootPass:()=>classifyBootPass,bootPassEventType:()=>bootPassEventType,bootPassDecisions:()=>bootPassDecisions,BOOT_PASS_CONCURRENCY_CAP:()=>BOOT_PASS_CONCURRENCY_CAP});async function readAgentResumeRow(agentId){return(await(await getConnection())`
|
|
1485
1485
|
SELECT
|
|
1486
1486
|
a.id,
|
|
1487
1487
|
a.auto_resume,
|
|
@@ -1502,7 +1502,7 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
|
|
|
1502
1502
|
) AS latest_assignment_outcome
|
|
1503
1503
|
FROM agents a
|
|
1504
1504
|
WHERE a.id = ${agentId}
|
|
1505
|
-
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID7}from"crypto";import{existsSync as existsSync32}from"fs";import{join as join37}from"path";function getSystemPromptFile(workingDir){let agentsPath=join37(workingDir,"AGENTS.md");if(existsSync32(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null,sessionId=priorSessionId??randomUUID7(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir26}from"os";import{join as join38}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join38(homedir26(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join38(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1505
|
+
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync32}from"fs";import{join as join37}from"path";function getSystemPromptFile(workingDir){let agentsPath=join37(workingDir,"AGENTS.md");if(existsSync32(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null,sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir26}from"os";import{join as join38}from"path";async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join38(homedir26(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join38(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1506
1506
|
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1507
1507
|
Examples:
|
|
1508
1508
|
genie send 'start task #3' --to engineer # Message a specific agent
|
|
@@ -2035,7 +2035,7 @@ Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let conte
|
|
|
2035
2035
|
[${time}] SYSTEM:`,entry2.text];default:return[]}}function formatFullConversation(entries){return entries.flatMap(formatTranscriptEntryForDisplay).join(`
|
|
2036
2036
|
`)}async function findWorker(identifier){let worker=await get(identifier);if(worker)return worker;if(worker=await findByTask(identifier),worker)return worker;return(await list()).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function resolveContext(workerIdOrName,options){if(options.logFile)return{worker:{id:"direct",paneId:"",session:"",worktree:null,startedAt:new Date().toISOString(),state:"idle",lastStateChange:new Date().toISOString(),repoPath:process.cwd(),provider:"claude"},workerId:"direct",provider:"claude",duration:"N/A"};let worker=await findWorker(workerIdOrName);if(!worker)console.error(`Agent "${workerIdOrName}" not found. Run \`genie agent list\` to see agents.`),process.exit(1);let elapsed=getElapsedTime(worker);return{worker,workerId:worker.id,provider:worker.provider??"claude",branch:worker.worktree?`work/${worker.taskId}`:void 0,duration:elapsed.formatted}}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.after)filter.since=options.after,hasFilter=!0;if(options.type)filter.roles=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function filterSinceExchanges(entries,since){let userCount=0;for(let i2=entries.length-1;i2>=0;i2--)if(entries[i2].role==="user"){if(userCount++,userCount>=since)return entries.slice(i2)}return entries}async function loadEntries(ctx,options){let{readTranscript:readTranscript2,getProvider:getProvider3}=await Promise.resolve().then(() => exports_transcript);if(options.logFile)return(await getProvider3(ctx.worker)).readEntries(options.logFile);return readTranscript2(ctx.worker)}function filterEntries(entries,options){let{applyFilter:applyFilter2}=__toCommonJS(exports_transcript),filtered=options.since&&options.since>0?filterSinceExchanges(entries,options.since):entries,transcriptFilter=buildFilter2(options);if(transcriptFilter)filtered=applyFilter2(filtered,transcriptFilter);return filtered}function outputEntries(filtered,options){if(options.ndjson){for(let entry2 of filtered){let{raw:_raw,...rest}=entry2;console.log(JSON.stringify(options.raw?entry2:rest))}return!0}if(options.raw){for(let entry2 of filtered)console.log(JSON.stringify(entry2.raw));return!0}if(options.full)return console.log(formatFullConversation(filtered)),!0;return!1}async function historyCommand(workerIdOrName,options){let ctx=await resolveContext(workerIdOrName,options),entries=await loadEntries(ctx,options);if(entries.length===0)console.error(`No transcript found for agent "${ctx.workerId}".`),process.exit(1);let filtered=filterEntries(entries,options);if(outputEntries(filtered,options))return;let events=extractEvents(filtered),toolCallCount=entries.filter((e)=>e.role==="tool_call").length,userMessageCount=entries.filter((e)=>e.role==="user").length,stats2={workerId:ctx.workerId,taskId:ctx.workerId,branch:ctx.branch,provider:ctx.provider,duration:ctx.duration,totalEntries:entries.length,compressedLines:events.length,compressionRatio:entries.length/Math.max(events.length,1),exchanges:userMessageCount,toolCalls:toolCallCount,status:detectStatus(entries)};if(options.json){console.log(JSON.stringify({stats:stats2,events},null,2));return}console.log(formatEventsForDisplay(events,stats2))}var init_history=__esm(()=>{init_agent_registry();init_term_format()});var exports_frontmatter_writer={};__export(exports_frontmatter_writer,{writeFrontmatter:()=>writeFrontmatter,serializeSdkConfig:()=>serializeSdkConfig});import{readFileSync as readFileSync21,writeFileSync as writeFileSync13}from"fs";function writeFrontmatter(filePath,updates){let content=readFileSync21(filePath,"utf-8"),{yamlObj,body}=splitFrontmatter(content),merged={...yamlObj,...stripUndefined(updates)},output=`---
|
|
2037
2037
|
${dump(merged,{lineWidth:-1,noRefs:!0,sortKeys:!1,quotingType:'"'})}---
|
|
2038
|
-
${body}`;writeFileSync13(filePath,output,"utf-8")}function serializeSdkConfig(sdk){let result2={};for(let[key,value]of Object.entries(sdk)){if(value===void 0||value===null)continue;if(Array.isArray(value)&&value.length===0)continue;if(typeof value==="object"&&!Array.isArray(value)&&Object.keys(value).length===0)continue;result2[key]=value}return result2}function splitFrontmatter(content){let match=content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!match)return{yamlObj:{},body:content};let yamlStr=match[1],body=match[2],yamlObj={};try{let parsed=load(yamlStr);if(typeof parsed==="object"&&parsed!==null)yamlObj=parsed}catch{}return{yamlObj,body}}function stripUndefined(obj){let result2={};for(let[key,value]of Object.entries(obj))if(value!==void 0)result2[key]=value;return result2}var init_frontmatter_writer=__esm(()=>{init_js_yaml()});var exports_tui_disable={};__export(exports_tui_disable,{noticeTuiSkipped:()=>noticeTuiSkipped,isTuiDisabled:()=>isTuiDisabled});function isTuiDisabled(){let envVal=process.env.GENIE_TUI_DISABLE;if(envVal&&TRUTHY.has(envVal.trim().toLowerCase()))return!0;if(process.argv.includes("--no-tui"))return!0;return!1}function noticeTuiSkipped(context){let reason=process.env.GENIE_TUI_DISABLE?"GENIE_TUI_DISABLE is set":"--no-tui flag present";console.error(`genie: TUI ${context} skipped (${reason}). See https://github.com/automagik-dev/genie for status of the upstream OpenTUI kqueue spin on macOS.`)}var TRUTHY;var init_tui_disable=__esm(()=>{TRUTHY=new Set(["1","true","yes","on"])});var exports_service_registry={};__export(exports_service_registry,{unregisterService:()=>unregisterService,registerService:()=>registerService,reapDeadServices:()=>reapDeadServices,killAllServices:()=>killAllServices,getRegisteredServices:()=>getRegisteredServices,clearRegistry:()=>clearRegistry});function registerService(name,pid){registry.set(name,{pid,name,startedAt:new Date})}function unregisterService(name){registry.delete(name)}function getRegisteredServices(){return Array.from(registry.values())}function reapDeadServices(){let reaped=[];for(let[name,entry2]of registry)try{process.kill(entry2.pid,0)}catch{registry.delete(name),reaped.push(name)}return reaped}function killAllServices(){for(let[_name,entry2]of registry)try{process.kill(entry2.pid,"SIGTERM")}catch{registry.delete(_name)}let deadline=Date.now()+3000,checkInterval=200,stillAlive=()=>{for(let[,entry2]of registry)try{return process.kill(entry2.pid,0),!0}catch{}return!1};while(Date.now()<deadline&&stillAlive()){let waitUntil=Date.now()+checkInterval;while(Date.now()<waitUntil);}for(let[name,entry2]of registry){try{process.kill(entry2.pid,0),process.kill(entry2.pid,"SIGKILL")}catch{}registry.delete(name)}}function clearRegistry(){registry.clear()}var registry;var init_service_registry=__esm(()=>{registry=new Map});function parseDuration(input){let match=input.trim().match(DURATION_RE);if(!match)throw Error(`Invalid duration: "${input}". Expected format: 10m, 2h, 24h, 1d`);let value=Number.parseFloat(match[1]),unit=match[2].toLowerCase(),ms=value*{s:1000,sec:1000,m:60000,min:60000,h:3600000,hr:3600000,d:86400000,day:86400000}[unit];if(ms<=0)throw Error(`Duration must be positive: "${input}"`);return ms}function expandRange(range,step,min,max){if(step===0)throw Error("Cron step value cannot be 0");if(range==="*"){let out=[];for(let i2=min;i2<=max;i2+=step)out.push(i2);return out}if(range.includes("-")){let[start,end]=range.split("-").map(Number),out=[];for(let i2=start;i2<=end;i2+=step)out.push(i2);return out}return[Number.parseInt(range,10)]}function parseCronField(field,min,max){let values2=new Set;for(let part of field.split(",")){let stepMatch=part.match(/^(.+)\/(\d+)$/),step=stepMatch?Number.parseInt(stepMatch[2],10):1,range=stepMatch?stepMatch[1]:part;for(let v of expandRange(range,step,min,max))values2.add(v)}return[...values2].sort((a,b2)=>a-b2)}function getTimeParts(date,tz){if(!tz)return{month:date.getMonth()+1,dom:date.getDate(),dow:date.getDay(),hour:date.getHours(),minute:date.getMinutes()};let parts=new Intl.DateTimeFormat("en-US",{timeZone:tz,year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short",hour12:!1}).formatToParts(date),get3=(type2)=>Number(parts.find((p)=>p.type===type2)?.value??0),dayMap={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},weekday=parts.find((p)=>p.type==="weekday")?.value??"Sun";return{month:get3("month"),dom:get3("day"),dow:dayMap[weekday]??0,hour:get3("hour")===24?0:get3("hour"),minute:get3("minute")}}function parseOpts(afterOrOpts){if(afterOrOpts instanceof Date)return{after:afterOrOpts};if(afterOrOpts)return{after:afterOrOpts.after,timezone:afterOrOpts.timezone};return{}}function advanceToNextDay(candidate,tz){candidate.setTime(candidate.getTime()+86400000);let tp=getTimeParts(candidate,tz);candidate.setTime(candidate.getTime()-tp.hour*3600000-tp.minute*60000)}function parseCronExpr(cronExpr){let parts=cronExpr.trim().split(/\s+/);if(parts.length<5)throw Error(`Invalid cron expression: "${cronExpr}"`);let[minField,hourField,domField,monthField,dowField]=parts;return{minutes:parseCronField(minField,0,59),hours:parseCronField(hourField,0,23),doms:parseCronField(domField,1,31),months:parseCronField(monthField,1,12),dows:parseCronField(dowField,0,6),domRestricted:domField!=="*",dowRestricted:dowField!=="*"}}function computeNextCronDue(cronExpr,afterOrOpts){let{after,timezone}=parseOpts(afterOrOpts),cron=parseCronExpr(cronExpr),candidate=new Date((after??new Date).getTime());candidate.setSeconds(0,0),candidate.setTime(candidate.getTime()+60000);let limit=new Date(candidate.getTime()+31622400000);while(candidate<=limit){let tp=getTimeParts(candidate,timezone);if(!cron.months.includes(tp.month)){advanceToNextDay(candidate,timezone);continue}if(!(cron.domRestricted&&cron.dowRestricted?cron.doms.includes(tp.dom)||cron.dows.includes(tp.dow):cron.doms.includes(tp.dom)&&cron.dows.includes(tp.dow))){advanceToNextDay(candidate,timezone);continue}if(!cron.hours.includes(tp.hour)){candidate.setTime(candidate.getTime()+3600000-tp.minute*60000);continue}if(cron.minutes.includes(tp.minute))return candidate;candidate.setTime(candidate.getTime()+60000)}throw Error(`No next cron occurrence found for "${cronExpr}" within 366 days`)}var DURATION_RE;var init_cron=__esm(()=>{DURATION_RE=/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i});import{randomUUID as
|
|
2038
|
+
${body}`;writeFileSync13(filePath,output,"utf-8")}function serializeSdkConfig(sdk){let result2={};for(let[key,value]of Object.entries(sdk)){if(value===void 0||value===null)continue;if(Array.isArray(value)&&value.length===0)continue;if(typeof value==="object"&&!Array.isArray(value)&&Object.keys(value).length===0)continue;result2[key]=value}return result2}function splitFrontmatter(content){let match=content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!match)return{yamlObj:{},body:content};let yamlStr=match[1],body=match[2],yamlObj={};try{let parsed=load(yamlStr);if(typeof parsed==="object"&&parsed!==null)yamlObj=parsed}catch{}return{yamlObj,body}}function stripUndefined(obj){let result2={};for(let[key,value]of Object.entries(obj))if(value!==void 0)result2[key]=value;return result2}var init_frontmatter_writer=__esm(()=>{init_js_yaml()});var exports_tui_disable={};__export(exports_tui_disable,{noticeTuiSkipped:()=>noticeTuiSkipped,isTuiDisabled:()=>isTuiDisabled});function isTuiDisabled(){let envVal=process.env.GENIE_TUI_DISABLE;if(envVal&&TRUTHY.has(envVal.trim().toLowerCase()))return!0;if(process.argv.includes("--no-tui"))return!0;return!1}function noticeTuiSkipped(context){let reason=process.env.GENIE_TUI_DISABLE?"GENIE_TUI_DISABLE is set":"--no-tui flag present";console.error(`genie: TUI ${context} skipped (${reason}). See https://github.com/automagik-dev/genie for status of the upstream OpenTUI kqueue spin on macOS.`)}var TRUTHY;var init_tui_disable=__esm(()=>{TRUTHY=new Set(["1","true","yes","on"])});var exports_service_registry={};__export(exports_service_registry,{unregisterService:()=>unregisterService,registerService:()=>registerService,reapDeadServices:()=>reapDeadServices,killAllServices:()=>killAllServices,getRegisteredServices:()=>getRegisteredServices,clearRegistry:()=>clearRegistry});function registerService(name,pid){registry.set(name,{pid,name,startedAt:new Date})}function unregisterService(name){registry.delete(name)}function getRegisteredServices(){return Array.from(registry.values())}function reapDeadServices(){let reaped=[];for(let[name,entry2]of registry)try{process.kill(entry2.pid,0)}catch{registry.delete(name),reaped.push(name)}return reaped}function killAllServices(){for(let[_name,entry2]of registry)try{process.kill(entry2.pid,"SIGTERM")}catch{registry.delete(_name)}let deadline=Date.now()+3000,checkInterval=200,stillAlive=()=>{for(let[,entry2]of registry)try{return process.kill(entry2.pid,0),!0}catch{}return!1};while(Date.now()<deadline&&stillAlive()){let waitUntil=Date.now()+checkInterval;while(Date.now()<waitUntil);}for(let[name,entry2]of registry){try{process.kill(entry2.pid,0),process.kill(entry2.pid,"SIGKILL")}catch{}registry.delete(name)}}function clearRegistry(){registry.clear()}var registry;var init_service_registry=__esm(()=>{registry=new Map});function parseDuration(input){let match=input.trim().match(DURATION_RE);if(!match)throw Error(`Invalid duration: "${input}". Expected format: 10m, 2h, 24h, 1d`);let value=Number.parseFloat(match[1]),unit=match[2].toLowerCase(),ms=value*{s:1000,sec:1000,m:60000,min:60000,h:3600000,hr:3600000,d:86400000,day:86400000}[unit];if(ms<=0)throw Error(`Duration must be positive: "${input}"`);return ms}function expandRange(range,step,min,max){if(step===0)throw Error("Cron step value cannot be 0");if(range==="*"){let out=[];for(let i2=min;i2<=max;i2+=step)out.push(i2);return out}if(range.includes("-")){let[start,end]=range.split("-").map(Number),out=[];for(let i2=start;i2<=end;i2+=step)out.push(i2);return out}return[Number.parseInt(range,10)]}function parseCronField(field,min,max){let values2=new Set;for(let part of field.split(",")){let stepMatch=part.match(/^(.+)\/(\d+)$/),step=stepMatch?Number.parseInt(stepMatch[2],10):1,range=stepMatch?stepMatch[1]:part;for(let v of expandRange(range,step,min,max))values2.add(v)}return[...values2].sort((a,b2)=>a-b2)}function getTimeParts(date,tz){if(!tz)return{month:date.getMonth()+1,dom:date.getDate(),dow:date.getDay(),hour:date.getHours(),minute:date.getMinutes()};let parts=new Intl.DateTimeFormat("en-US",{timeZone:tz,year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short",hour12:!1}).formatToParts(date),get3=(type2)=>Number(parts.find((p)=>p.type===type2)?.value??0),dayMap={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},weekday=parts.find((p)=>p.type==="weekday")?.value??"Sun";return{month:get3("month"),dom:get3("day"),dow:dayMap[weekday]??0,hour:get3("hour")===24?0:get3("hour"),minute:get3("minute")}}function parseOpts(afterOrOpts){if(afterOrOpts instanceof Date)return{after:afterOrOpts};if(afterOrOpts)return{after:afterOrOpts.after,timezone:afterOrOpts.timezone};return{}}function advanceToNextDay(candidate,tz){candidate.setTime(candidate.getTime()+86400000);let tp=getTimeParts(candidate,tz);candidate.setTime(candidate.getTime()-tp.hour*3600000-tp.minute*60000)}function parseCronExpr(cronExpr){let parts=cronExpr.trim().split(/\s+/);if(parts.length<5)throw Error(`Invalid cron expression: "${cronExpr}"`);let[minField,hourField,domField,monthField,dowField]=parts;return{minutes:parseCronField(minField,0,59),hours:parseCronField(hourField,0,23),doms:parseCronField(domField,1,31),months:parseCronField(monthField,1,12),dows:parseCronField(dowField,0,6),domRestricted:domField!=="*",dowRestricted:dowField!=="*"}}function computeNextCronDue(cronExpr,afterOrOpts){let{after,timezone}=parseOpts(afterOrOpts),cron=parseCronExpr(cronExpr),candidate=new Date((after??new Date).getTime());candidate.setSeconds(0,0),candidate.setTime(candidate.getTime()+60000);let limit=new Date(candidate.getTime()+31622400000);while(candidate<=limit){let tp=getTimeParts(candidate,timezone);if(!cron.months.includes(tp.month)){advanceToNextDay(candidate,timezone);continue}if(!(cron.domRestricted&&cron.dowRestricted?cron.doms.includes(tp.dom)||cron.dows.includes(tp.dow):cron.doms.includes(tp.dom)&&cron.dows.includes(tp.dow))){advanceToNextDay(candidate,timezone);continue}if(!cron.hours.includes(tp.hour)){candidate.setTime(candidate.getTime()+3600000-tp.minute*60000);continue}if(cron.minutes.includes(tp.minute))return candidate;candidate.setTime(candidate.getTime()+60000)}throw Error(`No next cron occurrence found for "${cronExpr}" within 366 days`)}var DURATION_RE;var init_cron=__esm(()=>{DURATION_RE=/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|d|day)s?$/i});import{randomUUID as randomUUID7}from"crypto";function parseNotifyPayload(channel,raw){switch(channel){case"genie_task_stage":{let parts=raw.split(":");if(parts.length<3)return null;return{channel,eventType:"task.stage_change",payload:{taskId:parts[0],fromStage:parts[1],toStage:parts[2]},taskId:parts[0],summary:`Task ${parts[0]} moved from ${parts[1]} to ${parts[2]}`}}case"genie_executor_state":{let parts=raw.split(":");if(parts.length<4)return null;let eventType=parts[3]==="error"?"executor.error":"executor.state_change";return{channel,eventType,payload:{executorId:parts[0],agentId:parts[1],oldState:parts[2],newState:parts[3]},agentId:parts[1],summary:`${parts[1]} state: ${parts[2]} \u2192 ${parts[3]}`}}case"genie_message":{let parts=raw.split(":");if(parts.length<2)return null;return{channel,eventType:"task.comment",payload:{messageId:parts[0],conversationId:parts[1]},summary:`New message in conversation ${parts[1]}`}}case"genie_audit_event":{let parts=raw.split(":");if(parts.length<3)return null;return{channel,eventType:`${parts[0]}.${parts[2]}`,payload:{entityType:parts[0],entityId:parts[1],auditEventType:parts[2]},summary:`${parts[0]} ${parts[1]}: ${parts[2]}`}}default:return null}}function isActionableEvent(eventType){return ACTIONABLE_EVENTS.has(eventType)||eventType.startsWith("request.")}async function resolveTargetTeams(event){if(!isActionableEvent(event.eventType))return[];let active=(await listTeams()).filter((t)=>t.status==="in_progress");if(event.agentId){let aid=event.agentId;return active.filter((t)=>t.members.includes(aid)||t.leader===aid).map((t)=>t.name)}return active.map((t)=>t.name)}async function writeMailbox(repoPath,leader,message,traceId){try{await(await getConnection())`
|
|
2039
2039
|
INSERT INTO mailbox (id, repo_path, "from", "to", body, trace_id, created_at)
|
|
2040
2040
|
VALUES (${`mail-${traceId}`}, ${repoPath}, 'system', ${leader}, ${message}, ${traceId}, now())
|
|
2041
2041
|
`}catch{try{await send(repoPath,"system",leader,message)}catch{}}}async function deliverToHierarchy(leader,teamName,message,traceId){let sql=await getConnection(),current=leader,visited=new Set([current]);for(;;){let reportsTo=(await sql`
|
|
@@ -2048,13 +2048,13 @@ ${body}`;writeFileSync13(filePath,output,"utf-8")}function serializeSdkConfig(sd
|
|
|
2048
2048
|
AND a.team = ${teamName}
|
|
2049
2049
|
AND e.state NOT IN ('terminated', 'done', 'error')
|
|
2050
2050
|
LIMIT 1
|
|
2051
|
-
`;if(rows.length>0){let provider=getProvider(rows[0].provider);if(provider?.deliverMessage)await provider.deliverMessage(rows[0].executor_id,{text:message,traceId})}}async function deliverToTeamOnce(event,teamName){let team=await getTeam(teamName);if(!team)return;let traceId=randomUUID8(),message=`[${event.eventType}] ${event.summary}`;if(event.taskId){let systemActor={actorType:"local",actorId:"system"};try{await commentOnTask(event.taskId,systemActor,message,team.repo)}catch{}}if(team.leader)await writeMailbox(team.repo,team.leader,message,traceId),await deliverViaProvider(team.leader,teamName,message,traceId),await deliverToHierarchy(team.leader,teamName,message,traceId);await publishRuntimeEvent({repoPath:team.repo,kind:"system",agent:event.agentId??"system",team:teamName,text:event.summary,source:"hook",threadId:event.taskId?`task:${event.taskId}`:`team:${teamName}`,traceId,data:{channel:event.channel,eventType:event.eventType,...event.payload}})}async function deliverToTeam2(event,teamName){if(!CRITICAL_EVENTS.has(event.eventType)){try{await deliverToTeamOnce(event,teamName)}catch{}return}for(let attempt=1;attempt<=MAX_RETRIES;attempt++)try{await deliverToTeamOnce(event,teamName);return}catch{if(attempt<MAX_RETRIES){let backoff2=BASE_BACKOFF_MS*2**(attempt-1);await new Promise((r)=>setTimeout(r,backoff2))}}}async function routeEvent(event,handler){if(handler){await handler(event);return}let targets=await resolveTargetTeams(event);for(let teamName of targets)await deliverToTeam2(event,teamName)}async function startEventRouter(handler){if(process.env.GENIE_ENABLE_EVENT_ROUTING==="false")return{stop:async()=>{}};let sql=await getConnection(),listeners=[];for(let channel of CHANNELS){let listener=await sql.listen(channel,(payload)=>{let event=parseNotifyPayload(channel,payload);if(!event)return;routeEvent(event,handler).catch(()=>{})});listeners.push(listener)}return{stop:async()=>{for(let listener of listeners)await listener.unlisten()}}}var ACTIONABLE_EVENTS,CRITICAL_EVENTS,MAX_RETRIES=3,BASE_BACKOFF_MS=500,CHANNELS;var init_event_router=__esm(()=>{init_db();init_mailbox();init_registry2();init_runtime_events();init_task_service();init_team_manager();ACTIONABLE_EVENTS=new Set(["task.blocked","executor.error","executor.permission"]),CRITICAL_EVENTS=new Set(["task.blocked","executor.error","executor.permission"]);CHANNELS=["genie_task_stage","genie_executor_state","genie_message","genie_audit_event"]});function validateRunSpec(input){if(!input.command||input.command.trim().length===0)throw Error("RunSpec.command is required and cannot be empty");if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms<1e4)throw Error(`RunSpec.lease_timeout_ms must be >= 10000ms, got ${input.lease_timeout_ms}`);if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms>3600000)throw Error(`RunSpec.lease_timeout_ms must be <= 3600000ms (1h), got ${input.lease_timeout_ms}`);if(input.provider&&!VALID_PROVIDERS.has(input.provider))throw Error(`RunSpec.provider must be 'claude' or 'codex', got '${input.provider}'`);if(input.ref_policy&&!VALID_REF_POLICIES.has(input.ref_policy))throw Error(`RunSpec.ref_policy must be 'current' or 'default', got '${input.ref_policy}'`);if(input.approval_policy&&!VALID_APPROVAL_POLICIES.has(input.approval_policy))throw Error(`RunSpec.approval_policy must be 'auto' or 'manual', got '${input.approval_policy}'`)}function resolveRunSpec(input){return validateRunSpec(input),{repo:input.repo??DEFAULTS.repo,ref_policy:input.ref_policy??DEFAULTS.ref_policy,provider:input.provider??DEFAULTS.provider,role:input.role??DEFAULTS.role,model:input.model??DEFAULTS.model,command:input.command.trim(),approval_policy:input.approval_policy??DEFAULTS.approval_policy,concurrency_class:input.concurrency_class??DEFAULTS.concurrency_class,lease_timeout_ms:input.lease_timeout_ms??DEFAULTS.lease_timeout_ms}}var DEFAULTS,VALID_PROVIDERS,VALID_REF_POLICIES,VALID_APPROVAL_POLICIES;var init_run_spec=__esm(()=>{DEFAULTS={repo:process.cwd(),ref_policy:"current",provider:"claude",role:"worker",model:"",approval_policy:"auto",concurrency_class:"default",lease_timeout_ms:300000},VALID_PROVIDERS=new Set(["claude","codex"]),VALID_REF_POLICIES=new Set(["current","default"]),VALID_APPROVAL_POLICIES=new Set(["auto","manual"])});var exports_session_filewatch={};__export(exports_session_filewatch,{stopFilewatch:()=>stopFilewatch,startFilewatch:()=>startFilewatch,resetUnrecoverableSessions:()=>resetUnrecoverableSessions,isForeignKeyViolation:()=>isForeignKeyViolation,handleFileChange:()=>handleFileChange});import{watch}from"fs";import{homedir as homedir29}from"os";import{basename as basename6,join as join44}from"path";function resetUnrecoverableSessions(){unrecoverableSessions.clear(),offsetCache.clear()}function isForeignKeyViolation(err){if(!err||typeof err!=="object")return!1;let code=err.code;if(typeof code==="string"&&code==="23503")return!0;return(err instanceof Error?err.message:String(err)).toLowerCase().includes("foreign key constraint")}async function loadOffsets(sql){try{let rows=await sql`SELECT id, last_ingested_offset FROM sessions WHERE last_ingested_offset > 0`;for(let row of rows)offsetCache.set(row.id,row.last_ingested_offset)}catch{}}function extractSessionInfo(filePath){if(!filePath.endsWith(".jsonl"))return null;let sessionId=basename6(filePath,".jsonl"),parts=filePath.split("/"),sessionsIdx=parts.lastIndexOf("sessions"),subagentsIdx=parts.lastIndexOf("subagents");if(subagentsIdx>0&&parts[subagentsIdx-1]){let parentSessionId=parts[subagentsIdx-1],projectIdx=parts.indexOf("projects"),projectPath=projectIdx>=0?parts.slice(0,projectIdx+2).join("/"):"";return{sessionId,projectPath,parentSessionId,isSubagent:!0}}if(sessionsIdx>0){let projectPath=parts.slice(0,sessionsIdx).join("/");return{sessionId,projectPath,parentSessionId:null,isSubagent:!1}}return null}async function handleFileChange(filePath,sql,deps=defaultDeps4){let info=extractSessionInfo(filePath);if(!info)return;if(unrecoverableSessions.has(info.sessionId))return;let storedOffset=offsetCache.get(info.sessionId)??0;try{deps.setLiveWorkPending(!0);let workerMap=await deps.buildWorkerMap(sql),result2=await deps.ingestFileFull(sql,info.sessionId,filePath,info.projectPath,storedOffset,{parentSessionId:info.parentSessionId,isSubagent:info.isSubagent,workerMap});offsetCache.set(info.sessionId,result2.newOffset)}catch(err){let message=err instanceof Error?err.message:String(err);if(isForeignKeyViolation(err))unrecoverableSessions.add(info.sessionId),offsetCache.set(info.sessionId,Number.POSITIVE_INFINITY),deps.logError(`[filewatch] skipping ${filePath} \u2014 FK constraint violation (orphan session, parent not registered): ${message}`);else deps.logError(`[filewatch] error ingesting ${filePath} at offset ${storedOffset}: ${message}`)}finally{deps.setLiveWorkPending(!1)}}async function startFilewatch(sql){if(watcher)return!0;let claudeDir=join44(process.env.CLAUDE_CONFIG_DIR??join44(homedir29(),".claude"),"projects");await loadOffsets(sql);try{return watcher=watch(claudeDir,{recursive:!0},(_eventType,filename)=>{if(!filename||!filename.endsWith(".jsonl"))return;let fullPath=join44(claudeDir,filename),existing=debounceTimers.get(fullPath);if(existing)clearTimeout(existing);debounceTimers.set(fullPath,setTimeout(()=>{debounceTimers.delete(fullPath),handleFileChange(fullPath,sql).catch((err)=>{let message=err instanceof Error?err.message:String(err);console.error(`[filewatch] unhandled error for ${fullPath}: ${message}`)})},DEBOUNCE_MS))}),watcher.on("error",(err)=>{console.error("[filewatch] watcher error:",err.message)}),console.log(`[filewatch] watching ${claudeDir} (${offsetCache.size} sessions cached)`),!0}catch(err){let message=err instanceof Error?err.message:String(err);return console.error(`[filewatch] failed to start: ${message}`),!1}}function stopFilewatch(){if(watcher)watcher.close(),watcher=null;for(let timer2 of debounceTimers.values())clearTimeout(timer2);debounceTimers.clear()}var watcher=null,offsetCache,debounceTimers,DEBOUNCE_MS=500,unrecoverableSessions,defaultDeps4;var init_session_filewatch=__esm(()=>{init_session_capture();offsetCache=new Map,debounceTimers=new Map,unrecoverableSessions=new Set;defaultDeps4={buildWorkerMap,ingestFileFull,setLiveWorkPending,logError:(msg)=>console.error(msg)}});var exports_scheduler_daemon={};__export(exports_scheduler_daemon,{terminalizeCleanExitUnverified:()=>terminalizeCleanExitUnverified,startDaemon:()=>startDaemon,runBootPass:()=>runBootPass,runAgentRecoveryPass:()=>runAgentRecoveryPass,recoverOnStartup:()=>recoverOnStartup,reconcileUnresumable:()=>reconcileUnresumable,reconcileOrphans:()=>reconcileOrphans,reconcileOrphanedRuns:()=>reconcileOrphanedRuns,reclaimExpiredLeases:()=>reclaimExpiredLeases,processMailboxRetryMessage:()=>processMailboxRetryMessage,logToFile:()=>logToFile,logReconcilerMode:()=>logReconcilerMode,isTurnAwareReconcilerEnabled:()=>isTurnAwareReconcilerEnabled,fireTrigger:()=>fireTrigger,emitWorkerEvents:()=>emitWorkerEvents,collectMachineSnapshot:()=>collectMachineSnapshot,collectHeartbeats:()=>collectHeartbeats,claimDueTriggers:()=>claimDueTriggers,attemptAgentResume:()=>attemptAgentResume,_resetWorkerStatesForTesting:()=>_resetWorkerStatesForTesting,TURN_AWARE_RECONCILER_FLAG:()=>TURN_AWARE_RECONCILER_FLAG,MAX_DELIVERY_ATTEMPTS:()=>MAX_DELIVERY_ATTEMPTS,ESCALATION_RECIPIENT:()=>ESCALATION_RECIPIENT});import{randomUUID as randomUUID9}from"crypto";import{appendFileSync as appendFileSync3,mkdirSync as mkdirSync15}from"fs";import{homedir as homedir30}from"os";import{join as join45}from"path";function isTurnAwareReconcilerEnabled(env=process.env){let raw=env[TURN_AWARE_RECONCILER_FLAG];if(raw===void 0)return!0;let v=raw.trim().toLowerCase();if(v==="")return!0;if(v==="0"||v==="false"||v==="no")return!1;if(v==="1"||v==="true"||v==="yes")return!0;return!0}function logReconcilerMode(deps,daemonId){let enabled=isTurnAwareReconcilerEnabled();deps.log({timestamp:deps.now().toISOString(),level:"info",event:enabled?"reconciler_mode_turn_aware":"reconciler_mode_legacy",daemon_id:daemonId,flag:TURN_AWARE_RECONCILER_FLAG,enabled,message:enabled?"turn-aware reconciler enabled":"flag off, using legacy reconciler"})}function getLogDir2(){return join45(process.env.GENIE_HOME??join45(homedir30(),".genie"),"logs")}function getLogFile(){return join45(getLogDir2(),"scheduler.log")}function logToFile(entry2){let logDir=getLogDir2();mkdirSync15(logDir,{recursive:!0});let enriched=entry2.trace_id?entry2:withAmbientTraceId(entry2);appendFileSync3(getLogFile(),`${JSON.stringify(enriched)}
|
|
2051
|
+
`;if(rows.length>0){let provider=getProvider(rows[0].provider);if(provider?.deliverMessage)await provider.deliverMessage(rows[0].executor_id,{text:message,traceId})}}async function deliverToTeamOnce(event,teamName){let team=await getTeam(teamName);if(!team)return;let traceId=randomUUID7(),message=`[${event.eventType}] ${event.summary}`;if(event.taskId){let systemActor={actorType:"local",actorId:"system"};try{await commentOnTask(event.taskId,systemActor,message,team.repo)}catch{}}if(team.leader)await writeMailbox(team.repo,team.leader,message,traceId),await deliverViaProvider(team.leader,teamName,message,traceId),await deliverToHierarchy(team.leader,teamName,message,traceId);await publishRuntimeEvent({repoPath:team.repo,kind:"system",agent:event.agentId??"system",team:teamName,text:event.summary,source:"hook",threadId:event.taskId?`task:${event.taskId}`:`team:${teamName}`,traceId,data:{channel:event.channel,eventType:event.eventType,...event.payload}})}async function deliverToTeam2(event,teamName){if(!CRITICAL_EVENTS.has(event.eventType)){try{await deliverToTeamOnce(event,teamName)}catch{}return}for(let attempt=1;attempt<=MAX_RETRIES;attempt++)try{await deliverToTeamOnce(event,teamName);return}catch{if(attempt<MAX_RETRIES){let backoff2=BASE_BACKOFF_MS*2**(attempt-1);await new Promise((r)=>setTimeout(r,backoff2))}}}async function routeEvent(event,handler){if(handler){await handler(event);return}let targets=await resolveTargetTeams(event);for(let teamName of targets)await deliverToTeam2(event,teamName)}async function startEventRouter(handler){if(process.env.GENIE_ENABLE_EVENT_ROUTING==="false")return{stop:async()=>{}};let sql=await getConnection(),listeners=[];for(let channel of CHANNELS){let listener=await sql.listen(channel,(payload)=>{let event=parseNotifyPayload(channel,payload);if(!event)return;routeEvent(event,handler).catch(()=>{})});listeners.push(listener)}return{stop:async()=>{for(let listener of listeners)await listener.unlisten()}}}var ACTIONABLE_EVENTS,CRITICAL_EVENTS,MAX_RETRIES=3,BASE_BACKOFF_MS=500,CHANNELS;var init_event_router=__esm(()=>{init_db();init_mailbox();init_registry2();init_runtime_events();init_task_service();init_team_manager();ACTIONABLE_EVENTS=new Set(["task.blocked","executor.error","executor.permission"]),CRITICAL_EVENTS=new Set(["task.blocked","executor.error","executor.permission"]);CHANNELS=["genie_task_stage","genie_executor_state","genie_message","genie_audit_event"]});function validateRunSpec(input){if(!input.command||input.command.trim().length===0)throw Error("RunSpec.command is required and cannot be empty");if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms<1e4)throw Error(`RunSpec.lease_timeout_ms must be >= 10000ms, got ${input.lease_timeout_ms}`);if(input.lease_timeout_ms!==void 0&&input.lease_timeout_ms>3600000)throw Error(`RunSpec.lease_timeout_ms must be <= 3600000ms (1h), got ${input.lease_timeout_ms}`);if(input.provider&&!VALID_PROVIDERS.has(input.provider))throw Error(`RunSpec.provider must be 'claude' or 'codex', got '${input.provider}'`);if(input.ref_policy&&!VALID_REF_POLICIES.has(input.ref_policy))throw Error(`RunSpec.ref_policy must be 'current' or 'default', got '${input.ref_policy}'`);if(input.approval_policy&&!VALID_APPROVAL_POLICIES.has(input.approval_policy))throw Error(`RunSpec.approval_policy must be 'auto' or 'manual', got '${input.approval_policy}'`)}function resolveRunSpec(input){return validateRunSpec(input),{repo:input.repo??DEFAULTS.repo,ref_policy:input.ref_policy??DEFAULTS.ref_policy,provider:input.provider??DEFAULTS.provider,role:input.role??DEFAULTS.role,model:input.model??DEFAULTS.model,command:input.command.trim(),approval_policy:input.approval_policy??DEFAULTS.approval_policy,concurrency_class:input.concurrency_class??DEFAULTS.concurrency_class,lease_timeout_ms:input.lease_timeout_ms??DEFAULTS.lease_timeout_ms}}var DEFAULTS,VALID_PROVIDERS,VALID_REF_POLICIES,VALID_APPROVAL_POLICIES;var init_run_spec=__esm(()=>{DEFAULTS={repo:process.cwd(),ref_policy:"current",provider:"claude",role:"worker",model:"",approval_policy:"auto",concurrency_class:"default",lease_timeout_ms:300000},VALID_PROVIDERS=new Set(["claude","codex"]),VALID_REF_POLICIES=new Set(["current","default"]),VALID_APPROVAL_POLICIES=new Set(["auto","manual"])});var exports_session_filewatch={};__export(exports_session_filewatch,{stopFilewatch:()=>stopFilewatch,startFilewatch:()=>startFilewatch,resetUnrecoverableSessions:()=>resetUnrecoverableSessions,isForeignKeyViolation:()=>isForeignKeyViolation,handleFileChange:()=>handleFileChange});import{watch}from"fs";import{homedir as homedir29}from"os";import{basename as basename6,join as join44}from"path";function resetUnrecoverableSessions(){unrecoverableSessions.clear(),offsetCache.clear()}function isForeignKeyViolation(err){if(!err||typeof err!=="object")return!1;let code=err.code;if(typeof code==="string"&&code==="23503")return!0;return(err instanceof Error?err.message:String(err)).toLowerCase().includes("foreign key constraint")}async function loadOffsets(sql){try{let rows=await sql`SELECT id, last_ingested_offset FROM sessions WHERE last_ingested_offset > 0`;for(let row of rows)offsetCache.set(row.id,row.last_ingested_offset)}catch{}}function extractSessionInfo(filePath){if(!filePath.endsWith(".jsonl"))return null;let sessionId=basename6(filePath,".jsonl"),parts=filePath.split("/"),sessionsIdx=parts.lastIndexOf("sessions"),subagentsIdx=parts.lastIndexOf("subagents");if(subagentsIdx>0&&parts[subagentsIdx-1]){let parentSessionId=parts[subagentsIdx-1],projectIdx=parts.indexOf("projects"),projectPath=projectIdx>=0?parts.slice(0,projectIdx+2).join("/"):"";return{sessionId,projectPath,parentSessionId,isSubagent:!0}}if(sessionsIdx>0){let projectPath=parts.slice(0,sessionsIdx).join("/");return{sessionId,projectPath,parentSessionId:null,isSubagent:!1}}return null}async function handleFileChange(filePath,sql,deps=defaultDeps4){let info=extractSessionInfo(filePath);if(!info)return;if(unrecoverableSessions.has(info.sessionId))return;let storedOffset=offsetCache.get(info.sessionId)??0;try{deps.setLiveWorkPending(!0);let workerMap=await deps.buildWorkerMap(sql),result2=await deps.ingestFileFull(sql,info.sessionId,filePath,info.projectPath,storedOffset,{parentSessionId:info.parentSessionId,isSubagent:info.isSubagent,workerMap});offsetCache.set(info.sessionId,result2.newOffset)}catch(err){let message=err instanceof Error?err.message:String(err);if(isForeignKeyViolation(err))unrecoverableSessions.add(info.sessionId),offsetCache.set(info.sessionId,Number.POSITIVE_INFINITY),deps.logError(`[filewatch] skipping ${filePath} \u2014 FK constraint violation (orphan session, parent not registered): ${message}`);else deps.logError(`[filewatch] error ingesting ${filePath} at offset ${storedOffset}: ${message}`)}finally{deps.setLiveWorkPending(!1)}}async function startFilewatch(sql){if(watcher)return!0;let claudeDir=join44(process.env.CLAUDE_CONFIG_DIR??join44(homedir29(),".claude"),"projects");await loadOffsets(sql);try{return watcher=watch(claudeDir,{recursive:!0},(_eventType,filename)=>{if(!filename||!filename.endsWith(".jsonl"))return;let fullPath=join44(claudeDir,filename),existing=debounceTimers.get(fullPath);if(existing)clearTimeout(existing);debounceTimers.set(fullPath,setTimeout(()=>{debounceTimers.delete(fullPath),handleFileChange(fullPath,sql).catch((err)=>{let message=err instanceof Error?err.message:String(err);console.error(`[filewatch] unhandled error for ${fullPath}: ${message}`)})},DEBOUNCE_MS))}),watcher.on("error",(err)=>{console.error("[filewatch] watcher error:",err.message)}),console.log(`[filewatch] watching ${claudeDir} (${offsetCache.size} sessions cached)`),!0}catch(err){let message=err instanceof Error?err.message:String(err);return console.error(`[filewatch] failed to start: ${message}`),!1}}function stopFilewatch(){if(watcher)watcher.close(),watcher=null;for(let timer2 of debounceTimers.values())clearTimeout(timer2);debounceTimers.clear()}var watcher=null,offsetCache,debounceTimers,DEBOUNCE_MS=500,unrecoverableSessions,defaultDeps4;var init_session_filewatch=__esm(()=>{init_session_capture();offsetCache=new Map,debounceTimers=new Map,unrecoverableSessions=new Set;defaultDeps4={buildWorkerMap,ingestFileFull,setLiveWorkPending,logError:(msg)=>console.error(msg)}});var exports_scheduler_daemon={};__export(exports_scheduler_daemon,{terminalizeCleanExitUnverified:()=>terminalizeCleanExitUnverified,startDaemon:()=>startDaemon,runBootPass:()=>runBootPass,runAgentRecoveryPass:()=>runAgentRecoveryPass,recoverOnStartup:()=>recoverOnStartup,reconcileUnresumable:()=>reconcileUnresumable,reconcileOrphans:()=>reconcileOrphans,reconcileOrphanedRuns:()=>reconcileOrphanedRuns,reclaimExpiredLeases:()=>reclaimExpiredLeases,processMailboxRetryMessage:()=>processMailboxRetryMessage,logToFile:()=>logToFile,logReconcilerMode:()=>logReconcilerMode,isTurnAwareReconcilerEnabled:()=>isTurnAwareReconcilerEnabled,fireTrigger:()=>fireTrigger,emitWorkerEvents:()=>emitWorkerEvents,collectMachineSnapshot:()=>collectMachineSnapshot,collectHeartbeats:()=>collectHeartbeats,claimDueTriggers:()=>claimDueTriggers,attemptAgentResume:()=>attemptAgentResume,_resetWorkerStatesForTesting:()=>_resetWorkerStatesForTesting,TURN_AWARE_RECONCILER_FLAG:()=>TURN_AWARE_RECONCILER_FLAG,MAX_DELIVERY_ATTEMPTS:()=>MAX_DELIVERY_ATTEMPTS,ESCALATION_RECIPIENT:()=>ESCALATION_RECIPIENT});import{randomUUID as randomUUID8}from"crypto";import{appendFileSync as appendFileSync3,mkdirSync as mkdirSync15}from"fs";import{homedir as homedir30}from"os";import{join as join45}from"path";function isTurnAwareReconcilerEnabled(env=process.env){let raw=env[TURN_AWARE_RECONCILER_FLAG];if(raw===void 0)return!0;let v=raw.trim().toLowerCase();if(v==="")return!0;if(v==="0"||v==="false"||v==="no")return!1;if(v==="1"||v==="true"||v==="yes")return!0;return!0}function logReconcilerMode(deps,daemonId){let enabled=isTurnAwareReconcilerEnabled();deps.log({timestamp:deps.now().toISOString(),level:"info",event:enabled?"reconciler_mode_turn_aware":"reconciler_mode_legacy",daemon_id:daemonId,flag:TURN_AWARE_RECONCILER_FLAG,enabled,message:enabled?"turn-aware reconciler enabled":"flag off, using legacy reconciler"})}function getLogDir2(){return join45(process.env.GENIE_HOME??join45(homedir30(),".genie"),"logs")}function getLogFile(){return join45(getLogDir2(),"scheduler.log")}function logToFile(entry2){let logDir=getLogDir2();mkdirSync15(logDir,{recursive:!0});let enriched=entry2.trace_id?entry2:withAmbientTraceId(entry2);appendFileSync3(getLogFile(),`${JSON.stringify(enriched)}
|
|
2052
2052
|
`)}function withAmbientTraceId(entry2){let ctx=getAmbient();if(!ctx)return entry2;return{...entry2,trace_id:ctx.trace_id}}async function defaultSpawnCommand(command,env){return{pid:Bun.spawn(["sh","-c",command],{env:{...process.env,...env},stdio:["ignore","ignore","ignore"]}).pid}}function defaultJitter(maxMs){return Math.floor(Math.random()*maxMs)}function defaultSleep(ms){return new Promise((resolve6)=>setTimeout(resolve6,ms))}async function defaultIsPaneAlive(paneId){let{isPaneAlive:isPaneAlive2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));return isPaneAlive2(paneId)}async function defaultListWorkers(){let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),agents=await list2(),sessionByAgent=new Map;try{let rows=await(await getConnection2())`
|
|
2053
2053
|
SELECT a.id AS agent_id, e.claude_session_id
|
|
2054
2054
|
FROM agents a
|
|
2055
2055
|
LEFT JOIN executors e ON e.id = a.current_executor_id
|
|
2056
2056
|
`;sessionByAgent=new Map(rows.map((r)=>[r.agent_id,r.claude_session_id]))}catch{}return agents.map((a)=>({id:a.id,paneId:a.paneId,repoPath:a.repoPath,state:a.state,team:a.team,wishSlug:a.wishSlug,groupNumber:a.groupNumber,autoResume:a.autoResume,resumeAttempts:a.resumeAttempts,maxResumeAttempts:a.maxResumeAttempts,lastResumeAttempt:a.lastResumeAttempt,currentSessionId:sessionByAgent.get(a.id)??null}))}async function defaultPublishEvent(subject,data,repoPath){let payload=data,{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,subject,{timestamp:payload.timestamp,kind:payload.kind??"system",agent:payload.agent??"scheduler",team:payload.team,direction:payload.direction,peer:payload.peer,text:payload.text??subject,data:payload.data,source:payload.source??"registry"})}async function defaultCountTmuxSessions(){try{let{execSync:execSync10}=await import("child_process"),{genieTmuxCmd:genieTmuxCmd2}=await Promise.resolve().then(() => (init_tmux_wrapper(),exports_tmux_wrapper));return execSync10(`${genieTmuxCmd2("list-sessions")} 2>/dev/null`,{encoding:"utf-8"}).trim().split(`
|
|
2057
|
-
`).filter(Boolean).length}catch{return 0}}async function defaultResumeAgent(agentId){try{let{execSync:execSync10}=await import("child_process");return execSync10(`genie agent resume ${agentId} --no-reset-attempts`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}),!0}catch{return!1}}async function defaultUpdateAgent(agentId,updates){let{update:update2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await update2(agentId,updates)}function createDefaultDeps(){return{getConnection:async()=>{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()},spawnCommand:defaultSpawnCommand,log:logToFile,generateId:
|
|
2057
|
+
`).filter(Boolean).length}catch{return 0}}async function defaultResumeAgent(agentId){try{let{execSync:execSync10}=await import("child_process");return execSync10(`genie agent resume ${agentId} --no-reset-attempts`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}),!0}catch{return!1}}async function defaultUpdateAgent(agentId,updates){let{update:update2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await update2(agentId,updates)}function createDefaultDeps(){return{getConnection:async()=>{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()},spawnCommand:defaultSpawnCommand,log:logToFile,generateId:randomUUID8,now:()=>new Date,sleep:defaultSleep,jitter:defaultJitter,isPaneAlive:defaultIsPaneAlive,listWorkers:defaultListWorkers,countTmuxSessions:defaultCountTmuxSessions,publishEvent:defaultPublishEvent,resumeAgent:defaultResumeAgent,updateAgent:defaultUpdateAgent}}function resolveConfig(overrides){let envMax=process.env.GENIE_MAX_CONCURRENT,maxConcurrent=envMax?Number.parseInt(envMax,10):5;return{maxConcurrent:overrides?.maxConcurrent??(Number.isNaN(maxConcurrent)?5:maxConcurrent),pollIntervalMs:overrides?.pollIntervalMs??30000,maxJitterMs:overrides?.maxJitterMs??30000,jitterThreshold:overrides?.jitterThreshold??3,heartbeatIntervalMs:overrides?.heartbeatIntervalMs??60000,orphanCheckIntervalMs:overrides?.orphanCheckIntervalMs??300000,deadHeartbeatThreshold:overrides?.deadHeartbeatThreshold??2,leaseRecoveryIntervalMs:overrides?.leaseRecoveryIntervalMs??60000}}async function claimDueTriggers(deps,config,daemonId){let sql=await deps.getConnection(),now=deps.now(),leaseUntil=new Date(now.getTime()+300000),runningCount=(await sql`
|
|
2058
2058
|
SELECT count(*)::int AS cnt FROM runs
|
|
2059
2059
|
WHERE status IN ('leased', 'running')
|
|
2060
2060
|
`)[0]?.cnt??0,available=Math.max(0,config.maxConcurrent-runningCount);if(available===0)return deps.log({timestamp:now.toISOString(),level:"debug",event:"concurrency_cap_reached",running:runningCount,max:config.maxConcurrent}),[];let limit=Math.min(available,5),claimed=await sql.begin(async(tx)=>{let rows=await tx`
|
|
@@ -2349,11 +2349,11 @@ Flow: 1) \`omni say "..."\` to reply \u2192 2) \`omni done\` to close.
|
|
|
2349
2349
|
Bare text output goes nowhere \u2014 you MUST use omni verbs to reach the user.
|
|
2350
2350
|
|
|
2351
2351
|
The user's message:
|
|
2352
|
-
`.trim()}import{randomUUID as
|
|
2352
|
+
`.trim()}import{randomUUID as randomUUID9}from"crypto";import{homedir as homedir31}from"os";import{join as join47}from"path";function safeName(raw,maxLen=30){return raw.replace(/[^a-zA-Z0-9._-]/g,"").slice(0,maxLen)||"unknown"}function sanitizeWindowName2(chatId,chatName){let whatsappDm=chatId.match(/^(\d+)@s\.whatsapp\.net$/);if(whatsappDm)return`wa-${whatsappDm[1]}`;let whatsappGroup=chatId.match(/^(\d+)@g\.us$/);if(whatsappGroup)return`grp-${chatName?safeName(chatName):whatsappGroup[1]}`;let lid=chatId.match(/^(\d+)@lid$/);if(lid)return chatName?`wa-${safeName(chatName)}`:`lid-${lid[1]}`;return`chat-${safeName(chatId)}`}function resolveBridgeTmuxSession(agentName,entryBridgeTmuxSession,envOverride){return(envOverride||entryBridgeTmuxSession||agentName).replace(/[\/:]/g,"-")}async function lookupChatName(chatId,_instanceId){try{let configPath2=join47(homedir31(),".omni","config.json"),{readFileSync:readFileSync22}=await import("fs"),config=JSON.parse(readFileSync22(configPath2,"utf-8")),apiUrl=config.apiUrl||"http://localhost:8882",apiKey=config.apiKey||"";if(!apiKey)return null;let url=`${apiUrl}/api/v2/chats?externalId=${encodeURIComponent(chatId)}`,res=await fetch(url,{headers:{Authorization:`Bearer ${apiKey}`},signal:AbortSignal.timeout(3000)});if(!res.ok)return null;let body=await res.json();return(body.items?.find((c)=>c.externalId===chatId)??body.items?.[0])?.name||null}catch{return null}}function buildOmniSpawnParams(agentName,chatId,entry2,env,initialMessage){let instanceId=env.OMNI_INSTANCE??"",senderName=env.OMNI_SENDER_NAME??"whatsapp-user",turnContext=buildTurnBasedPrompt(senderName,instanceId,chatId),fullInitialPrompt=initialMessage?`${turnContext}
|
|
2353
2353
|
|
|
2354
2354
|
---
|
|
2355
2355
|
|
|
2356
|
-
${initialMessage}`:turnContext,permissions=entry2.permissions?.allow?.length||entry2.permissions?.deny?.length?{allow:entry2.permissions.allow,deny:entry2.permissions.deny}:void 0;return{provider:entry2.provider??"claude",team:agentName,role:agentName,sessionId:
|
|
2356
|
+
${initialMessage}`:turnContext,permissions=entry2.permissions?.allow?.length||entry2.permissions?.deny?.length?{allow:entry2.permissions.allow,deny:entry2.permissions.deny}:void 0;return{provider:entry2.provider??"claude",team:agentName,role:agentName,sessionId:randomUUID9(),model:entry2.model,promptMode:entry2.promptMode,systemPromptFile:join47(entry2.dir,"AGENTS.md"),initialPrompt:fullInitialPrompt,skipHooks:!0,permissions,disallowedTools:entry2.disallowedTools,nativeTeam:{enabled:!0,agentName,color:entry2.color??void 0}}}class ClaudeCodeOmniExecutor{sessions=new Map;safePgCall=null;setSafePgCall(fn){this.safePgCall=fn}setNatsPublish(_fn){}async injectNudge(session,text){let paneId=session.tmux?.paneId;if(!paneId)return;let nudgeText=`[system] ${text}`;await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(nudgeText)} Enter`)}async spawn(agentName,chatId,env,initialMessage){let resolved=await resolve5(agentName);if(!resolved)throw Error(`Agent "${agentName}" not found in genie directory`);let entry2=resolved.entry,tmuxSession=resolveBridgeTmuxSession(agentName,entry2.bridgeTmuxSession,env.GENIE_TMUX_SESSION),chatName=await lookupChatName(chatId,env.OMNI_INSTANCE??""),windowName=sanitizeWindowName2(chatId,chatName??void 0),{paneId,created}=await ensureTeamWindow(tmuxSession,windowName,entry2.dir);if(created){let omniEnv={...env,GENIE_OMNI_CHAT_ID:chatId,GENIE_OMNI_AGENT:agentName},params=buildOmniSpawnParams(agentName,chatId,entry2,omniEnv,initialMessage),launch=buildLaunchCommand(params),allEnv={...omniEnv,...launch.env},envPrefix=Object.entries(allEnv).map(([k,v])=>`${k}=${shellQuote(v)}`).join(" "),cmd=envPrefix?`${envPrefix} ${launch.command}`:launch.command;await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(cmd)} Enter`)}let sessionKey2=`${agentName}:${chatId}`,registration=await this.registerInWorldA(agentName,chatId,env.OMNI_INSTANCE??"",tmuxSession,windowName,paneId,entry2.dir);if(this.sessions.set(sessionKey2,{executorId:registration?.executorId??null,agentId:registration?.agentId??null,repoPath:entry2.dir}),registration?.executorId)await this.updateState(registration.executorId,"running",chatId);let now=Date.now();return{id:sessionKey2,agentName,chatId,executorType:"tmux",createdAt:now,lastActivityAt:now,tmux:{session:tmuxSession,window:windowName,paneId}}}async registerInWorldA(agentName,chatId,instanceId,tmuxSession,tmuxWindow,tmuxPaneId,repoPath){if(!this.safePgCall)return null;let agent=await this.safePgCall("tmux-find-or-create-agent",()=>findOrCreateAgent(`${agentName}:${chatId}`,"omni","omni"),null,{chatId});if(!agent)return null;await this.safePgCall("tmux-update-agent-pane",async()=>{await(await Promise.resolve().then(() => (init_db(),exports_db)).then((m)=>m.getConnection()))`
|
|
2357
2357
|
UPDATE agents
|
|
2358
2358
|
SET pane_id = ${tmuxPaneId},
|
|
2359
2359
|
session = ${tmuxSession},
|
|
@@ -2431,7 +2431,7 @@ Shutting down genie serve...`),await stopSchedulerHandles(),await stopOmniAndBra
|
|
|
2431
2431
|
genie serve is running (${mode}). ${headless?"Send SIGTERM to stop.":"Press Ctrl+C to stop."}`);let{shutdown:shutdown2,hasStarted}=buildShutdownFn(headless);if(installGracefulExitHandlers(shutdown2,hasStarted),handles.schedulerHandle)await handles.schedulerHandle.done;else await new Promise(()=>{});removeServePid()}async function startBackground(headless){let existingEntry=readServePid();if(existingEntry&&isProcessAlive(existingEntry.pid))console.log(`genie serve already running (PID ${existingEntry.pid})`),process.exit(0);if(existingEntry)forceRemoveServePid();let bunPath=process.execPath??"bun",args=[process.argv[1]??"genie","serve","--foreground"];if(headless)args.push("--headless");let child=spawn4(bunPath,args,{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}});if(child.unref(),child.pid)if(await new Promise((resolve6)=>setTimeout(resolve6,1000)),isProcessAlive(child.pid))console.log(`genie serve started (PID ${child.pid})`);else console.error("Error: genie serve exited immediately."),process.exit(1);else console.error("Error: failed to spawn genie serve"),process.exit(1)}function forceRemoveServePid(){try{unlinkSync10(servePidPath())}catch{}}async function stopServe(){let entry2=readServePid();if(!entry2){console.log("genie serve is not running (no PID file).");return}writeStoppingLockSync();try{let pid=entry2.pid;if(!isProcessAlive(pid)){console.log(`Stale PID file (PID ${pid} not running). Cleaning up.`),forceRemoveServePid(),killTuiSession();return}console.log(`Stopping genie serve (PID ${pid})...`);try{process.kill(-pid,"SIGTERM")}catch{try{process.kill(pid,"SIGTERM")}catch{}}let deadline=Date.now()+1e4;while(Date.now()<deadline&&isProcessAlive(pid))await new Promise((resolve6)=>setTimeout(resolve6,250));if(isProcessAlive(pid)){console.log("Did not stop within 10s. Sending SIGKILL.");try{process.kill(-pid,"SIGKILL")}catch{try{process.kill(pid,"SIGKILL")}catch{}}}killTuiSession(),forceRemoveServePid(),console.log("genie serve stopped.")}finally{clearStoppingLock()}}async function printPgserveHealth(){try{let{isAvailable:isAvailable2,getActivePort:getActivePort2}=await Promise.resolve().then(() => (init_db(),exports_db)),dbOk=await isAvailable2();console.log(` pgserve: ${dbOk?`healthy (port ${getActivePort2()})`:"unreachable"}`)}catch{console.log(" pgserve: unavailable")}}function resolveBrainPortFromWorkspace(brain){try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace)),ws=findWorkspace2();if(ws?.root&&brain.readServerInfo){let info=brain.readServerInfo(join48(ws.root,"brain"));if(info?.port)return info.port}}catch{}return null}async function probeBrainHealth(brainPort){try{let resp=await fetch(`http://127.0.0.1:${brainPort}/healthz`);console.log(resp.ok?` brain: running (port ${brainPort})`:` brain: unhealthy (port ${brainPort}, status ${resp.status})`)}catch{console.log(` brain: stopped (port ${brainPort} unreachable)`)}}async function printBrainStatus(){try{let brain=await import("@khal-os/brain"),brainPort=resolveBrainPortFromWorkspace(brain)??handles.brainHandle?.port??null;if(brainPort)await probeBrainHealth(brainPort);else console.log(" brain: stopped")}catch{console.log(" brain: not installed")}}async function printPgserveStatus(){await printPgserveHealth(),await printBrainStatus()}function printTmuxStatus(){let agentRunning=isGenieTmuxRunning(),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L genie: ${agentRunning?`running (${sessions.length} sessions)`:"stopped"}`),sessions.length>0)console.log(` ${sessions.join(", ")}`);let tuiReady=isTuiSessionReady();console.log(` tmux -L genie-tui: ${tuiReady?"running":"stopped"}`)}async function printDaemonStatus(serveRunning){try{let schedulerPidPath=join48(genieHome3(),"scheduler.pid");if(existsSync39(schedulerPidPath)){let sPid=Number.parseInt(readFileSync23(schedulerPidPath,"utf-8").trim(),10),sAlive=!Number.isNaN(sPid)&&isProcessAlive(sPid);console.log(` scheduler: ${sAlive?`running (PID ${sPid})`:"stopped"}`)}else if(serveRunning)console.log(" scheduler: integrated (in-process)");else console.log(" scheduler: stopped")}catch{console.log(" scheduler: unknown")}try{let{getInboxPollIntervalMs:getInboxPollIntervalMs2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log(" inbox: disabled");else console.log(` inbox: ${serveRunning?"watching":"stopped"} (poll ${pollMs/1000}s)`)}catch{console.log(" inbox: unavailable")}}async function printBridgeStatus(){try{let{getBridgeStatus:getBridgeStatus2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2();if(res.state==="running"&&res.pong){let uptimeSec=Math.round(res.pong.uptimeMs/1000),latency=res.latencyMs??0;console.log(` omni-bridge: running (pid ${res.pong.pid}, uptime ${uptimeSec}s, ping ${latency}ms)`)}else if(res.state==="stale")console.log(` omni-bridge: stale \u2014 ${res.detail}`);else console.log(" omni-bridge: stopped")}catch{console.log(" omni-bridge: unavailable")}}async function statusServe(){let entry2=readServePid(),running2=entry2!==null&&isProcessAlive(entry2.pid);if(console.log(`
|
|
2432
2432
|
Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${running2?"running":"stopped"}`),running2&&entry2)console.log(` PID: ${entry2.pid}`);await printPgserveStatus(),printTmuxStatus(),await printDaemonStatus(running2),await printBridgeStatus(),console.log(` PID file: ${servePidPath()}`),console.log("")}async function runStartPreconditions(autoFix){if(process.env.GENIE_SKIP_PRECONDITIONS==="1")return;let{ensureServeReady:ensureServeReady2}=await Promise.resolve().then(() => (init_ensure_ready(),exports_ensure_ready)),report=await ensureServeReady2({autoFix});if(autoFix)return;if(!report.ok)console.error("genie serve start refused: one or more preconditions are not ok (--no-fix mode)."),process.exit(2)}function registerServeCommands(program2){let serve=program2.command("serve").description("Start all genie infrastructure (pgserve, tmux, scheduler)");serve.command("start",{isDefault:!0}).description("Start genie serve").option("--daemon","Run in background").option("--foreground","Run in foreground (default)").option("--headless","Run without TUI (services only: pgserve, scheduler, inbox-watcher)").option("--no-fix","Refuse to start when any precondition is not ok (default: auto-fix)").action(async(options)=>{let autoFix=options.fix!==!1;if(await runStartPreconditions(autoFix),options.daemon)await startBackground(options.headless);else await startForeground(options.headless)}),serve.command("stop").description("Stop genie serve and all services").action(async()=>{await stopServe()}),serve.command("status").description("Show service health").action(async()=>{await statusServe()})}var STOPPING_LOCK_TTL_MS=30000,TUI_SESSION="genie-tui",NAV_WIDTH=30,TUI_STYLE,handles;var init_serve=__esm(()=>{init_genie_tokens();init_ensure_tmux();init_process_identity();init_tmux_wrapper();init_tui_disable();TUI_STYLE={activeBorder:palette.borderActive,inactiveBorder:palette.border};handles={schedulerHandle:null,agentWatcher:null,brainHandle:null,omniApprovalHandler:null,omniBridge:null,detectorScheduler:null,derivedSignals:null}});var exports_tmux2={};__export(exports_tmux2,{newAgentWindow:()=>newAgentWindow,hasProjectSession:()=>hasProjectSession,attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{spawnSync as spawnSync6}from"child_process";function runTuiTmux(args,stdio="ignore"){return spawnSync6(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{stdio})}function runTuiTmuxOutput(args){let result2=spawnSync6(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{encoding:"utf-8"});return result2.status===0?result2.stdout.trim():null}function runAgentTmux(args,stdio="ignore"){return spawnSync6(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,...args],{stdio})}function shellQuote3(value){return`'${value.replace(/'/g,"'\\''")}'`}function buildAttachLoop(targetSession){return`while true; do TMUX='' ${[TMUX_BIN,"-L",GENIE_AGENT_SOCKET,"attach-session","-t",targetSession].map(shellQuote3).join(" ")} 2>/dev/null; sleep 0.3; done`}function resolveRightPane(rightPane){if(runTuiTmux(["display-message","-t",rightPane,"-p",""]).status===0)return rightPane;let panes=runTuiTmuxOutput(["list-panes","-t",`${SESSION_NAME}:0`,"-F","#{pane_id}"])?.split(`
|
|
2433
2433
|
`)??[];return panes[1]||panes[0]||rightPane}function hasProjectSession(targetSession){return runAgentTmux(["has-session","-t",targetSession]).status===0}function attachProjectWindow(rightPane,targetSession,windowIndex){if(targetSession===SESSION_NAME)return;let pane=resolveRightPane(rightPane);if(!hasProjectSession(targetSession))return;if(windowIndex!==void 0)runAgentTmux(["select-window","-t",`${targetSession}:${windowIndex}`]);runAgentTmux(["set-option","-t",targetSession,"status","off"]),runTuiTmux(["respawn-pane","-k","-t",pane,"sh","-lc",buildAttachLoop(targetSession)]),runTuiTmux(["select-pane","-t",`${SESSION_NAME}:0.0`])}function attachTuiSession(){if(process.env.TMUX)runTuiTmux(["switch-client","-t",SESSION_NAME],"inherit");else runTuiTmux(["attach-session","-t",SESSION_NAME],"inherit")}function nextRoleSuffix(baseName){let sessionName=baseName.replace(/\//g,"-"),output=spawnSync6(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,"list-windows","-t",`=${sessionName}`,"-F","#{window_name}"],{encoding:"utf-8"}),names=output.status===0?output.stdout.trim().split(`
|
|
2434
|
-
`):[],max=names.length+1,re=new RegExp(`^${baseName}-(\\d+)$`);for(let n of names){let m=n.match(re);if(m)max=Math.max(max,Number.parseInt(m[1],10)+1)}return max}function newAgentWindow(agentName){(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}let{spawn:spawn5}=__require("child_process"),{join:join49,resolve:resolve6}=__require("path"),{existsSync:existsSync40}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let parentName=agentName.includes("/")?agentName.slice(0,agentName.indexOf("/")):agentName,agentDir=resolve6(join49(wsRoot,"agents",parentName));if(existsSync40(agentDir))cwd=agentDir}let suffix=nextRoleSuffix(agentName),role=`${agentName}-${suffix}`,sessionName=agentName.replace(/\//g,"-"),args=["spawn",agentName,"--role",role,"--session",sessionName,"--new-window"];(genieBin&&genieBin!=="genie"?spawn5(bunPath,[genieBin,...args],{detached:!0,stdio:"ignore",cwd}):spawn5("genie",args,{detached:!0,stdio:"ignore",cwd})).unref()})()}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX_BIN;var init_tmux2=__esm(()=>{init_ensure_tmux();TUI_TMUX_CONF=(()=>{let{existsSync:existsSync40}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync40(tuiConf)?tuiConf:"/dev/null"})(),TMUX_BIN=tmuxBin()});function s(orgId,path3){return`khal.${orgId}.genie.${path3}`}var GENIE_SUBJECTS;var init_subjects=__esm(()=>{GENIE_SUBJECTS={dashboard:{stats:(orgId)=>s(orgId,"dashboard.stats")},agents:{list:(orgId)=>s(orgId,"agents.list"),show:(orgId)=>s(orgId,"agents.show")},sessions:{list:(orgId)=>s(orgId,"sessions.list"),content:(orgId)=>s(orgId,"sessions.content"),search:(orgId)=>s(orgId,"sessions.search")},tasks:{list:(orgId)=>s(orgId,"tasks.list"),show:(orgId)=>s(orgId,"tasks.show")},boards:{list:(orgId)=>s(orgId,"boards.list"),show:(orgId)=>s(orgId,"boards.show")},costs:{summary:(orgId)=>s(orgId,"costs.summary"),sessions:(orgId)=>s(orgId,"costs.sessions"),tokens:(orgId)=>s(orgId,"costs.tokens"),efficiency:(orgId)=>s(orgId,"costs.efficiency")},schedules:{list:(orgId)=>s(orgId,"schedules.list"),history:(orgId)=>s(orgId,"schedules.history")},system:{health:(orgId)=>s(orgId,"system.health"),snapshots:(orgId)=>s(orgId,"system.snapshots"),tables:(orgId)=>s(orgId,"system.tables"),channels:(orgId)=>s(orgId,"system.channels")},settings:{get:(orgId)=>s(orgId,"settings.get"),set:(orgId)=>s(orgId,"settings.set"),templates:(orgId)=>s(orgId,"settings.templates"),templateSave:(orgId)=>s(orgId,"settings.templates.save"),skills:(orgId)=>s(orgId,"settings.skills"),rules:(orgId)=>s(orgId,"settings.rules"),workspace:(orgId)=>s(orgId,"settings.workspace"),testPg:(orgId)=>s(orgId,"settings.test_pg")},pty:{create:(orgId)=>s(orgId,"pty.create"),input:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.input`),data:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.data`),resize:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.resize`),kill:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.kill`)},fs:{list:(orgId)=>s(orgId,"fs.list"),read:(orgId)=>s(orgId,"fs.read"),write:(orgId)=>s(orgId,"fs.write")},approval:{resolve:(orgId)=>s(orgId,"approval.resolve"),list:(orgId)=>s(orgId,"approval.list")},events:{agentState:(orgId)=>s(orgId,"events.agent_state"),executorState:(orgId)=>s(orgId,"events.executor_state"),taskStage:(orgId)=>s(orgId,"events.task_stage"),runtime:(orgId)=>s(orgId,"events.runtime"),audit:(orgId)=>s(orgId,"events.audit"),message:(orgId)=>s(orgId,"events.message"),mailbox:(orgId)=>s(orgId,"events.mailbox"),taskDep:(orgId)=>s(orgId,"events.task_dep"),trigger:(orgId)=>s(orgId,"events.trigger"),approvalRequest:(orgId)=>s(orgId,"events.approval_request"),approvalResolved:(orgId)=>s(orgId,"events.approval_resolved")}}});function emit2(event){for(let handler of listeners)handler(event)}async function startListening(nats,orgId){if(listenersActive)return;if(listenersActive=!0,nats)natsConn=nats,natsOrgId=orgId??"default";let sql=await getConnection();for(let mapping of CHANNEL_MAP){let listener=await sql.listen(mapping.channel,(payload)=>{let parsed;try{parsed=JSON.parse(payload)}catch{parsed={raw:payload}}if(emit2({type:mapping.eventType,payload:parsed}),natsConn){let subject=mapping.natsSubject(natsOrgId);natsConn.publish(subject,sc.encode(JSON.stringify(parsed)))}});stopFns.push(()=>listener.unlisten())}}async function stopListening(){listenersActive=!1,natsConn=null,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var import_nats4,listeners,natsConn=null,natsOrgId="default",sc,CHANNEL_MAP,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();init_subjects();import_nats4=__toESM(require_mod4(),1),listeners=new Set;sc=import_nats4.StringCodec(),CHANNEL_MAP=[{channel:"genie_agent_state",eventType:"agent-state-changed",natsSubject:GENIE_SUBJECTS.events.agentState},{channel:"genie_executor_state",eventType:"executor-state-changed",natsSubject:GENIE_SUBJECTS.events.executorState},{channel:"genie_task_stage",eventType:"task-stage-changed",natsSubject:GENIE_SUBJECTS.events.taskStage},{channel:"genie_runtime_event",eventType:"runtime-event",natsSubject:GENIE_SUBJECTS.events.runtime},{channel:"genie_audit_event",eventType:"audit-event",natsSubject:GENIE_SUBJECTS.events.audit},{channel:"genie_message",eventType:"message",natsSubject:GENIE_SUBJECTS.events.message},{channel:"genie_mailbox_delivery",eventType:"mailbox-delivery",natsSubject:GENIE_SUBJECTS.events.mailbox},{channel:"genie_task_dep",eventType:"task-dep-changed",natsSubject:GENIE_SUBJECTS.events.taskDep},{channel:"genie_trigger_due",eventType:"trigger-due",natsSubject:GENIE_SUBJECTS.events.trigger},{channel:"genie_approval_request",eventType:"approval-request",natsSubject:GENIE_SUBJECTS.events.approvalRequest},{channel:"genie_approval_resolved",eventType:"approval-resolved",natsSubject:GENIE_SUBJECTS.events.approvalResolved}],stopFns=[]});import{randomUUID as randomUUID11}from"crypto";function onPtyData(cb){dataCallback=cb}function onPtyExit(cb){exitCallback=cb}async function spawnForAgent(agentName,opts={}){let{buildClaudeCommand:buildClaudeCommand3}=await Promise.resolve().then(() => (init_provider_adapters(),exports_provider_adapters)),{createExecutor:createExecutor2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),{findOrCreateAgent:findOrCreateAgent2,setCurrentExecutor:setCurrentExecutor2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),cols=opts.cols??120,rows=opts.rows??40,cwd=opts.cwd??process.cwd(),launch=buildClaudeCommand3({provider:"claude",team:"app",role:"engineer",name:agentName}),agent=await findOrCreateAgent2(agentName,"app","engineer"),executor=await createExecutor2(agent.id,"app-pty","process",{repoPath:cwd,state:"spawning",metadata:{command:launch.command,source:"genie-app"}});await setCurrentExecutor2(agent.id,executor.id);let env={...process.env,...launch.env,GENIE_APP_PTY:"true"},session=spawnPty(launch.command,{cwd,cols,rows,env,agentId:agent.id,executorId:executor.id,taskId:opts.taskId??null}),{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await updateExecutorState2(executor.id,"running"),session}function spawnBash(cwd){let shell=process.env.SHELL??"/bin/bash";return spawnPty(shell,{cwd:cwd??process.cwd(),cols:120,rows:40,env:process.env,agentId:null,executorId:null,taskId:null})}function writeTerminal(sessionId,data){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.write(data),!0}function resizeTerminal(sessionId,cols,rows){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.resize(cols,rows),session.cols=cols,session.rows=rows,!0}async function killTerminal(sessionId){let session=sessions.get(sessionId);if(!session)return!1;if(session.pty.kill(),session.state="exited",session.executorId)try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,"terminated")}catch{}return sessions.delete(sessionId),!0}async function killAll(){let ids=[...sessions.keys()];await Promise.allSettled(ids.map((id)=>killTerminal(id)))}async function pipeStdout(stdout,sessionId,ptyHandle){let reader=stdout.getReader(),decoder=new TextDecoder;try{while(!0){let{done,value}=await reader.read();if(done)break;let text=decoder.decode(value);if(dataCallback)dataCallback(sessionId,text);if(ptyHandle.onData)ptyHandle.onData(text)}}catch{}}function onProcExit(sessionId,code,ptyHandle){let session=sessions.get(sessionId);if(session)session.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}function spawnPty(command,opts){let sessionId=randomUUID11(),ptyHandle;try{let bunPty=(()=>{throw new Error("Cannot require module "+"bun-pty");})(),parts=command.split(" "),raw=bunPty.spawn(parts,{cwd:opts.cwd,env:opts.env,cols:opts.cols,rows:opts.rows});ptyHandle={write:(data)=>raw.write(data),resize:(cols,rows)=>raw.resize(cols,rows),kill:()=>raw.kill(),onData:null,onExit:null},raw.onData=(data)=>{if(dataCallback)dataCallback(sessionId,data);if(ptyHandle.onData)ptyHandle.onData(data)},raw.onExit=(code)=>{let session2=sessions.get(sessionId);if(session2)session2.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}}catch{let parts=command.split(" "),proc=Bun.spawn(parts,{cwd:opts.cwd,env:opts.env,stdin:"pipe",stdout:"pipe",stderr:"pipe"});ptyHandle={write:(data)=>proc.stdin.write(data),resize:()=>{},kill:()=>proc.kill(),onData:null,onExit:null},pipeStdout(proc.stdout,sessionId,ptyHandle),proc.exited.then((code)=>onProcExit(sessionId,code,ptyHandle))}let session={id:sessionId,pty:ptyHandle,agentId:opts.agentId,executorId:opts.executorId,taskId:opts.taskId,command,state:"running",cols:opts.cols,rows:opts.rows,createdAt:new Date().toISOString()};return sessions.set(sessionId,session),session}function handlePtyExit(sessionId,code){let session=sessions.get(sessionId);if(!session?.executorId)return;(async()=>{try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,code===0?"done":"error")}catch{}})()}var sessions,dataCallback=null,exitCallback=null;var init_pty=__esm(()=>{sessions=new Map});var exports_src_backend={};import{existsSync as existsSync40,readFileSync as readFileSync24,readdirSync as readdirSync10,writeFileSync as writeFileSync16}from"fs";import{homedir as homedir33}from"os";import{join as join49,resolve as resolve6}from"path";function findSkillsDir(){let genieHome4=process.env.GENIE_HOME??join49(homedir33(),".genie"),candidateDirs=[join49(process.cwd(),"skills"),join49(genieHome4,"..","skills")];for(let d of candidateDirs)if(existsSync40(d))return d;return null}function parseSkillMd(content,fallbackName){let descMatch=content.match(/^description:\s*["']?(.+?)["']?\s*$/m),description="";if(descMatch)description=descMatch[1].trim();else description=content.split(`
|
|
2434
|
+
`):[],max=names.length+1,re=new RegExp(`^${baseName}-(\\d+)$`);for(let n of names){let m=n.match(re);if(m)max=Math.max(max,Number.parseInt(m[1],10)+1)}return max}function newAgentWindow(agentName){(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}let{spawn:spawn5}=__require("child_process"),{join:join49,resolve:resolve6}=__require("path"),{existsSync:existsSync40}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let parentName=agentName.includes("/")?agentName.slice(0,agentName.indexOf("/")):agentName,agentDir=resolve6(join49(wsRoot,"agents",parentName));if(existsSync40(agentDir))cwd=agentDir}let suffix=nextRoleSuffix(agentName),role=`${agentName}-${suffix}`,sessionName=agentName.replace(/\//g,"-"),args=["spawn",agentName,"--role",role,"--session",sessionName,"--new-window"];(genieBin&&genieBin!=="genie"?spawn5(bunPath,[genieBin,...args],{detached:!0,stdio:"ignore",cwd}):spawn5("genie",args,{detached:!0,stdio:"ignore",cwd})).unref()})()}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX_BIN;var init_tmux2=__esm(()=>{init_ensure_tmux();TUI_TMUX_CONF=(()=>{let{existsSync:existsSync40}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync40(tuiConf)?tuiConf:"/dev/null"})(),TMUX_BIN=tmuxBin()});function s(orgId,path3){return`khal.${orgId}.genie.${path3}`}var GENIE_SUBJECTS;var init_subjects=__esm(()=>{GENIE_SUBJECTS={dashboard:{stats:(orgId)=>s(orgId,"dashboard.stats")},agents:{list:(orgId)=>s(orgId,"agents.list"),show:(orgId)=>s(orgId,"agents.show")},sessions:{list:(orgId)=>s(orgId,"sessions.list"),content:(orgId)=>s(orgId,"sessions.content"),search:(orgId)=>s(orgId,"sessions.search")},tasks:{list:(orgId)=>s(orgId,"tasks.list"),show:(orgId)=>s(orgId,"tasks.show")},boards:{list:(orgId)=>s(orgId,"boards.list"),show:(orgId)=>s(orgId,"boards.show")},costs:{summary:(orgId)=>s(orgId,"costs.summary"),sessions:(orgId)=>s(orgId,"costs.sessions"),tokens:(orgId)=>s(orgId,"costs.tokens"),efficiency:(orgId)=>s(orgId,"costs.efficiency")},schedules:{list:(orgId)=>s(orgId,"schedules.list"),history:(orgId)=>s(orgId,"schedules.history")},system:{health:(orgId)=>s(orgId,"system.health"),snapshots:(orgId)=>s(orgId,"system.snapshots"),tables:(orgId)=>s(orgId,"system.tables"),channels:(orgId)=>s(orgId,"system.channels")},settings:{get:(orgId)=>s(orgId,"settings.get"),set:(orgId)=>s(orgId,"settings.set"),templates:(orgId)=>s(orgId,"settings.templates"),templateSave:(orgId)=>s(orgId,"settings.templates.save"),skills:(orgId)=>s(orgId,"settings.skills"),rules:(orgId)=>s(orgId,"settings.rules"),workspace:(orgId)=>s(orgId,"settings.workspace"),testPg:(orgId)=>s(orgId,"settings.test_pg")},pty:{create:(orgId)=>s(orgId,"pty.create"),input:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.input`),data:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.data`),resize:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.resize`),kill:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.kill`)},fs:{list:(orgId)=>s(orgId,"fs.list"),read:(orgId)=>s(orgId,"fs.read"),write:(orgId)=>s(orgId,"fs.write")},approval:{resolve:(orgId)=>s(orgId,"approval.resolve"),list:(orgId)=>s(orgId,"approval.list")},events:{agentState:(orgId)=>s(orgId,"events.agent_state"),executorState:(orgId)=>s(orgId,"events.executor_state"),taskStage:(orgId)=>s(orgId,"events.task_stage"),runtime:(orgId)=>s(orgId,"events.runtime"),audit:(orgId)=>s(orgId,"events.audit"),message:(orgId)=>s(orgId,"events.message"),mailbox:(orgId)=>s(orgId,"events.mailbox"),taskDep:(orgId)=>s(orgId,"events.task_dep"),trigger:(orgId)=>s(orgId,"events.trigger"),approvalRequest:(orgId)=>s(orgId,"events.approval_request"),approvalResolved:(orgId)=>s(orgId,"events.approval_resolved")}}});function emit2(event){for(let handler of listeners)handler(event)}async function startListening(nats,orgId){if(listenersActive)return;if(listenersActive=!0,nats)natsConn=nats,natsOrgId=orgId??"default";let sql=await getConnection();for(let mapping of CHANNEL_MAP){let listener=await sql.listen(mapping.channel,(payload)=>{let parsed;try{parsed=JSON.parse(payload)}catch{parsed={raw:payload}}if(emit2({type:mapping.eventType,payload:parsed}),natsConn){let subject=mapping.natsSubject(natsOrgId);natsConn.publish(subject,sc.encode(JSON.stringify(parsed)))}});stopFns.push(()=>listener.unlisten())}}async function stopListening(){listenersActive=!1,natsConn=null,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var import_nats4,listeners,natsConn=null,natsOrgId="default",sc,CHANNEL_MAP,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();init_subjects();import_nats4=__toESM(require_mod4(),1),listeners=new Set;sc=import_nats4.StringCodec(),CHANNEL_MAP=[{channel:"genie_agent_state",eventType:"agent-state-changed",natsSubject:GENIE_SUBJECTS.events.agentState},{channel:"genie_executor_state",eventType:"executor-state-changed",natsSubject:GENIE_SUBJECTS.events.executorState},{channel:"genie_task_stage",eventType:"task-stage-changed",natsSubject:GENIE_SUBJECTS.events.taskStage},{channel:"genie_runtime_event",eventType:"runtime-event",natsSubject:GENIE_SUBJECTS.events.runtime},{channel:"genie_audit_event",eventType:"audit-event",natsSubject:GENIE_SUBJECTS.events.audit},{channel:"genie_message",eventType:"message",natsSubject:GENIE_SUBJECTS.events.message},{channel:"genie_mailbox_delivery",eventType:"mailbox-delivery",natsSubject:GENIE_SUBJECTS.events.mailbox},{channel:"genie_task_dep",eventType:"task-dep-changed",natsSubject:GENIE_SUBJECTS.events.taskDep},{channel:"genie_trigger_due",eventType:"trigger-due",natsSubject:GENIE_SUBJECTS.events.trigger},{channel:"genie_approval_request",eventType:"approval-request",natsSubject:GENIE_SUBJECTS.events.approvalRequest},{channel:"genie_approval_resolved",eventType:"approval-resolved",natsSubject:GENIE_SUBJECTS.events.approvalResolved}],stopFns=[]});import{randomUUID as randomUUID10}from"crypto";function onPtyData(cb){dataCallback=cb}function onPtyExit(cb){exitCallback=cb}async function spawnForAgent(agentName,opts={}){let{buildClaudeCommand:buildClaudeCommand3}=await Promise.resolve().then(() => (init_provider_adapters(),exports_provider_adapters)),{createExecutor:createExecutor2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),{findOrCreateAgent:findOrCreateAgent2,setCurrentExecutor:setCurrentExecutor2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),cols=opts.cols??120,rows=opts.rows??40,cwd=opts.cwd??process.cwd(),launch=buildClaudeCommand3({provider:"claude",team:"app",role:"engineer",name:agentName}),agent=await findOrCreateAgent2(agentName,"app","engineer"),executor=await createExecutor2(agent.id,"app-pty","process",{repoPath:cwd,state:"spawning",metadata:{command:launch.command,source:"genie-app"}});await setCurrentExecutor2(agent.id,executor.id);let env={...process.env,...launch.env,GENIE_APP_PTY:"true"},session=spawnPty(launch.command,{cwd,cols,rows,env,agentId:agent.id,executorId:executor.id,taskId:opts.taskId??null}),{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await updateExecutorState2(executor.id,"running"),session}function spawnBash(cwd){let shell=process.env.SHELL??"/bin/bash";return spawnPty(shell,{cwd:cwd??process.cwd(),cols:120,rows:40,env:process.env,agentId:null,executorId:null,taskId:null})}function writeTerminal(sessionId,data){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.write(data),!0}function resizeTerminal(sessionId,cols,rows){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.resize(cols,rows),session.cols=cols,session.rows=rows,!0}async function killTerminal(sessionId){let session=sessions.get(sessionId);if(!session)return!1;if(session.pty.kill(),session.state="exited",session.executorId)try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,"terminated")}catch{}return sessions.delete(sessionId),!0}async function killAll(){let ids=[...sessions.keys()];await Promise.allSettled(ids.map((id)=>killTerminal(id)))}async function pipeStdout(stdout,sessionId,ptyHandle){let reader=stdout.getReader(),decoder=new TextDecoder;try{while(!0){let{done,value}=await reader.read();if(done)break;let text=decoder.decode(value);if(dataCallback)dataCallback(sessionId,text);if(ptyHandle.onData)ptyHandle.onData(text)}}catch{}}function onProcExit(sessionId,code,ptyHandle){let session=sessions.get(sessionId);if(session)session.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}function spawnPty(command,opts){let sessionId=randomUUID10(),ptyHandle;try{let bunPty=(()=>{throw new Error("Cannot require module "+"bun-pty");})(),parts=command.split(" "),raw=bunPty.spawn(parts,{cwd:opts.cwd,env:opts.env,cols:opts.cols,rows:opts.rows});ptyHandle={write:(data)=>raw.write(data),resize:(cols,rows)=>raw.resize(cols,rows),kill:()=>raw.kill(),onData:null,onExit:null},raw.onData=(data)=>{if(dataCallback)dataCallback(sessionId,data);if(ptyHandle.onData)ptyHandle.onData(data)},raw.onExit=(code)=>{let session2=sessions.get(sessionId);if(session2)session2.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}}catch{let parts=command.split(" "),proc=Bun.spawn(parts,{cwd:opts.cwd,env:opts.env,stdin:"pipe",stdout:"pipe",stderr:"pipe"});ptyHandle={write:(data)=>proc.stdin.write(data),resize:()=>{},kill:()=>proc.kill(),onData:null,onExit:null},pipeStdout(proc.stdout,sessionId,ptyHandle),proc.exited.then((code)=>onProcExit(sessionId,code,ptyHandle))}let session={id:sessionId,pty:ptyHandle,agentId:opts.agentId,executorId:opts.executorId,taskId:opts.taskId,command,state:"running",cols:opts.cols,rows:opts.rows,createdAt:new Date().toISOString()};return sessions.set(sessionId,session),session}function handlePtyExit(sessionId,code){let session=sessions.get(sessionId);if(!session?.executorId)return;(async()=>{try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,code===0?"done":"error")}catch{}})()}var sessions,dataCallback=null,exitCallback=null;var init_pty=__esm(()=>{sessions=new Map});var exports_src_backend={};import{existsSync as existsSync40,readFileSync as readFileSync24,readdirSync as readdirSync10,writeFileSync as writeFileSync16}from"fs";import{homedir as homedir33}from"os";import{join as join49,resolve as resolve6}from"path";function findSkillsDir(){let genieHome4=process.env.GENIE_HOME??join49(homedir33(),".genie"),candidateDirs=[join49(process.cwd(),"skills"),join49(genieHome4,"..","skills")];for(let d of candidateDirs)if(existsSync40(d))return d;return null}function parseSkillMd(content,fallbackName){let descMatch=content.match(/^description:\s*["']?(.+?)["']?\s*$/m),description="";if(descMatch)description=descMatch[1].trim();else description=content.split(`
|
|
2435
2435
|
`).filter((l)=>l.trim()&&!l.startsWith("---")&&!l.startsWith("#"))[0]?.trim()??"";let nameMatch=content.match(/^name:\s*(.+?)\s*$/m);return{name:nameMatch?nameMatch[1].trim():fallbackName,description}}function scanSkillsDirectory(skillsDir){try{let entries=readdirSync10(skillsDir,{withFileTypes:!0}),skills=[];for(let entry2 of entries){if(!entry2.isDirectory())continue;let skillMdPath=join49(skillsDir,entry2.name,"SKILL.md");if(!existsSync40(skillMdPath))continue;let content=readFileSync24(skillMdPath,"utf-8"),{name,description}=parseSkillMd(content,entry2.name);skills.push({name,slug:entry2.name,description,path:skillMdPath})}return skills.sort((a,b2)=>a.name.localeCompare(b2.name))}catch{return[]}}function scanRuleDirs(){let genieHome4=process.env.GENIE_HOME??join49(homedir33(),".genie"),ruleDirs=[join49(genieHome4,"rules"),join49(homedir33(),".claude","rules")],allRules=[];for(let rulesDir of ruleDirs){if(!existsSync40(rulesDir))continue;try{let files=readdirSync10(rulesDir).filter((f)=>f.endsWith(".md"));for(let f of files){let filePath=join49(rulesDir,f);allRules.push({name:f.replace(".md",""),path:filePath,content:readFileSync24(filePath,"utf-8"),source:rulesDir.includes(".claude")?"claude":"genie"})}}catch{}}return allRules}async function start2(){console.log("[genie-app] Starting backend service..."),console.log(`[genie-app] NATS: ${NATS_URL}`),console.log(`[genie-app] Org: ${ORG_ID}`);let sql=await getConnection();console.log("[genie-app] PG connected"),nc=await import_nats5.connect({servers:NATS_URL,name:"genie-app-backend",reconnect:!0,maxReconnectAttempts:-1,reconnectTimeWait:2000}),console.log("[genie-app] NATS connected"),registerHandlers(sql),await startListening(nc,ORG_ID),console.log("[genie-app] PG LISTEN/NOTIFY active (11 channels)"),onPtyData((sessionId,data)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({data})))}),onPtyExit((sessionId,code)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({code,type:"exit"})))}),console.log("[genie-app] Backend ready")}function registerHandlers(sql){if(!nc)return;let sub=GENIE_SUBJECTS;reply(sub.dashboard.stats(ORG_ID),async()=>{let[agentRows,taskRows,teamRows,costRows,snapshot]=await Promise.all([sql`
|
|
2436
2436
|
SELECT
|
|
2437
2437
|
COUNT(*) FILTER (WHERE a.state IN ('working', 'idle', 'permission', 'question')) AS online,
|
|
@@ -2720,7 +2720,7 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2720
2720
|
UPDATE board_templates SET columns = ${sql.json(columns)}, updated_at = now()
|
|
2721
2721
|
WHERE id = ${templateId}
|
|
2722
2722
|
RETURNING *
|
|
2723
|
-
`;if(rows.length===0)return null;return recordAuditEvent("template",templateId,"template_column_updated",getActor(),{columnName,updates:Object.keys(updates)}).catch(()=>{}),mapTemplate(rows[0])}async function snapshotFromBoard(boardId,templateName){return createTemplate({name:templateName,fromBoardId:boardId})}var init_template_service=__esm(()=>{init_audit();init_board_service();init_db()});import{existsSync as existsSync46}from"fs";import{join as join55}from"path";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join55(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync46(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join55(REPOS_BASE,parsed.namespace);if(!existsSync46(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join55(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync46(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}var REPOS_BASE="/home/genie/workspace/repos";var init_wish_resolve=__esm(()=>{init_tmux()});var exports_assignment_registry={};__export(exports_assignment_registry,{getTaskHistory:()=>getTaskHistory,getExecutorAssignments:()=>getExecutorAssignments,getAssignment:()=>getAssignment,getActiveAssignment:()=>getActiveAssignment,createAssignment:()=>createAssignment,completeAssignment:()=>completeAssignment});import{randomUUID as
|
|
2723
|
+
`;if(rows.length===0)return null;return recordAuditEvent("template",templateId,"template_column_updated",getActor(),{columnName,updates:Object.keys(updates)}).catch(()=>{}),mapTemplate(rows[0])}async function snapshotFromBoard(boardId,templateName){return createTemplate({name:templateName,fromBoardId:boardId})}var init_template_service=__esm(()=>{init_audit();init_board_service();init_db()});import{existsSync as existsSync46}from"fs";import{join as join55}from"path";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join55(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync46(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join55(REPOS_BASE,parsed.namespace);if(!existsSync46(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join55(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync46(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}var REPOS_BASE="/home/genie/workspace/repos";var init_wish_resolve=__esm(()=>{init_tmux()});var exports_assignment_registry={};__export(exports_assignment_registry,{getTaskHistory:()=>getTaskHistory,getExecutorAssignments:()=>getExecutorAssignments,getAssignment:()=>getAssignment,getActiveAssignment:()=>getActiveAssignment,createAssignment:()=>createAssignment,completeAssignment:()=>completeAssignment});import{randomUUID as randomUUID13}from"crypto";async function createAssignment(executorId,taskId,wishSlug,groupNumber){let sql=await getConnection(),id=randomUUID13(),now=new Date().toISOString(),rows=await sql`
|
|
2724
2724
|
INSERT INTO assignments (id, executor_id, task_id, wish_slug, group_number, started_at)
|
|
2725
2725
|
VALUES (${id}, ${executorId}, ${taskId}, ${wishSlug??null}, ${groupNumber??null}, ${now})
|
|
2726
2726
|
RETURNING *
|
|
@@ -3910,7 +3910,7 @@ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);i
|
|
|
3910
3910
|
AND tenant_id = ${tenantId}
|
|
3911
3911
|
ORDER BY id ASC
|
|
3912
3912
|
LIMIT ${opts.limit??1e5}
|
|
3913
|
-
`,canon=JSON.stringify({verify,rows:rows.map(serializeRowForDigest)}),key=opts.secret??process.env.GENIE_AUDIT_EXPORT_SECRET??process.env.GENIE_EVENTS_TOKEN_SECRET??"genie-export-fallback",signature=createHmac3("sha256",key).update(canon).digest("hex");return{generated_at:new Date().toISOString(),tenant_id:tenantId,since_id:sinceId,verify,rows,bundle_signature:signature,signer:opts.signer}}function serializeRowForDigest(row){return{id:row.id,kind:row.kind,agent:row.agent,text:row.text,data:row.data,trace_id:row.trace_id,span_id:row.span_id,severity:row.severity,created_at:row.created_at,chain_hash:row.chain_hash?row.chain_hash.toString("hex"):null,chain_key_version:row.chain_key_version}}import{createHmac as createHmac4,randomUUID as
|
|
3913
|
+
`,canon=JSON.stringify({verify,rows:rows.map(serializeRowForDigest)}),key=opts.secret??process.env.GENIE_AUDIT_EXPORT_SECRET??process.env.GENIE_EVENTS_TOKEN_SECRET??"genie-export-fallback",signature=createHmac3("sha256",key).update(canon).digest("hex");return{generated_at:new Date().toISOString(),tenant_id:tenantId,since_id:sinceId,verify,rows,bundle_signature:signature,signer:opts.signer}}function serializeRowForDigest(row){return{id:row.id,kind:row.kind,agent:row.agent,text:row.text,data:row.data,trace_id:row.trace_id,span_id:row.span_id,severity:row.severity,created_at:row.created_at,chain_hash:row.chain_hash?row.chain_hash.toString("hex"):null,chain_key_version:row.chain_key_version}}import{createHmac as createHmac4,randomUUID as randomUUID11,timingSafeEqual as timingSafeEqual2}from"crypto";init_registry();var ALL_ROLES=["events:admin","events:operator","events:subscriber","events:audit"];var CHANNEL_MATRIX={"events:admin":["genie_events.cli","genie_events.agent","genie_events.wish","genie_events.hook","genie_events.resume","genie_events.executor","genie_events.mailbox","genie_events.error","genie_events.state_transition","genie_events.schema","genie_events.session","genie_events.tmux","genie_events.cache","genie_events.runbook","genie_events.consumer","genie_events.permissions","genie_events.team","genie_events.emitter","genie_events.notify","genie_events.stream","genie_events.correlation","genie_events.audit"],"events:operator":["genie_events.cli","genie_events.agent","genie_events.wish","genie_events.hook","genie_events.resume","genie_events.executor","genie_events.mailbox","genie_events.error","genie_events.state_transition","genie_events.session","genie_events.tmux","genie_events.cache","genie_events.runbook","genie_events.consumer","genie_events.permissions","genie_events.team","genie_events.emitter","genie_events.notify","genie_events.stream","genie_events.correlation"],"events:subscriber":["genie_events.mailbox","genie_events.state_transition","genie_events.error","genie_events.runbook","genie_events.agent","genie_events.wish","genie_events.session","genie_events.executor","genie_events.hook","genie_events.resume","genie_events.consumer","genie_events.cache","genie_events.tmux","genie_events.permissions","genie_events.team"],"events:audit":["genie_events.audit","genie_events.team","genie_events.permissions"]};function allowedChannels(role){return CHANNEL_MATRIX[role]}class RBACError extends Error{code;constructor(message){super(message);this.code="RBAC_DENIED",this.name="RBACError"}}function resolveChannels2(role,requested){let defaults=allowedChannels(role);for(let ch of requested)if(!defaults.includes(ch))throw new RBACError(`role ${role} cannot LISTEN on '${ch}' (outside role default set)`);return requested.length>0?requested:defaults}class TokenError extends Error{code;constructor(code,message){super(message);this.code=code,this.name="TokenError"}}function b64urlEncode(input){return(typeof input==="string"?Buffer.from(input,"utf8"):input).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function b64urlDecode(input){let padded=input+"=".repeat((4-input.length%4)%4);return Buffer.from(padded.replace(/-/g,"+").replace(/_/g,"/"),"base64")}var DEV_FALLBACK_SECRET="genie-events-token-fallback-dev-only";function resolveSecret(override){if(override)return override;let env=process.env.GENIE_EVENTS_TOKEN_SECRET;if(env&&env.length>0)return env;return DEV_FALLBACK_SECRET}var HEADER={alg:"HS256",typ:"GEVT"};function sign(input,secret){return b64urlEncode(createHmac4("sha256",secret).update(input).digest())}function safeEqual(a,b2){let ab=Buffer.from(a),bb=Buffer.from(b2);if(ab.length!==bb.length)return!1;return timingSafeEqual2(ab,bb)}var MAX_TTL_SECONDS=86400;function mintToken2(opts){if(!ALL_ROLES.includes(opts.role))throw new RBACError(`unknown role '${opts.role}'`);let channels=resolveChannels2(opts.role,opts.allowed_channels??[]),types4=(opts.allowed_types??[]).slice();for(let t of types4){let prefix=t.split(".")[0];if(!channels.some((c)=>c===`genie_events.${prefix}`))throw new RBACError(`type '${t}' is not reachable via the requested channels for role ${opts.role}`)}let now=Math.floor((opts.now??Date.now())/1000),ttl=Math.min(Math.max(opts.ttl_seconds??3600,60),MAX_TTL_SECONDS),payload={role:opts.role,allowed_types:types4,allowed_channels:[...channels],tenant_id:opts.tenant_id??"default",subscriber_id:opts.subscriber_id??`sub-${randomUUID11().slice(0,12)}`,token_id:randomUUID11(),iat:now,exp:now+ttl},secret=resolveSecret(opts.secret),encodedHeader=b64urlEncode(JSON.stringify(HEADER)),encodedPayload=b64urlEncode(JSON.stringify(payload)),signingInput=`${encodedHeader}.${encodedPayload}`,signature=sign(signingInput,secret);return{token:`${signingInput}.${signature}`,payload}}function decodeAndVerifyPayload(token,secret){let parts=token.split(".");if(parts.length!==3)throw new TokenError("TOKEN_MALFORMED","token is not a three-segment JWT");let[encodedHeader,encodedPayload,signature]=parts,expected=sign(`${encodedHeader}.${encodedPayload}`,secret);if(!safeEqual(expected,signature))throw new TokenError("TOKEN_SIGNATURE_INVALID","signature mismatch");try{return JSON.parse(b64urlDecode(encodedPayload).toString("utf8"))}catch{throw new TokenError("TOKEN_MALFORMED","payload is not valid JSON")}}function assertPayloadClaims(payload,opts){if(!ALL_ROLES.includes(payload.role))throw new TokenError("TOKEN_ROLE_UNKNOWN",`unknown role '${payload.role}'`);let nowSec=Math.floor((opts.now??Date.now())/1000);if(nowSec>=payload.exp)throw new TokenError("TOKEN_EXPIRED",`token expired at ${payload.exp} (now=${nowSec})`);if(opts.expectedTenantId!==void 0&&payload.tenant_id!==opts.expectedTenantId)throw new TokenError("TOKEN_TENANT_MISMATCH",`token tenant ${payload.tenant_id} does not match expected ${opts.expectedTenantId}`);let hasTypes=Array.isArray(payload.allowed_types)&&payload.allowed_types.length>0,hasChannels=Array.isArray(payload.allowed_channels)&&payload.allowed_channels.length>0;if(!hasTypes&&!hasChannels)throw new TokenError("TOKEN_ALLOWLIST_EMPTY","token must carry at least one of allowed_types / allowed_channels");let roleDefaults=allowedChannels(payload.role);for(let ch of payload.allowed_channels)if(!roleDefaults.includes(ch))throw new TokenError("TOKEN_ROLE_UNKNOWN",`channel ${ch} outside role ${payload.role} defaults`)}async function assertNotRevoked(payload,opts){if(opts.revokedTokenIds?opts.revokedTokenIds.has(payload.token_id):await isRevoked(payload.token_id))throw new TokenError("TOKEN_REVOKED",`token_id ${payload.token_id} revoked`)}async function verifyToken(token,opts={}){let secret=resolveSecret(opts.secret),payload=decodeAndVerifyPayload(token,secret);return assertPayloadClaims(payload,opts),await assertNotRevoked(payload,opts),payload}async function revokeToken(args){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));await(await getConnection2())`
|
|
3914
3914
|
INSERT INTO genie_events_revocations (token_id, subscriber_id, tenant_id, revoked_by, reason)
|
|
3915
3915
|
VALUES (${args.token_id}, ${args.subscriber_id??null}, ${args.tenant_id??"default"}, ${args.revoked_by}, ${args.reason})
|
|
3916
3916
|
ON CONFLICT (token_id) DO NOTHING
|
|
@@ -3965,7 +3965,7 @@ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);i
|
|
|
3965
3965
|
text, data, thread_id, trace_id, parent_event_id, created_at
|
|
3966
3966
|
) VALUES (
|
|
3967
3967
|
$1, $2, $3, $4, $5, $6, NULL, NULL, $7, $8::jsonb, NULL, NULL, NULL, $9::timestamptz
|
|
3968
|
-
)`,[process.env.GENIE_REPO_PATH??process.cwd(),MIGRATE_SUBJECT,"system",MIGRATE_SOURCE,row.actor??"legacy",null,row.event_type,JSON.stringify(buildEnrichedData(row)),createdAt])}async function applyBatch(sql,batch,stats2,limit){let cursorId=0;for(let row of batch){if(stats2.migrated>=limit)break;try{await insertMigratedRow(sql,row),stats2.migrated+=1}catch{stats2.skipped+=1}cursorId=row.legacy_id}return cursorId}async function runAuditMigration(options){let sql=await getConnection(),sinceTs=options.since?parseSince3(options.since):null,total=await countTotalRows(sql,sinceTs),already=await countAlreadyMigrated(sql,sinceTs),toMigrate=total-already,stats2={total_audit_rows:total,already_migrated:already,to_migrate:Math.max(0,toMigrate),migrated:0,skipped:0,dry_run:Boolean(options.dryRun)};if(options.dryRun||toMigrate<=0)return stats2;let limit=options.limit??Number.POSITIVE_INFINITY,cursorId=0;while(stats2.migrated<limit){let batch=await fetchBatch(sql,cursorId,sinceTs);if(batch.length===0)break;if(cursorId=await applyBatch(sql,batch,stats2,limit),batch.length<BATCH_SIZE2)break}return stats2}function normalizeDetails(raw){if(!raw)return{};if(typeof raw==="string")try{let parsed=JSON.parse(raw);return typeof parsed==="object"&&parsed!==null?parsed:{_raw:raw}}catch{return{_raw:raw}}return raw}async function migrateCommand(options){if(!options.audit)console.error("Usage: genie events migrate --audit [--dry-run] [--since <dur>] [--limit <n>]"),process.exit(1);try{let stats2=await runAuditMigration(options);if(options.json){console.log(JSON.stringify(stats2,null,2));return}if(console.log(color("brightCyan",`Audit Migration${options.dryRun?" (dry-run)":""}`)),console.log(color("dim","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(`Total audit_events: ${stats2.total_audit_rows}`),console.log(`Already migrated: ${stats2.already_migrated}`),console.log(`To migrate: ${stats2.to_migrate}`),!options.dryRun){if(console.log(`Migrated: ${stats2.migrated}`),stats2.skipped>0)console.log(color("yellow",`Skipped (errors): ${stats2.skipped}`))}}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Migration failed: ${msg}`),process.exit(1)}}init_emit();import{randomUUID as
|
|
3968
|
+
)`,[process.env.GENIE_REPO_PATH??process.cwd(),MIGRATE_SUBJECT,"system",MIGRATE_SOURCE,row.actor??"legacy",null,row.event_type,JSON.stringify(buildEnrichedData(row)),createdAt])}async function applyBatch(sql,batch,stats2,limit){let cursorId=0;for(let row of batch){if(stats2.migrated>=limit)break;try{await insertMigratedRow(sql,row),stats2.migrated+=1}catch{stats2.skipped+=1}cursorId=row.legacy_id}return cursorId}async function runAuditMigration(options){let sql=await getConnection(),sinceTs=options.since?parseSince3(options.since):null,total=await countTotalRows(sql,sinceTs),already=await countAlreadyMigrated(sql,sinceTs),toMigrate=total-already,stats2={total_audit_rows:total,already_migrated:already,to_migrate:Math.max(0,toMigrate),migrated:0,skipped:0,dry_run:Boolean(options.dryRun)};if(options.dryRun||toMigrate<=0)return stats2;let limit=options.limit??Number.POSITIVE_INFINITY,cursorId=0;while(stats2.migrated<limit){let batch=await fetchBatch(sql,cursorId,sinceTs);if(batch.length===0)break;if(cursorId=await applyBatch(sql,batch,stats2,limit),batch.length<BATCH_SIZE2)break}return stats2}function normalizeDetails(raw){if(!raw)return{};if(typeof raw==="string")try{let parsed=JSON.parse(raw);return typeof parsed==="object"&&parsed!==null?parsed:{_raw:raw}}catch{return{_raw:raw}}return raw}async function migrateCommand(options){if(!options.audit)console.error("Usage: genie events migrate --audit [--dry-run] [--since <dur>] [--limit <n>]"),process.exit(1);try{let stats2=await runAuditMigration(options);if(options.json){console.log(JSON.stringify(stats2,null,2));return}if(console.log(color("brightCyan",`Audit Migration${options.dryRun?" (dry-run)":""}`)),console.log(color("dim","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(`Total audit_events: ${stats2.total_audit_rows}`),console.log(`Already migrated: ${stats2.already_migrated}`),console.log(`To migrate: ${stats2.to_migrate}`),!options.dryRun){if(console.log(`Migrated: ${stats2.migrated}`),stats2.skipped>0)console.log(color("yellow",`Skipped (errors): ${stats2.skipped}`))}}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Migration failed: ${msg}`),process.exit(1)}}init_emit();import{randomUUID as randomUUID12}from"crypto";import{existsSync as existsSync41,mkdirSync as mkdirSync18,readFileSync as readFileSync25,renameSync as renameSync5,writeFileSync as writeFileSync18}from"fs";import{homedir as homedir34}from"os";import{join as join50}from"path";function genieHome4(){return process.env.GENIE_HOME??join50(homedir34(),".genie")}function stateDir(){return join50(genieHome4(),"state")}function statePath(consumerId){return join50(stateDir(),`consumer-${sanitizeId(consumerId)}.json`)}function sanitizeId(id){return id.replace(/[^a-zA-Z0-9_-]/g,"_").slice(0,128)}function generateConsumerId(prefix="stream"){let uuid=randomUUID12().replace(/-/g,"").slice(0,12);return`${prefix}-${uuid}`}function loadConsumerState(consumerId){let path3=statePath(consumerId);if(!existsSync41(path3))return null;try{let raw=readFileSync25(path3,"utf-8"),parsed=JSON.parse(raw);if(!parsed||typeof parsed!=="object")return null;if(typeof parsed.consumer_id!=="string"||parsed.consumer_id!==consumerId)return null;if(typeof parsed.last_seen_id!=="number"||!Number.isFinite(parsed.last_seen_id))return null;return{consumer_id:parsed.consumer_id,last_seen_id:parsed.last_seen_id,updated_at:typeof parsed.updated_at==="string"?parsed.updated_at:new Date().toISOString(),filters:parsed.filters}}catch{return null}}function saveConsumerState(state){let dir=stateDir();if(!existsSync41(dir))mkdirSync18(dir,{recursive:!0});let target=statePath(state.consumer_id),tmp=`${target}.${process.pid}.tmp`;writeFileSync18(tmp,`${JSON.stringify({...state,updated_at:new Date().toISOString()},null,2)}
|
|
3969
3969
|
`,"utf-8"),renameSync5(tmp,target)}init_term_format();async function resolveTokenPayload(options){let tokenStr=options.token??process.env.GENIE_EVENTS_TOKEN;if(tokenStr&&!options.skipTokenCheck)try{return await verifyToken(tokenStr)}catch(err){if(err instanceof TokenError)console.error(color("red",`token rejected: ${err.code} \u2014 ${err.message}`)),process.exit(2);throw err}if(process.env.GENIE_EVENTS_TOKEN_REQUIRED==="1"&&!options.skipTokenCheck)console.error(color("red","GENIE_EVENTS_TOKEN required but not provided")),process.exit(2);return null}function resolvePrefixes(options,tokenPayload){let baseprefixes=options.kind?[options.kind]:[...DEFAULT_CHANNEL_PREFIXES],tokenPrefixes=tokenPayload?tokenPayload.allowed_channels.map((c)=>c.replace(/^genie_events\./,"")):null,prefixesToListen=tokenPrefixes?baseprefixes.filter((p)=>tokenPrefixes.includes(p)):baseprefixes;if(tokenPrefixes&&prefixesToListen.length===0)console.error(color("red",`token allow-list ${JSON.stringify(tokenPrefixes)} does not intersect requested prefix(es) ${JSON.stringify(baseprefixes)}`)),process.exit(2);return prefixesToListen}function emitGapEvent(consumerId,fromId,toId){try{emitEvent("stream.gap.detected",{consumer_id:consumerId,from_id:fromId,to_id:toId,missing_count:toId-fromId+1},{severity:"warn",source_subsystem:"consumer-stream"})}catch{}}function isTypeAllowed(tokenPayload,subject){if(!tokenPayload)return!0;let allowed=tokenPayload.allowed_types;if(!Array.isArray(allowed)||allowed.length===0)return!0;return allowed.includes(subject??"")}function persistCursor(consumerId,lastSeenId,options){try{saveConsumerState({consumer_id:consumerId,last_seen_id:lastSeenId,updated_at:new Date().toISOString(),filters:{kind:options.kind,severity:options.severity,since:options.since}})}catch{}}async function drainOnce(ctx){if(!ctx.isActive())return;let startId=ctx.getLastSeenId(),batch=await queryV2Batch({afterId:startId,kindPrefix:ctx.options.kind,severity:ctx.options.severity,since:ctx.options.since,limit:500});for(let row of batch){let prev=ctx.getLastSeenId();if(row.id>prev+1&&prev>0)emitGapEvent(ctx.consumerId,prev+1,row.id-1);if(ctx.setLastSeenId(row.id),!isTypeAllowed(ctx.tokenPayload,row.subject))continue;try{ctx.onEvent(row);let delivered=ctx.incrementDelivered();if(ctx.options.maxEvents&&delivered>=ctx.options.maxEvents){ctx.deactivate();break}}catch{}}if(batch.length>0)persistCursor(ctx.consumerId,ctx.getLastSeenId(),ctx.options)}async function runEventsStreamFollow(options,onEvent){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),tokenPayload=await resolveTokenPayload(options),consumerId=options.consumerId??tokenPayload?.subscriber_id??generateConsumerId("stream"),lastSeenId=loadConsumerState(consumerId)?.last_seen_id??await getLatestEventId(),eventsDelivered=0,active=!0,drainChain=Promise.resolve(),prefixesToListen=resolvePrefixes(options,tokenPayload),ctx={options,consumerId,tokenPayload,onEvent,getLastSeenId:()=>lastSeenId,setLastSeenId:(id)=>{lastSeenId=id},incrementDelivered:()=>++eventsDelivered,isActive:()=>active,deactivate:()=>{active=!1}},queueDrain=()=>{drainChain=drainChain.then(()=>drainOnce(ctx)).catch(()=>{})},listeners2=[];for(let prefix of prefixesToListen){let channel=`genie_events.${prefix}`;try{let listener=await sql.listen(channel,()=>{queueDrain()});listeners2.push(listener)}catch{}}let pollTimer=setInterval(queueDrain,2000),heartbeatMs=options.heartbeatIntervalMs??30000,heartbeatTimer=setInterval(()=>{try{emitEvent("consumer.heartbeat",{consumer_id:consumerId,last_event_id_processed:lastSeenId,backlog_depth:0},{severity:"debug",source_subsystem:"consumer-stream"})}catch{}},heartbeatMs);queueDrain();let idleTimer=null;if(options.idleExitMs)idleTimer=setTimeout(()=>{active=!1},options.idleExitMs);return{stop:async()=>{if(active=!1,clearInterval(pollTimer),clearInterval(heartbeatTimer),idleTimer)clearTimeout(idleTimer);try{await drainChain}catch{}for(let l of listeners2)try{await l.unlisten()}catch{}persistCursor(consumerId,lastSeenId,options)},getLastSeenId:()=>lastSeenId,getEventsDelivered:()=>eventsDelivered}}function formatRowPretty(row){let ts3=new Date(row.created_at).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),subject=row.subject??row.text??"unknown",severity=row.severity??"-",sevColor=severity==="error"||severity==="fatal"?color("red",severity):severity==="warn"?color("yellow",severity):severity==="debug"?color("dim",severity):color("cyan",severity),trace=row.trace_id?color("dim",` trace=${row.trace_id.slice(0,8)}`):"",duration=row.duration_ms!=null?` ${color("dim",`${row.duration_ms}ms`)}`:"";return`${color("dim",ts3)} ${sevColor.padEnd(5)} ${color("brightCyan",subject)}${duration}${trace}`}function printRow(row,json2){console.log(json2?JSON.stringify(row):formatRowPretty(row))}async function runStreamOnce(options){let latest=await getLatestEventId(),batch=await queryV2Batch({afterId:Math.max(0,latest-100),kindPrefix:options.kind,severity:options.severity,since:options.since,limit:500});for(let row of batch)printRow(row,options.json)}function printStreamHeader(options){if(options.json)return;let filterDesc=[];if(options.kind)filterDesc.push(`kind=${options.kind}`);if(options.severity)filterDesc.push(`severity=${options.severity}`);if(options.since)filterDesc.push(`since=${options.since}`);let suffix=filterDesc.length>0?` [${filterDesc.join(", ")}]`:"";console.log(color("dim",`Streaming genie_runtime_events${suffix} (Ctrl+C to stop)...`))}async function streamCommand(options){if(!options.follow){await runStreamOnce(options);return}printStreamHeader(options);let handle=await runEventsStreamFollow(options,(row)=>printRow(row,options.json)),shutdown3=async()=>{await handle.stop(),process.exit(0)};process.on("SIGINT",shutdown3),process.on("SIGTERM",shutdown3),await new Promise(()=>{})}init_term_format();function parseDuration2(dur,defaultSeconds){if(!dur)return defaultSeconds;let match=dur.match(/^(\d+)([smhd])$/);if(!match){let n=Number.parseInt(dur,10);return Number.isFinite(n)?n:defaultSeconds}let amount=Number.parseInt(match[1],10),unit=match[2],mult={s:1,m:60,h:3600,d:86400}[unit];return amount*mult}function parseCsv(csv){if(!csv)return[];return csv.split(",").map((s2)=>s2.trim()).filter((s2)=>s2.length>0)}function validateRole(role){if(!role||!ALL_ROLES.includes(role))throw Error(`--role is required and must be one of: ${ALL_ROLES.join(", ")}`);return role}async function subscribeCommand(options){let role=validateRole(options.role),types4=parseCsv(options.types),channels=parseCsv(options.channels),ttlSeconds=parseDuration2(options.ttl,3600),derivedChannels=channels.length>0?channels:types4.length>0?Array.from(new Set(types4.map((t)=>`genie_events.${t.split(".")[0]}`))):[...allowedChannels(role)];try{let{token,payload}=mintToken2({role,allowed_types:types4,allowed_channels:derivedChannels,tenant_id:options.tenant,subscriber_id:options.subscriberId,ttl_seconds:ttlSeconds});if(options.json)console.log(JSON.stringify({token,payload},null,2));else console.log(color("dim",`# genie events subscribe --role ${role}`)),console.log(color("brightCyan",`subscriber_id: ${payload.subscriber_id}`)),console.log(color("dim",`token_id: ${payload.token_id}`)),console.log(color("dim",`role: ${payload.role}`)),console.log(color("dim",`tenant_id: ${payload.tenant_id}`)),console.log(color("dim",`allowed_types: ${payload.allowed_types.length>0?payload.allowed_types.join(","):"(role default)"}`)),console.log(color("dim",`channels: ${payload.allowed_channels.join(",")}`)),console.log(color("dim",`expires_at: ${new Date(payload.exp*1000).toISOString()}`)),console.log(""),console.log(color("green","export GENIE_EVENTS_TOKEN=\\")),console.log(token);return{token,payload}}catch(err){if(err instanceof RBACError)console.error(color("red",`RBAC denied: ${err.message}`)),process.exit(2);throw err}}init_db();init_term_format();var TIMELINE_CTE=`
|
|
3970
3970
|
WITH RECURSIVE
|
|
3971
3971
|
base AS (
|
|
@@ -4536,7 +4536,7 @@ Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s2
|
|
|
4536
4536
|
No fixable violations to apply.`);process.exit(report.summary.total>0?1:0)}function handleDryRunFix(report,markdown,fixed,wishPath,jsonMode){if(jsonMode)console.log(JSON.stringify({...report,dryRun:!0,diff:renderDiff(markdown,fixed)}));else console.log(formatLintReport(report,{color:process.stdout.isTTY??!1,path:wishPath})),console.log(`
|
|
4537
4537
|
--- Dry-run diff (${wishPath}) ---`),console.log(renderDiff(markdown,fixed)),console.log(`
|
|
4538
4538
|
File not modified (--dry-run).`);process.exit(report.summary.total>0?1:0)}async function applyAndReportFix(report,fixed,wishPath,slug,lintOpts,jsonMode){await writeFile10(wishPath,fixed,"utf-8");let docOrError2=parseWishOrError(fixed),report2={...lintWish(docOrError2,fixed,lintOpts),wish:slug,file:wishPath},fixedCount=report.summary.fixable;if(jsonMode)console.log(JSON.stringify({...report2,fixedViolations:fixedCount}));else console.log(`\u2705 Applied ${fixedCount} fix(es) to ${wishPath}`),console.log(""),console.log(formatLintReport(report2,{color:process.stdout.isTTY??!1,path:wishPath}));let remainingErrors=report2.violations.filter((v)=>v.severity==="error").length;process.exit(remainingErrors>0?1:0)}async function runLintFix(report,markdown,wishPath,slug,lintOpts,options){let{applyFixes:applyFixes2}=await Promise.resolve().then(() => (init_wish_lint(),exports_wish_lint)),fixed=applyFixes2(markdown,report),jsonMode=options.json??!1;if(fixed===markdown)handleNoFixableViolations(report,wishPath,jsonMode);if(options.dryRun)handleDryRunFix(report,markdown,fixed,wishPath,jsonMode);return applyAndReportFix(report,fixed,wishPath,slug,lintOpts,jsonMode)}async function wishLintCommand(slug,options){let wishPath=join71(process.cwd(),".genie","wishes",slug,"WISH.md"),jsonMode=options.json??!1;if(!existsSync59(wishPath))reportWishFileMissing(slug,wishPath,jsonMode);let markdown=await readFile17(wishPath,"utf-8"),lintOpts={allowTodoPlaceholders:options.allowTodoPlaceholders},docOrError=parseWishOrError(markdown),report={...lintWish(docOrError,markdown,lintOpts),wish:slug,file:wishPath};if(options.fix){await runLintFix(report,markdown,wishPath,slug,lintOpts,options);return}emitLintReport(report,wishPath,jsonMode),exitWithErrorCount(report)}function reportWishParseError(error2,jsonMode){let payload={error:error2.message,rule:error2.rule,line:error2.line,column:error2.column??null,file:error2.file??null};if(jsonMode){console.log(JSON.stringify(payload));return}if(console.error(`\u274C Parse failed (${payload.rule}): ${payload.error}`),payload.file)console.error(` File: ${payload.file}`);if(payload.line)console.error(` Line: ${payload.line}`)}async function wishParseCommand(slug,options){try{let doc=parseWishFile(slug);console.log(options.json?JSON.stringify(doc):JSON.stringify(doc,null,2));return}catch(error2){if(error2 instanceof WishParseError)reportWishParseError(error2,options.json??!1),process.exit(1);let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function isWishFile(wishPath){try{return(await stat8(wishPath)).isFile()}catch{return!1}}function parseWishSummary(slug){try{let doc=parseWishFile(slug);return{status:doc.metadata.status??"-",groupCount:String(doc.executionGroups.length)}}catch(error2){return{status:error2 instanceof WishParseError?"malformed":"error",groupCount:"-"}}}async function loadStateCounts(slug){try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(slug);if(!state)return{ready:0,inProgress:0,done:0};let ready=0,inProgress=0,done=0;for(let g of Object.values(state.groups))if(g.status==="ready")ready++;else if(g.status==="in_progress")inProgress++;else if(g.status==="done")done++;return{ready,inProgress,done}}catch{return{ready:0,inProgress:0,done:0}}}async function wishListCommand(){let wishesRoot=join71(process.cwd(),".genie","wishes");if(!existsSync59(wishesRoot))console.error(`\u274C Wishes directory not found: ${wishesRoot}`),process.exit(1);let entries=await readdir9(wishesRoot),rows=[];for(let entry2 of entries.sort()){if(entry2.startsWith("_")||entry2.startsWith("."))continue;if(!await isWishFile(join71(wishesRoot,entry2,"WISH.md")))continue;let{status,groupCount}=parseWishSummary(entry2),{ready,inProgress,done}=await loadStateCounts(entry2);rows.push({slug:entry2,status,groupCount,ready,inProgress,done})}if(rows.length===0){console.log("No wishes found under .genie/wishes/");return}let slugW=Math.max(4,...rows.map((r)=>r.slug.length)),statusW=Math.max(6,...rows.map((r)=>r.status.length)),pad=(s2,n)=>s2+" ".repeat(Math.max(0,n-s2.length));console.log(` ${pad("SLUG",slugW)} ${pad("STATUS",statusW)} GROUPS READY IN-PROG DONE`),console.log(` ${"\u2500".repeat(slugW+statusW+32)}`);for(let r of rows)console.log(` ${pad(r.slug,slugW)} ${pad(r.status,statusW)} ${pad(r.groupCount,6)} ${pad(String(r.ready),5)} ${pad(String(r.inProgress),7)} ${r.done}`);console.log(""),console.log(` Total: ${rows.length} wishes`)}function registerWishCommands(program2){let wish=program2.command("wish").description("Wish lifecycle management");wish.command("new <slug>").description("Scaffold a new WISH.md from templates/wish-template.md").option("--force","Overwrite an existing wish directory").action(async(slug,options)=>{await wishNewCommand(slug,options)}),wish.command("lint <slug>").description("Structural health check (stub \u2014 full implementation in Group 3)").option("--json","Emit machine-readable JSON output").option("--fix","Auto-repair deterministic violations").option("--dry-run","With --fix: print diff without writing").option("--allow-todo-placeholders","Pass <TODO> placeholders without emitting todo-placeholder-remaining").action(async(slug,options)=>{await wishLintCommand(slug,options)}),wish.command("parse <slug>").description("Parse WISH.md and emit the WishDocument as JSON").option("--json","One-line JSON output (default: pretty-printed)").action(async(slug,options)=>{await wishParseCommand(slug,options)}),wish.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),wish.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),wish.command("reset <ref>").option("-y, --yes","Skip confirmation prompt (required in non-interactive mode)").description("Reset wish state. <slug>#<group> resets one in-progress group; bare <slug> wipes the wish and recreates from current WISH.md").action(async(ref,options)=>{await resetAction(ref,options)}),wish.command("list").description("Enumerate all wishes with status, group counts, and progress").action(async()=>{await wishListCommand()})}var _T_BOOT=Date.now();try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}function parseNumericFlag2(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}if(process.env.GENIE_PROFILE_DB)console.error(`[profile] imports=${Date.now()-_T_BOOT}ms`);var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);program2.option("--no-interactive","Disable interactive prompts (exit 2 instead of prompting)");program2.option("--no-tui","Skip TUI bootstrap (or set GENIE_TUI_DISABLE=1)");program2.configureHelp({sortSubcommands:!0,showGlobalOptions:!0});program2.configureOutput({outputError:(str5,write)=>{let cmd=program2.commands.find((c)=>process.argv.slice(2,6).includes(c.name())),prefix=cmd?`genie ${cmd.name()}`:"genie";write(`\x1B[31mError (${prefix}): ${str5}\x1B[0m
|
|
4539
|
-
`)}});async function startNamedSession(name){let{randomUUID:
|
|
4539
|
+
`)}});async function startNamedSession(name){let{randomUUID:randomUUID14}=await import("crypto"),{buildTeamLeadCommand:buildTeamLeadCommand2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),sessionId=randomUUID14(),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,sessionId});console.log(`Starting new session: ${name}`);let{spawnSync:spawnSync9}=await import("child_process"),result2=spawnSync9("sh",["-c",cmd],{stdio:"inherit"});if(result2.status)process.exit(result2.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").option("--observability","Report partition health + GENIE_WIDE_EMIT flag state").option("--fix-team-orphans","Archive stale Claude-team config dirs missing config.json (paired with invincible-genie wish migration 050)").option("--dry-run","Pair with --fix-team-orphans to preview archive moves without mutating").option("--json","Emit JSON instead of human output (pairs with --observability)").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentCommands(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerDispatchGroupCommands(program2);registerWishCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerSecCommands(program2);registerProjectCommands(program2);registerPruneCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBrainCommands(program2);registerBriefCommands(program2);registerApprovalCommands(program2);program2.command("done [ref]").description("Close the current turn (inside an agent session) or mark a wish group done (team-lead, <slug>#<group>)").action(async(ref)=>{let{doneAction:doneAction2}=await Promise.resolve().then(() => (init_done(),exports_done));await doneAction2(ref)});program2.command("blocked").description("Close the current turn with outcome=blocked").requiredOption("--reason <message>","Why the turn is blocked").action(async(options)=>{let{blockedAction:blockedAction2}=await Promise.resolve().then(() => (init_blocked(),exports_blocked));await blockedAction2(options)});program2.command("failed").description("Close the current turn with outcome=failed").requiredOption("--reason <message>","Why the turn failed").action(async(options)=>{let{failedAction:failedAction2}=await Promise.resolve().then(() => (init_failed(),exports_failed));await failedAction2(options)});program2.command("pane-trap").description("Internal: write clean_exit_unverified outcome for a dying pane/shell. Invoked by the tmux pane-died hook and the inline shell EXIT trap.").option("--pane-id <id>","tmux pane id (%N) \u2014 resolved to executor via executors.tmux_pane_id").option("--executor-id <id>","explicit executor UUID (preferred when available)").option("--reason <reason>","trap source: pane_died or shell_exit","pane_died").action(async(options)=>{let{paneTrapAction:paneTrapAction2}=await Promise.resolve().then(() => (init_pane_trap2(),exports_pane_trap));await paneTrapAction2(options)});installWorkspaceCheck(program2);var auditTimers=new Map,auditSpans=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{}),(async()=>{try{let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(!isWideEmitEnabled2())return;let{startSpan:startSpan2}=await Promise.resolve().then(() => (init_emit(),exports_emit)),{getAmbient:getAmbient2}=await Promise.resolve().then(() => (init_trace_context(),exports_trace_context)),handle=startSpan2("cli.command",{command:name,args:actionCommand.args??[],cwd:process.cwd()},{severity:"debug",source_subsystem:"cli",ctx:getAmbient2()??void 0,agent:getActor()});auditSpans.set(name,handle)}catch{}})()});program2.hook("postAction",async(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name);try{let{isConnected:isConnected2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!isConnected2())return;await recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs})}catch{}let handle=auditSpans.get(name);auditSpans.delete(name);try{if(handle){let{isWideEmitEnabled:isWideEmitEnabled2}=await Promise.resolve().then(() => exports_observability_flag);if(isWideEmitEnabled2()){let{endSpan:endSpan2}=await Promise.resolve().then(() => (init_emit(),exports_emit));endSpan2(handle,{exit_code:0,duration_ms:durationMs??0},{severity:"debug",source_subsystem:"cli",agent:getActor()})}}}catch{}try{let{flushNow:flushNow2}=await Promise.resolve().then(() => (init_emit(),exports_emit));await flushNow2()}catch{}});program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--stream","Stream SDK messages to stdout in real-time (claude-sdk provider)").option("--stream-format <format>","Streaming output format: text, json, ndjson (default: text)","text").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag2("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag2("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").option("--prompt <text>","Initial prompt to send as the first user message").addHelpText("after",`
|
|
4540
4540
|
Examples:
|
|
4541
4541
|
genie spawn engineer # Spawn built-in engineer role
|
|
4542
4542
|
genie spawn researcher --model sonnet # Spawn with model override
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260428.
|
|
3
|
+
"version": "4.260428.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": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"react": "19.2.5",
|
|
63
63
|
"react-dom": "19.2.5",
|
|
64
64
|
"systeminformation": "5.31.5",
|
|
65
|
-
"uuid": "
|
|
65
|
+
"uuid": "14.0.0",
|
|
66
66
|
"zod": "3.25.76"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260428.
|
|
3
|
+
"version": "4.260428.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"
|