@automagik/genie 4.260331.5 → 4.260331.6

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
@@ -513,7 +513,17 @@ tmux set-option -w pane-active-border-style "fg=$COLOR"
513
513
  ${sql.array(rows.map((r)=>r.actor??""))}::text[],
514
514
  ${sql.array(rows.map((r)=>JSON.stringify(r.details)))}::jsonb[]
515
515
  )
516
- `}catch{}}function getOtelPort(){let envPort=process.env.GENIE_OTEL_PORT;if(envPort){let parsed=Number.parseInt(envPort,10);if(!Number.isNaN(parsed)&&parsed>0&&parsed<65536)return parsed}return getActivePort()+1}async function startOtelReceiver(){if(server)return!0;let port=getOtelPort();try{return server=Bun.serve({port,hostname:"127.0.0.1",fetch:async(req)=>{let url=new URL(req.url);if(req.method==="POST"&&url.pathname==="/v1/logs"){try{let payload=await req.json(),rows=processLogs(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="POST"&&url.pathname==="/v1/metrics"){try{let payload=await req.json(),rows=processMetrics(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="GET"&&url.pathname==="/health")return Response.json({status:"ok",port});return new Response("Not Found",{status:404})}}),!0}catch(err){let message=err instanceof Error?err.message:String(err);if(message.includes("EADDRINUSE")||message.includes("address already in use"))console.warn(`OTel receiver: port ${port} already in use \u2014 skipping (another instance may be running)`);else console.warn(`OTel receiver: failed to start on port ${port}: ${message}`);return!1}}function stopOtelReceiver(){if(server)server.stop(),server=null}var server=null;var init_otel_receiver=__esm(()=>{init_db()});function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}import{randomUUID as randomUUID3}from"crypto";var native_default;var init_native=__esm(()=>{native_default={randomUUID:randomUUID3}});import{randomFillSync}from"crypto";function rng(){if(poolPtr>rnds8Pool.length-16)randomFillSync(rnds8Pool),poolPtr=0;return rnds8Pool.slice(poolPtr,poolPtr+=16)}var rnds8Pool,poolPtr;var init_rng=__esm(()=>{rnds8Pool=new Uint8Array(256),poolPtr=rnds8Pool.length});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(native_default.randomUUID&&!buf&&!options)return native_default.randomUUID();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_native();init_rng();init_stringify();v4_default=v4});var init_esm7=__esm(()=>{init_v4()});var exports_mailbox={};__export(exports_mailbox,{toNativeInboxMessage:()=>toNativeInboxMessage,send:()=>send,readOutbox:()=>readOutbox,markRead:()=>markRead,markDelivered:()=>markDelivered,inbox:()=>inbox,getUnread:()=>getUnread});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();await sql`
516
+ `}catch{}}function getOtelPort(){let envPort=process.env.GENIE_OTEL_PORT;if(envPort){let parsed=Number.parseInt(envPort,10);if(!Number.isNaN(parsed)&&parsed>0&&parsed<65536)return parsed}return getActivePort()+1}async function startOtelReceiver(){if(server)return!0;let port=getOtelPort();try{return server=Bun.serve({port,hostname:"127.0.0.1",fetch:async(req)=>{let url=new URL(req.url);if(req.method==="POST"&&url.pathname==="/v1/logs"){try{let payload=await req.json(),rows=processLogs(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="POST"&&url.pathname==="/v1/metrics"){try{let payload=await req.json(),rows=processMetrics(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="GET"&&url.pathname==="/health")return Response.json({status:"ok",port});return new Response("Not Found",{status:404})}}),!0}catch(err){let message=err instanceof Error?err.message:String(err);if(message.includes("EADDRINUSE")||message.includes("address already in use"))console.warn(`OTel receiver: port ${port} already in use \u2014 skipping (another instance may be running)`);else console.warn(`OTel receiver: failed to start on port ${port}: ${message}`);return!1}}function stopOtelReceiver(){if(server)server.stop(),server=null}var server=null;var init_otel_receiver=__esm(()=>{init_db()});function debug(msg){if(process.env.DEBUG)console.error(`[target-resolver] ${msg}`)}async function defaultTmuxLookup(sessionName,windowName){try{let tmux=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),session=await tmux.findSessionByName(sessionName);if(!session)return null;let windows=await tmux.listWindows(session.id);if(!windows||windows.length===0)return null;let targetWindow;if(windowName){if(targetWindow=windows.find((w)=>w.name===windowName),!targetWindow)return null}else targetWindow=windows.find((w)=>w.active)||windows[0];let panes=await tmux.listPanes(targetWindow.id);if(!panes||panes.length===0)return null;return{paneId:(panes.find((p)=>p.active)||panes[0]).id,session:sessionName}}catch{return null}}async function defaultIsPaneLive(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{pane_id}'`)).trim()===paneId}catch{return!1}}async function defaultCleanupDeadPane(workerId,paneId){try{await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).removeSubPane(workerId,paneId)}catch{}}async function defaultDeriveSession(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{session_name}'`)).trim()||null}catch{return null}}async function assertLive(paneId,isPaneLive,errorMsg,cleanup){if(!await isPaneLive(paneId)){if(cleanup)await cleanup();throw Error(errorMsg)}}async function resolveRawPane(target,opts){if(opts.checkLiveness)await assertLive(target,opts.isPaneLive,`Pane ${target} is dead or does not exist. Check with: tmux list-panes -a`);let session=await opts.deriveSession(target);return{paneId:target,session:session??void 0,resolvedVia:"raw"}}async function resolveWindowId(target,workers,opts){let matchingWorker=Object.values(workers).find((w)=>w.windowId===target);if(!matchingWorker)throw Error(`Window "${target}" not found in worker registry.
517
+ Run 'genie agent list' to list agents.`);if(opts.checkLiveness)await assertLive(matchingWorker.paneId,opts.isPaneLive,`Window ${target}: worker ${matchingWorker.id} pane ${matchingWorker.paneId} is dead. Run 'genie agent kill${matchingWorker.id}' to clean up.`);return{paneId:matchingWorker.paneId,session:matchingWorker.session,workerId:matchingWorker.id,resolvedVia:"worker"}}function resolveWorkerSubPane(worker,leftSide,rightSide){let index=Number.parseInt(rightSide,10);if(Number.isNaN(index)||index<0)throw Error(`Invalid sub-pane index "${rightSide}" for worker "${leftSide}". Use a non-negative integer (0 = primary, 1+ = sub-panes).`);let paneId=getPaneByIndex(worker,index);if(!paneId){let maxIndex=worker.subPanes?worker.subPanes.length:0;throw Error(`Worker "${leftSide}" has no sub-pane index ${index}. Available: 0 (primary)${maxIndex>0?`, 1-${maxIndex} (sub-panes)`:""}. Sub-pane index ${index} does not exist.`)}return paneId}function pickUnique(target,candidates,label){if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 ${label}: ${ids}
518
+ Use the full ID instead.`)}function resolveByRole(target,workers,currentTeam){if(!currentTeam)return null;let candidates=Object.entries(workers).filter(([,w])=>w.role===target&&w.team===currentTeam);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}" in team "${currentTeam}"`)}function resolveByCustomName(target,workers,currentTeam){if(currentTeam){let teamCandidates=Object.entries(workers).filter(([,w])=>w.customName===target&&w.team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(([,w])=>w.customName===target);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName "${target}"`)}function resolveByPartialId(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&id.endsWith(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
519
+ Use the full ID instead.`)}function resolveBySubstring(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&!id.endsWith(target)&&id.includes(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
520
+ Use the full ID instead.`)}function resolveByRoleGlobal(target,workers){let candidates=Object.entries(workers).filter(([,w])=>w.role===target);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}"`)}function resolveByPartialRole(target,workers,currentTeam){let matches=([,w])=>w.role!==void 0&&w.role!==target&&w.role.startsWith(target);if(currentTeam){let teamCandidates=Object.entries(workers).filter((e)=>matches(e)&&e[1].team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with role starting with "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(matches);return pickUnique(target,allCandidates,`${allCandidates.length} workers with role starting with "${target}"`)}function resolveByPartialCustomName(target,workers,currentTeam){let matches=([,w])=>w.customName!==void 0&&w.customName!==target&&w.customName.startsWith(target);if(currentTeam){let teamCandidates=Object.entries(workers).filter((e)=>matches(e)&&e[1].team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName starting with "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(matches);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName starting with "${target}"`)}async function resolveColonTarget(target,workers,opts){let colonIndex=target.indexOf(":"),leftSide=target.substring(0,colonIndex),rightSide=target.substring(colonIndex+1),worker=workers[leftSide];if(worker){let paneId=resolveWorkerSubPane(worker,leftSide,rightSide),index=Number.parseInt(rightSide,10);if(opts.checkLiveness)await assertLive(paneId,opts.isPaneLive,`Worker ${leftSide}: pane ${paneId} is dead. Run 'genie agent kill${leftSide}' to clean up.`,()=>opts.cleanupDeadPane(leftSide,paneId));return{paneId,session:worker.session,workerId:leftSide,paneIndex:index,resolvedVia:"worker"}}let sessionWindowResult=await opts.tmuxLookup(leftSide,rightSide);if(!sessionWindowResult)throw Error(`Target "${target}" not found. No worker "${leftSide}" in registry and no tmux session:window "${leftSide}:${rightSide}" found.
521
+ Run 'genie agent list' to list agents.`);if(opts.checkLiveness)await assertLive(sessionWindowResult.paneId,opts.isPaneLive,`Session "${leftSide}" window "${rightSide}": pane ${sessionWindowResult.paneId} is dead.`);return{paneId:sessionWindowResult.paneId,session:sessionWindowResult.session,resolvedVia:"session:window"}}async function resolveBareName(target,workers,opts){let worker=workers[target];if(worker){if(opts.checkLiveness)await assertLive(worker.paneId,opts.isPaneLive,`Worker ${target}: pane ${worker.paneId} is dead. Run 'genie agent kill${target}' to clean up.`,()=>opts.cleanupDeadPane(target,worker.paneId));return{paneId:worker.paneId,session:worker.session,workerId:target,resolvedVia:"worker"}}let currentTeam=opts.getCurrentTeam?await opts.getCurrentTeam():await getCurrentSessionName()??process.env.GENIE_TEAM??null,fuzzyMatch=resolveByRole(target,workers,currentTeam)??resolveByCustomName(target,workers,currentTeam)??resolveByPartialId(target,workers,currentTeam)??resolveBySubstring(target,workers,currentTeam)??resolveByRoleGlobal(target,workers)??resolveByPartialRole(target,workers,currentTeam)??resolveByPartialCustomName(target,workers,currentTeam);if(fuzzyMatch){let rid=fuzzyMatch.workerId??target;if(opts.checkLiveness)await assertLive(fuzzyMatch.paneId,opts.isPaneLive,`Worker ${rid}: pane ${fuzzyMatch.paneId} is dead.`,()=>opts.cleanupDeadPane(rid,fuzzyMatch.paneId));return fuzzyMatch}throw Error(`Target "${target}" not found. Not a worker or pane ID.
522
+ Run 'genie agent list' to list agents.`)}async function resolveTarget(target,options={}){let{checkLiveness=!1,workers:injectedWorkers,tmuxLookup=defaultTmuxLookup,isPaneLive=defaultIsPaneLive,cleanupDeadPane=defaultCleanupDeadPane,deriveSession=defaultDeriveSession}=options;if(debug(`resolving "${target}"`),target.startsWith("%"))return resolveRawPane(target,{checkLiveness,isPaneLive,deriveSession});if(target.startsWith("@")){let workers2=await getWorkers(injectedWorkers,options.registryPath);return resolveWindowId(target,workers2,{checkLiveness,isPaneLive})}let workers=await getWorkers(injectedWorkers,options.registryPath);if(target.indexOf(":")!==-1)return resolveColonTarget(target,workers,{checkLiveness,isPaneLive,cleanupDeadPane,tmuxLookup});return resolveBareName(target,workers,{checkLiveness,isPaneLive,cleanupDeadPane,getCurrentTeam:options.getCurrentTeam})}function formatResolvedLabel(resolved,originalTarget){let parts=[];if(resolved.workerId){if(parts.push(resolved.workerId),resolved.paneIndex!==void 0&&resolved.paneIndex>0)parts[parts.length-1]+=`:${resolved.paneIndex}`}else parts.push(originalTarget);let details=[`pane ${resolved.paneId}`];if(resolved.session)details.push(`session ${resolved.session}`);return`${parts[0]} (${details.join(", ")})`}async function getWorkers(injected,_registryPath){if(injected!==void 0)return injected;try{let workersList=await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).list(),map2={};for(let w of workersList)map2[w.id]=w;return map2}catch{return{}}}function getPaneByIndex(worker,index){if(index===0)return worker.paneId;let subIndex=index-1;if(!worker.subPanes||subIndex>=worker.subPanes.length||subIndex<0)return null;return worker.subPanes[subIndex]}var init_target_resolver=__esm(()=>{init_tmux()});function stripAnsi(str2){return str2.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"")}function matchPatterns(content,patterns){let cleanContent=stripAnsi(content),matches=[];for(let pattern of patterns){let regex=new RegExp(pattern.pattern.source,pattern.pattern.flags||"g"),match=regex.exec(cleanContent);while(match!==null){if(matches.push({type:pattern.type,match,extracted:pattern.extract?pattern.extract(match):void 0}),!pattern.pattern.flags?.includes("g"))break;match=regex.exec(cleanContent)}}return matches}function hasMatch(content,patterns){return matchPatterns(content,patterns).length>0}function getFirstMatch(content,patterns){let matches=matchPatterns(content,patterns);return matches.length>0?matches[0]:null}var permissionPatterns,questionPatterns,errorPatterns,completionPatterns,workingPatterns,idlePatterns,toolUsePatterns;var init_patterns=__esm(()=>{permissionPatterns=[{type:"bash_permission",pattern:/Allow (?:Bash|bash|command|shell).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"file_permission",pattern:/Allow (?:Edit|Write|Read|file|reading|writing|editing).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"mcp_permission",pattern:/Allow (?:MCP|mcp|tool).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"generic_permission",pattern:/^(?:Allow|Confirm|Approve)\s+(?:this|the|once|always)?\s*(?:\w+)?\s*\?\s*(?:\[([YyNn])\/([YyNn])\])?/im,extract:(match)=>({default:match[1]||"y"})},{type:"claude_code_yes_no",pattern:/(?:Yes|No)\s*$/m},{type:"claude_code_permission_block",pattern:/(?:Allow|Run|Execute)\s+(?:once|always)/i}],questionPatterns=[{type:"numbered_options",pattern:/\[(\d+)\]\s+(.+?)(?=\[(\d+)\]|$)/g,extract:(match)=>({number:match[1],option:match[2].trim()})},{type:"lettered_options",pattern:/\(([a-z])\)\s+(.+?)(?=\([a-z]\)|$)/gi,extract:(match)=>({letter:match[1],option:match[2].trim()})},{type:"yes_no_question",pattern:/\?\s*\[([YyNn])\/([YyNn])\]\s*$/,extract:(match)=>({default:match[1]})},{type:"claude_code_numbered_options",pattern:/(?:\u276F|>)?\s*(\d+)\.\s+(.+?)(?:\n|$)/g,extract:(match)=>({number:match[1],option:match[2].trim()})},{type:"claude_code_plan_approval",pattern:/Would you like to proceed\?/i}],errorPatterns=[{type:"error",pattern:/(?:^|\n)\s*(?:Error|ERROR|error):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"failed",pattern:/(?:^|\n)\s*(?:Failed|FAILED|failed):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"exception",pattern:/(?:^|\n)\s*(?:Exception|EXCEPTION|Uncaught|Unhandled):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"api_error",pattern:/(?:API|api)\s+(?:error|Error|ERROR):\s*(.+)/,extract:(match)=>({message:match[1]})}],completionPatterns=[{type:"checkmark",pattern:/(?:\u2713|\u2714|\u2611\uFE0E)/},{type:"success_message",pattern:/(?:Successfully|Completed|Done|Finished|Created|Updated|Saved)/i},{type:"task_complete",pattern:/(?:task|operation|process)\s+(?:complete|completed|finished|done)/i}],workingPatterns=[{type:"thinking",pattern:/(?:Thinking|thinking|Processing|processing)\.\.\./},{type:"spinner",pattern:/[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u28FE\u28FD\u28FB\u28BF\u287F\u28DF\u28EF\u28F7]/},{type:"loading",pattern:/(?:Loading|loading|Working|working)\.\.\./},{type:"claude_code_working",pattern:/(?:\uD83D\uDEE0\uFE0F|\uD83D\uDD27|\u2699\uFE0F)\s*(?:Read|Edit|Write|Bash|Glob|Grep|Task)/u},{type:"claude_code_streaming",pattern:/\u258C$/},{type:"claude_code_propagating",pattern:/Propagating\u2026/}],idlePatterns=[{type:"claude_prompt",pattern:/(?:^|\n)\s*>\s*$/},{type:"claude_code_prompt",pattern:/\u276F\s*(?!\d\.)/},{type:"claude_code_input_line",pattern:/\u276F\s*.+\n\u2500+\n/},{type:"idle_indicator",pattern:/\|\s*idle\s*$/i},{type:"input_prompt",pattern:/(?:^|\n)(?:Enter|Input|Type|Provide).*:\s*$/i}],toolUsePatterns=[{type:"run_command",pattern:/(?:Run|Running|Executing)\s+(?:command|bash):\s*(.+)/i,extract:(match)=>({command:match[1]})},{type:"read_file",pattern:/(?:Read|Reading)\s+(?:file):\s*(.+)/i,extract:(match)=>({file:match[1]})},{type:"write_file",pattern:/(?:Write|Writing|Edit|Editing)\s+(?:file|to):\s*(.+)/i,extract:(match)=>({file:match[1]})},{type:"search",pattern:/(?:Search|Searching|Grep|Glob)(?:ing)?:\s*(.+)/i,extract:(match)=>({query:match[1]})}]});var exports_state_detector={};__export(exports_state_detector,{detectState:()=>detectState});function detectQuestionState(questionMatches,hasPlanApproval,baseState){if(questionMatches.length===0&&!hasPlanApproval)return null;let menuOptions=questionMatches.filter((m)=>m.type==="claude_code_numbered_options"&&m.extracted?.option).map((m)=>m.extracted?.option).filter((o)=>o!==void 0);if(menuOptions.length>=2||hasPlanApproval)return{...baseState,type:"question",options:menuOptions.length>0?menuOptions:void 0,detail:hasPlanApproval?"plan_approval":void 0,confidence:0.85};let otherOptions=questionMatches.filter((m)=>m.extracted?.option&&m.type!=="claude_code_numbered_options").map((m)=>m.extracted?.option).filter((o)=>o!==void 0);if(otherOptions.length>=2)return{...baseState,type:"question",options:otherOptions,confidence:0.85};return null}function detectState(output,options={}){let{linesToAnalyze=50,minConfidence=0.3}=options,lines=output.split(`
523
+ `),recentLines=lines.slice(-linesToAnalyze).join(`
524
+ `),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(`
525
+ `)),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(`
526
+ `));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()});import{randomUUID as randomUUID3}from"crypto";var native_default;var init_native=__esm(()=>{native_default={randomUUID:randomUUID3}});import{randomFillSync}from"crypto";function rng(){if(poolPtr>rnds8Pool.length-16)randomFillSync(rnds8Pool),poolPtr=0;return rnds8Pool.slice(poolPtr,poolPtr+=16)}var rnds8Pool,poolPtr;var init_rng=__esm(()=>{rnds8Pool=new Uint8Array(256),poolPtr=rnds8Pool.length});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(native_default.randomUUID&&!buf&&!options)return native_default.randomUUID();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_native();init_rng();init_stringify();v4_default=v4});var init_esm7=__esm(()=>{init_v4()});var exports_mailbox={};__export(exports_mailbox,{toNativeInboxMessage:()=>toNativeInboxMessage,send:()=>send,readOutbox:()=>readOutbox,markRead:()=>markRead,markDelivered:()=>markDelivered,inbox:()=>inbox,getUnread:()=>getUnread});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();await sql`
517
527
  INSERT INTO mailbox (id, from_worker, to_worker, body, repo_path, read, delivered_at, created_at)
518
528
  VALUES (${id}, ${from}, ${to}, ${body}, ${repoPath}, false, ${null}, ${now})
519
529
  `;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{}return message}async function inbox(repoPath,workerId){let sql=await getConnection(),workerIds=normalizeWorkerIds(workerId);if(workerIds.length===0)return[];return(await sql`
@@ -535,312 +545,49 @@ tmux set-option -w pane-active-border-style "fg=$COLOR"
535
545
  `).map(rowToMessage)}async function markRead(messageId){return(await(await getConnection())`
536
546
  UPDATE mailbox SET read = true WHERE id = ${messageId}
537
547
  RETURNING id
538
- `).length>0}function toNativeInboxMessage(msg,color="blue"){let words=msg.body.split(/\s+/),summary=words.slice(0,8).join(" ")+(words.length>8?"...":"");return{from:msg.from,text:msg.body,summary,timestamp:msg.createdAt,color,read:!1}}var init_mailbox=__esm(()=>{init_esm7();init_db()});var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile3,readdir as readdir3,stat as stat2}from"fs/promises";import{join as join19}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join19(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join19(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join19(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join19(projectDir,"sessions-index.json");try{let content=await readFile3(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir3(projectDir);for(let entry of entries)if(entry.endsWith(".jsonl")&&!entry.startsWith(".")){let filePath=join19(projectDir,entry),stats=await stat2(filePath),sessionId=entry.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats.mtimeMs,messageCount:0,created:stats.birthtime.toISOString(),modified:stats.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry.model=raw.message.model;if(raw.message.usage)entry.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry,raw);if(raw.data)entry.data=raw.data;return entry}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile3(logPath,"utf-8")).split(`
539
- `);for(let line of lines){let entry=parseLogEntry(line);if(entry)entries.push(entry)}}catch{}return entries}async function findLogsForWorkspace(workspacePath,claudeDir){let projectDir=await findClaudeProjectDir(workspacePath,claudeDir);if(!projectDir)return null;let session=await findActiveSession(projectDir);if(!session)return null;return{projectDir,session}}async function getLogsForPane(paneWorkdir,claudeDir){let result=await findLogsForWorkspace(paneWorkdir,claudeDir);if(!result)return null;return{logPath:result.session.fullPath,session:result.session,projectDir:result.projectDir}}function extractText(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"&&"text"in item)parts.push(String(item.text));return parts.join(" ")}function convertAssistant(entry,base){let results=[],text=entry.message?extractText(entry.message.content):"";if(text)results.push({...base,role:"assistant",timestamp:entry.timestamp,text,usage:entry.usage?{input:entry.usage.input_tokens,output:entry.usage.output_tokens}:void 0});if(entry.toolCalls)for(let tc of entry.toolCalls)results.push({...base,role:"tool_call",timestamp:entry.timestamp,text:`${tc.name}: ${JSON.stringify(tc.input).slice(0,200)}`,toolCall:{id:tc.id,name:tc.name,input:tc.input}});return results}function claudeEntryToTranscript(entry){let base={provider:"claude",model:entry.model,raw:entry.raw};if(entry.type==="user"&&entry.message){let text=extractText(entry.message.content);return text?[{...base,role:"user",timestamp:entry.timestamp,text}]:[]}if(entry.type==="assistant")return convertAssistant(entry,base);if(entry.type==="system"||entry.type==="progress"){let text=entry.message?extractText(entry.message.content):"";return text?[{...base,role:"system",timestamp:entry.timestamp,text}]:[]}return[]}var claudeTranscriptProvider;var init_claude_logs=__esm(()=>{claudeTranscriptProvider={async discoverLogPath(worker){let workspacePath=worker.worktree||worker.repoPath,projectDir=await findClaudeProjectDir(workspacePath);if(!projectDir)return null;if(worker.claudeSessionId){let directPath=join19(projectDir,`${worker.claudeSessionId}.jsonl`);try{return await access(directPath),directPath}catch{let session=await findSessionById(projectDir,worker.claudeSessionId);if(session?.fullPath)return session.fullPath}}return(await getLogsForPane(workspacePath))?.logPath??null},async readEntries(logPath){return(await readLogFile(logPath)).flatMap(claudeEntryToTranscript)}}});class AppPtyProvider{name="app-pty";transport="process";buildSpawnCommand(ctx){let params=spawnContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let{access:access2,readdir:readdir4}=await import("fs/promises"),{join:join20}=await import("path"),claudeDir=join20(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return{sessionId}}let jsonlName=`${sessionId}.jsonl`;if(executor.repoPath||executor.worktree){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),path2=executor.repoPath??executor.worktree,hash=projectPathToHash2(path2),candidate=join20(claudeDir,hash,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}try{let dirs=await readdir4(claudeDir);for(let dir of dirs){let candidate=join20(claudeDir,dir,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}}catch{}return{sessionId}}async detectState(executor){if(!executor.pid)return"terminated";try{process.kill(executor.pid,0);let metadata=executor.metadata;if(metadata.appPtyState&&typeof metadata.appPtyState==="string")return metadata.appPtyState;return"running"}catch{return"terminated"}}async terminate(executor){if(!executor.pid)return;try{process.kill(executor.pid,"SIGTERM"),await new Promise((r)=>setTimeout(r,500))}catch{}try{process.kill(executor.pid,0),process.kill(executor.pid,"SIGKILL")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
540
- SELECT a.custom_name, a.team
541
- FROM executors e
542
- JOIN agents a ON e.agent_id = a.id
543
- WHERE e.id = ${executorId}
544
- LIMIT 1
545
- `;if(rows.length===0)return;let{custom_name:agentName,team:teamName}=rows[0];if(!agentName||!teamName)return;let{writeNativeInbox:writeNativeInbox2}=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams));await writeNativeInbox2(teamName,agentName,{from:"system",text:message.text,summary:message.text.slice(0,120),timestamp:new Date().toISOString(),color:"red",read:!1})}}function spawnContextToParams(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,sessionId:ctx.sessionId,systemPromptFile:ctx.systemPromptFile,systemPrompt:ctx.systemPrompt,promptMode:ctx.promptMode,initialPrompt:ctx.initialPrompt,name:ctx.name,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}function resumeContextToParams(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,resume:ctx.claudeSessionId,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}var init_app_pty=__esm(()=>{init_provider_adapters()});function stripAnsi(str2){return str2.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"")}function matchPatterns(content,patterns){let cleanContent=stripAnsi(content),matches=[];for(let pattern of patterns){let regex=new RegExp(pattern.pattern.source,pattern.pattern.flags||"g"),match=regex.exec(cleanContent);while(match!==null){if(matches.push({type:pattern.type,match,extracted:pattern.extract?pattern.extract(match):void 0}),!pattern.pattern.flags?.includes("g"))break;match=regex.exec(cleanContent)}}return matches}function hasMatch(content,patterns){return matchPatterns(content,patterns).length>0}function getFirstMatch(content,patterns){let matches=matchPatterns(content,patterns);return matches.length>0?matches[0]:null}var permissionPatterns,questionPatterns,errorPatterns,completionPatterns,workingPatterns,idlePatterns,toolUsePatterns;var init_patterns=__esm(()=>{permissionPatterns=[{type:"bash_permission",pattern:/Allow (?:Bash|bash|command|shell).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"file_permission",pattern:/Allow (?:Edit|Write|Read|file|reading|writing|editing).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"mcp_permission",pattern:/Allow (?:MCP|mcp|tool).*\?\s*(?:\[([YyNn])\/([YyNn])\])?/i,extract:(match)=>({default:match[1]||"y"})},{type:"generic_permission",pattern:/^(?:Allow|Confirm|Approve)\s+(?:this|the|once|always)?\s*(?:\w+)?\s*\?\s*(?:\[([YyNn])\/([YyNn])\])?/im,extract:(match)=>({default:match[1]||"y"})},{type:"claude_code_yes_no",pattern:/(?:Yes|No)\s*$/m},{type:"claude_code_permission_block",pattern:/(?:Allow|Run|Execute)\s+(?:once|always)/i}],questionPatterns=[{type:"numbered_options",pattern:/\[(\d+)\]\s+(.+?)(?=\[(\d+)\]|$)/g,extract:(match)=>({number:match[1],option:match[2].trim()})},{type:"lettered_options",pattern:/\(([a-z])\)\s+(.+?)(?=\([a-z]\)|$)/gi,extract:(match)=>({letter:match[1],option:match[2].trim()})},{type:"yes_no_question",pattern:/\?\s*\[([YyNn])\/([YyNn])\]\s*$/,extract:(match)=>({default:match[1]})},{type:"claude_code_numbered_options",pattern:/(?:\u276F|>)?\s*(\d+)\.\s+(.+?)(?:\n|$)/g,extract:(match)=>({number:match[1],option:match[2].trim()})},{type:"claude_code_plan_approval",pattern:/Would you like to proceed\?/i}],errorPatterns=[{type:"error",pattern:/(?:^|\n)\s*(?:Error|ERROR|error):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"failed",pattern:/(?:^|\n)\s*(?:Failed|FAILED|failed):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"exception",pattern:/(?:^|\n)\s*(?:Exception|EXCEPTION|Uncaught|Unhandled):\s*(.+)/,extract:(match)=>({message:match[1]})},{type:"api_error",pattern:/(?:API|api)\s+(?:error|Error|ERROR):\s*(.+)/,extract:(match)=>({message:match[1]})}],completionPatterns=[{type:"checkmark",pattern:/(?:\u2713|\u2714|\u2611\uFE0E)/},{type:"success_message",pattern:/(?:Successfully|Completed|Done|Finished|Created|Updated|Saved)/i},{type:"task_complete",pattern:/(?:task|operation|process)\s+(?:complete|completed|finished|done)/i}],workingPatterns=[{type:"thinking",pattern:/(?:Thinking|thinking|Processing|processing)\.\.\./},{type:"spinner",pattern:/[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u28FE\u28FD\u28FB\u28BF\u287F\u28DF\u28EF\u28F7]/},{type:"loading",pattern:/(?:Loading|loading|Working|working)\.\.\./},{type:"claude_code_working",pattern:/(?:\uD83D\uDEE0\uFE0F|\uD83D\uDD27|\u2699\uFE0F)\s*(?:Read|Edit|Write|Bash|Glob|Grep|Task)/u},{type:"claude_code_streaming",pattern:/\u258C$/},{type:"claude_code_propagating",pattern:/Propagating\u2026/}],idlePatterns=[{type:"claude_prompt",pattern:/(?:^|\n)\s*>\s*$/},{type:"claude_code_prompt",pattern:/\u276F\s*(?!\d\.)/},{type:"claude_code_input_line",pattern:/\u276F\s*.+\n\u2500+\n/},{type:"idle_indicator",pattern:/\|\s*idle\s*$/i},{type:"input_prompt",pattern:/(?:^|\n)(?:Enter|Input|Type|Provide).*:\s*$/i}],toolUsePatterns=[{type:"run_command",pattern:/(?:Run|Running|Executing)\s+(?:command|bash):\s*(.+)/i,extract:(match)=>({command:match[1]})},{type:"read_file",pattern:/(?:Read|Reading)\s+(?:file):\s*(.+)/i,extract:(match)=>({file:match[1]})},{type:"write_file",pattern:/(?:Write|Writing|Edit|Editing)\s+(?:file|to):\s*(.+)/i,extract:(match)=>({file:match[1]})},{type:"search",pattern:/(?:Search|Searching|Grep|Glob)(?:ing)?:\s*(.+)/i,extract:(match)=>({query:match[1]})}]});var exports_state_detector={};__export(exports_state_detector,{detectState:()=>detectState});function detectQuestionState(questionMatches,hasPlanApproval,baseState){if(questionMatches.length===0&&!hasPlanApproval)return null;let menuOptions=questionMatches.filter((m)=>m.type==="claude_code_numbered_options"&&m.extracted?.option).map((m)=>m.extracted?.option).filter((o)=>o!==void 0);if(menuOptions.length>=2||hasPlanApproval)return{...baseState,type:"question",options:menuOptions.length>0?menuOptions:void 0,detail:hasPlanApproval?"plan_approval":void 0,confidence:0.85};let otherOptions=questionMatches.filter((m)=>m.extracted?.option&&m.type!=="claude_code_numbered_options").map((m)=>m.extracted?.option).filter((o)=>o!==void 0);if(otherOptions.length>=2)return{...baseState,type:"question",options:otherOptions,confidence:0.85};return null}function detectState(output,options={}){let{linesToAnalyze=50,minConfidence=0.3}=options,lines=output.split(`
546
- `),recentLines=lines.slice(-linesToAnalyze).join(`
547
- `),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(`
548
- `)),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(`
549
- `));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()});class ClaudeCodeProvider{name="claude-code";transport="tmux";buildSpawnCommand(ctx){let params=spawnContextToParams2(ctx);return buildClaudeCommand(params)}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let logPath=await findSessionLogPath(sessionId,executor.repoPath??executor.worktree);return{sessionId,logPath:logPath??void 0}}async detectState(executor){let paneId=executor.tmuxPaneId;if(!paneId)return"terminated";try{let{capturePaneContent:capturePaneContent2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),content=await capturePaneContent2(paneId,50);if(!content||!content.trim())return"idle";let{detectState:detect}=await Promise.resolve().then(() => (init_state_detector(),exports_state_detector)),result=detect(content);return mapDetectedState(result.type)}catch{return"terminated"}}async terminate(executor){let{executeTmux:executeTmux3}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),paneId=executor.tmuxPaneId;if(paneId){try{await executeTmux3(`send-keys -t '${paneId}' C-c`),await new Promise((r)=>setTimeout(r,500))}catch{}try{await executeTmux3(`kill-pane -t '${paneId}'`)}catch{}}if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams2(ctx);return buildClaudeCommand(params)}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
550
- SELECT a.custom_name, a.team
551
- FROM executors e
548
+ `).length>0}function toNativeInboxMessage(msg,color="blue"){let words=msg.body.split(/\s+/),summary=words.slice(0,8).join(" ")+(words.length>8?"...":"");return{from:msg.from,text:msg.body,summary,timestamp:msg.createdAt,color,read:!1}}var init_mailbox=__esm(()=>{init_esm7();init_db()});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())`
549
+ SELECT e.ended_at FROM executors e
552
550
  JOIN agents a ON e.agent_id = a.id
553
- WHERE e.id = ${executorId}
551
+ WHERE a.custom_name = ${options.agent}
552
+ AND a.team = ${options.team}
553
+ AND e.ended_at IS NOT NULL
554
+ ORDER BY e.ended_at DESC
554
555
  LIMIT 1
555
- `;if(rows.length===0)return;let{custom_name:agentName,team:teamName}=rows[0];if(!agentName||!teamName)return;let{writeNativeInbox:writeNativeInbox2}=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams));await writeNativeInbox2(teamName,agentName,{from:"system",text:message.text,summary:message.text.slice(0,120),timestamp:new Date().toISOString(),color:"red",read:!1})}}function spawnContextToParams2(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,sessionId:ctx.sessionId,systemPromptFile:ctx.systemPromptFile,systemPrompt:ctx.systemPrompt,promptMode:ctx.promptMode,initialPrompt:ctx.initialPrompt,name:ctx.name,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}function resumeContextToParams2(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,resume:ctx.claudeSessionId,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}async function findSessionLogPath(sessionId,projectPath){let{access:access2,readdir:readdir4}=await import("fs/promises"),{join:join20}=await import("path"),claudeDir=join20(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return null}let jsonlName=`${sessionId}.jsonl`;if(projectPath){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),hash=projectPathToHash2(projectPath),candidate=join20(claudeDir,hash,jsonlName);try{return await access2(candidate),candidate}catch{}}try{let dirs=await readdir4(claudeDir);for(let dir of dirs){let candidate=join20(claudeDir,dir,jsonlName);try{return await access2(candidate),candidate}catch{}}}catch{}return null}function mapDetectedState(detectedType){switch(detectedType){case"idle":return"idle";case"working":case"tool_use":return"working";case"permission":return"permission";case"question":return"question";case"error":return"error";case"complete":return"done";case"unknown":return"running"}}var init_claude_code=__esm(()=>{init_provider_adapters()});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty)});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext});import{exec as exec2}from"child_process";import{readFile as readFile4}from"fs/promises";import{join as join20}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let teamConfig=await getTeam(team);if(teamConfig?.nativeTeamParentSessionId)return teamConfig.nativeTeamParentSessionId;return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role),currentExec=await getCurrentExecutor(agentIdentity.id);if(currentExec&&currentExec.state!=="terminated"&&currentExec.state!=="done"){let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentity.id)}let pid=null;try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);if(parsed>0)pid=parsed}catch{}let executorTransport=template.provider==="codex"?"api":"tmux",executor=await createExecutor(agentIdentity.id,template.provider,executorTransport,{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),session=(await getTeam(team))?.tmuxSessionName??await resolveRepoSession(repoPath)??await getCurrentSessionName()??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,claudeSessionId:effectiveSessionId,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude){await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget="team-lead";try{let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig2=await getTeam3(team);if(teamConfig2?.leader)leaderInboxTarget=teamConfig2.leader}catch{}await writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await findAnyGroupByAssignee(workerId,repoPath)??await findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join20(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile4(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
556
- ${groupSection}`:"","",gitLog?`Last git log:
557
- ${gitLog}`:"","",gitStatus?`Uncommitted changes:
558
- ${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
559
- `);await send(repoPath,"genie",workerId,resumePrompt)}catch{}}var execAsync;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_claude_native_teams();init_executor_registry();init_mailbox();init_provider_adapters();init_registry();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2)});var init_orchestrator=__esm(()=>{init_patterns();init_state_detector()});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000;var init_spawn_command=__esm(()=>{init_orchestrator();init_tmux()});var exports_inject={};__export(exports_inject,{isTeamHooked:()=>isTeamHooked,injectTeamHooks:()=>injectTeamHooks});import{existsSync as existsSync18}from"fs";import{mkdir as mkdir4,readFile as readFile5,writeFile as writeFile2}from"fs/promises";import{homedir as homedir15}from"os";import{join as join21}from"path";import{fileURLToPath}from"url";function escapeShellArg2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function buildDispatchCommand(){let entrypoint=fileURLToPath(new URL("../genie.ts",import.meta.url));if(!existsSync18(entrypoint))return"genie hook dispatch";let bun=process.execPath||"bun";return`${escapeShellArg2(bun)} run ${escapeShellArg2(entrypoint)} hook dispatch`}function isGenieDispatchCommand(command){return typeof command==="string"&&/(?:^|\s)hook\s+dispatch(?:\s|$)/.test(command)}function claudeConfigDir2(){return process.env.CLAUDE_CONFIG_DIR??join21(homedir15(),".claude")}function teamSettingsPath(teamName){let sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase();return join21(claudeConfigDir2(),"teams",sanitized,"settings.json")}function buildHooksConfig(){let hooks={},dispatchCommand=buildDispatchCommand();for(let event of DISPATCHED_EVENTS)hooks[event]=[{matcher:"*",hooks:[{type:"command",command:dispatchCommand,timeout:DISPATCH_TIMEOUT}]}];return hooks}async function injectIntoFile(settingsPath){let settings={};if(existsSync18(settingsPath))try{let content=await readFile5(settingsPath,"utf-8");settings=JSON.parse(content)}catch{}let hooksConfig=buildHooksConfig(),existingHooks=settings.hooks;if(existingHooks){if(DISPATCHED_EVENTS.every((event)=>{let existing=existingHooks[event],desiredCommand=hooksConfig[event][0].hooks[0].command;return existing?.some((m)=>m.hooks?.some((h)=>h.command===desiredCommand))}))return!1}let mergedHooks=existingHooks?{...existingHooks}:{};for(let event of DISPATCHED_EVENTS){let genieEntry=hooksConfig[event][0],existingEntries=(mergedHooks[event]??[]).map((matcher)=>({...matcher,hooks:matcher.hooks?.map((hook)=>isGenieDispatchCommand(hook.command)?{...hook,command:genieEntry.hooks[0].command,timeout:DISPATCH_TIMEOUT}:hook)}));if(!existingEntries.some((m)=>m.hooks?.some((h)=>isGenieDispatchCommand(h.command))))mergedHooks[event]=[...existingEntries,genieEntry];else mergedHooks[event]=existingEntries}settings.hooks=mergedHooks;let dir=join21(settingsPath,"..");return await mkdir4(dir,{recursive:!0}),await writeFile2(settingsPath,JSON.stringify(settings,null,2)),!0}async function injectTeamHooks(teamName){let path2=teamSettingsPath(teamName);return injectIntoFile(path2)}async function isTeamHooked(teamName){let path2=teamSettingsPath(teamName);if(!existsSync18(path2))return!1;try{let content=await readFile5(path2,"utf-8"),hooks=JSON.parse(content).hooks;if(!hooks)return!1;return DISPATCHED_EVENTS.every((event)=>{return hooks[event]?.some((m)=>m.hooks?.some((h)=>isGenieDispatchCommand(h.command)))})}catch{return!1}}var DISPATCH_TIMEOUT=15;var init_inject=__esm(()=>{init_types3()});var exports_idle_timeout={};__export(exports_idle_timeout,{suspendWorker:()=>suspendWorker,getIdleTimeoutMs:()=>getIdleTimeoutMs,checkIdleWorkers:()=>checkIdleWorkers,WATCHDOG_POLL_INTERVAL_MS:()=>WATCHDOG_POLL_INTERVAL_MS});function getIdleTimeoutMs(){let env=process.env.GENIE_IDLE_TIMEOUT_MS;if(env!==void 0){if(env==="")return DEFAULT_IDLE_TIMEOUT_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return DEFAULT_IDLE_TIMEOUT_MS}async function suspendWorker(executorId,deps=defaultDeps){let executor=(await deps.listExecutors()).find((e)=>e.id===executorId);if(!executor)return!1;if(executor.state==="terminated")return!0;if(executor.tmuxPaneId)try{await deps.executeTmux(`kill-pane -t '${executor.tmuxPaneId}'`)}catch{}return await deps.terminateExecutor(executorId),!0}async function checkIdleWorkers(deps=defaultDeps){let timeoutMs=getIdleTimeoutMs();if(timeoutMs===0)return[];let executors=await deps.listExecutors(),suspended=[];for(let e of executors){if(e.state!=="idle")continue;if(Date.now()-new Date(e.updatedAt).getTime()<timeoutMs)continue;if(e.tmuxPaneId){if(!await deps.isPaneAlive(e.tmuxPaneId)){await deps.terminateExecutor(e.id),suspended.push(e.id);continue}}if(await suspendWorker(e.id,deps))suspended.push(e.id)}return suspended}var defaultDeps,DEFAULT_IDLE_TIMEOUT_MS=1800000,WATCHDOG_POLL_INTERVAL_MS=60000;var init_idle_timeout=__esm(()=>{init_executor_registry();init_tmux();defaultDeps={listExecutors:()=>listExecutors(),terminateExecutor,updateExecutorState,executeTmux:executeTmux2,isPaneAlive}});var exports_agents={};__export(exports_agents,{handleWorkerStop:()=>handleWorkerStop,handleWorkerSpawn:()=>handleWorkerSpawn,handleWorkerResume:()=>handleWorkerResume,handleWorkerKill:()=>handleWorkerKill,handleLsCommand:()=>handleLsCommand,buildResumeContext:()=>buildResumeContext,buildInitialSplitWindowCommand:()=>buildInitialSplitWindowCommand});async function resolveTeamLeaderName(teamNameOrDefault){try{return(await getTeam(teamNameOrDefault))?.leader||"team-lead"}catch{return"team-lead"}}function isRelayAlive(pidFile){let{readFileSync:readFileSync9,existsSync:existsSync19}=__require("fs");if(!existsSync19(pidFile))return!1;try{let pid=Number.parseInt(readFileSync9(pidFile,"utf-8").trim());if(pid>0)return process.kill(pid,0),!0}catch{}return!1}async function ensureOtelRelay(team){let{writeFileSync:writeFileSync6,mkdirSync:mkdirSync7}=__require("fs"),{join:join22}=__require("path"),{homedir:homedir16}=__require("os"),relayDir=join22(homedir16(),".genie","relay");mkdirSync7(relayDir,{recursive:!0});let pidFile=join22(relayDir,"otel-relay.pid"),scriptFile=join22(relayDir,"otel-relay.mjs");if(isRelayAlive(pidFile))return!0;let inboxDir=join22(process.env.CLAUDE_CONFIG_DIR??join22(homedir16(),".claude"),"teams",team,"inboxes"),leaderInboxName=sanitizeTeamName(await resolveTeamLeaderName(team)),escapedRelayDir=relayDir.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),escapedInboxDir=inboxDir.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),escapedPidFile=pidFile.replace(/\\/g,"\\\\").replace(/'/g,"\\'");try{writeFileSync6(scriptFile,`import { createServer } from 'http';
560
- import { execSync } from 'child_process';
561
- import { readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs';
562
- import { createHash } from 'crypto';
563
- import { join } from 'path';
564
-
565
- const RELAY_DIR = '${escapedRelayDir}';
566
- const INBOX_DIR = '${escapedInboxDir}';
567
- const INBOX = join(INBOX_DIR, '${leaderInboxName}.json');
568
- const PID_FILE = '${escapedPidFile}';
569
- const PORT = ${OTEL_RELAY_PORT};
570
- const SILENCE_MS = 5000;
571
- const BOOTSTRAP_GRACE_MS = 20000; // Hard skip during first 20s after worker appears
572
- // After grace period, wait for first idle state (= bootstrap done) before relaying.
573
- // This avoids noise from codex reading context files and asking for permission.
574
-
575
- let silenceTimer = null;
576
- const lastHashes = new Map(); // workerId \u2192 content hash
577
- const workerFirstSeen = new Map(); // workerId \u2192 timestamp (ms)
578
- const bootstrapDone = new Set(); // Workers whose bootstrap is complete (stable idle seen)
579
- const bootstrapIdleCount = new Map(); // workerId \u2192 consecutive idle poll count during bootstrap
580
- const stoppedWorkers = new Set(); // Workers that finished \u2014 no more relays
581
-
582
- // Detect codex state from pane content
583
- function detectState(output) {
584
- const lines = output.split('\\n').filter(l => l.trim());
585
- const tail = lines.slice(-8).join('\\n');
586
-
587
- // Permission prompt \u2014 codex is asking for approval
588
- if (/Press enter to confirm or esc to cancel/.test(tail)) return 'permission';
589
- if (/Would you like to run/.test(tail)) return 'permission';
590
-
591
- // Working indicators \u2014 check BEFORE idle because the \u203A prompt placeholder
592
- // is visible even while codex is actively processing
593
- if (/[\u25E6\u25D0\u25D1\u25D2\u25D3\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F]/.test(tail)) return 'working'; // Spinner chars
594
- if (/esc to interrupt/.test(tail)) return 'working'; // Active processing hint
595
-
596
- // Idle \u2014 codex prompt waiting for input (\u203A at start of a line near bottom)
597
- // The status bar (gpt-5.3-codex...) appears below the prompt
598
- if (/^\\s*[>\u203A]\\s/m.test(tail)) return 'idle';
599
-
600
- // Still working
601
- return 'working';
602
- }
603
-
604
- // Extract meaningful summary from codex pane output
605
- function extractSummary(output, state) {
606
- const lines = output.split('\\n');
607
-
608
- if (state === 'permission') {
609
- // Find the command being requested (lines with $ or \u2718 near the bottom)
610
- const tail = lines.slice(-15);
611
- // Look for the command line (starts with $ or contains the command after "Run")
612
- const cmdLine = tail.reverse().find(l =>
613
- /^\\s*\\$\\s/.test(l) || /^\\s*[\u2022\u2718].*(?:Run|run|patch|write|exec)/.test(l)
614
- );
615
- if (cmdLine) {
616
- const cleaned = cmdLine.replace(/^\\s*\\$\\s*/, '').replace(/^\\s*[\u2022\u2718]\\s*/, '').trim().slice(0, 80);
617
- return '[needs approval] ' + cleaned;
618
- }
619
- return '[needs approval]';
620
- }
621
-
622
- // Idle state \u2014 find the last codex response (\u2022 prefixed lines)
623
- // Work backwards from the idle prompt to find the response block
624
- const responseLines = [];
625
- let foundPrompt = false;
626
- for (let i = lines.length - 1; i >= 0; i--) {
627
- const line = lines[i].trim();
628
- // Skip empty lines, status bar, and idle prompt
629
- if (!line) continue;
630
- if (/^[>\u203A]\\s/.test(line)) {
631
- if (foundPrompt) break; // Hit the user's input prompt \u2014 stop
632
- foundPrompt = true;
633
- continue;
634
- }
635
- if (/gpt-\\d|codex|left\\s*\xB7|^Tip:/.test(line)) continue;
636
- // Collect response lines (\u2022 prefixed or continuation)
637
- if (foundPrompt || /^[\u2022\u2714\u2718\u2500]/.test(line)) {
638
- foundPrompt = true;
639
- responseLines.unshift(line);
640
- if (responseLines.length >= 3) break; // Enough for summary
641
- }
642
- }
643
-
644
- if (responseLines.length > 0) {
645
- // Clean up the \u2022 prefix for summary
646
- const summary = responseLines
647
- .map(l => l.replace(/^[\u2022\u2714\u2718]\\s*/, '').trim())
648
- .filter(Boolean)
649
- .join(' ')
650
- .slice(0, 120);
651
- return summary || '(idle)';
652
- }
653
-
654
- return '(idle)';
655
- }
656
-
657
- function relayAll() {
658
- let paneFiles;
659
- try { paneFiles = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane')); }
660
- catch { return; }
661
-
662
- const now = Date.now();
663
-
664
- for (const file of paneFiles) {
665
- const workerId = file.replace(/-pane$/, '');
666
- if (stoppedWorkers.has(workerId)) continue;
667
-
668
- // Bootstrap grace period \u2014 skip relaying during first 25s
669
- if (!workerFirstSeen.has(workerId)) {
670
- // Use file mtime as registration time
671
- try {
672
- const stat = statSync(join(RELAY_DIR, file));
673
- workerFirstSeen.set(workerId, stat.mtimeMs);
674
- } catch {
675
- workerFirstSeen.set(workerId, now);
676
- }
677
- }
678
- const age = now - workerFirstSeen.get(workerId);
679
- if (age < BOOTSTRAP_GRACE_MS) continue;
680
-
681
- let paneId;
682
- try { paneId = readFileSync(join(RELAY_DIR, file), 'utf-8').trim(); }
683
- catch { continue; }
684
- if (!paneId || !/^%\\d+$/.test(paneId)) continue;
685
-
686
- let meta = { agent: workerId, color: 'blue' };
687
- try {
688
- const raw = readFileSync(join(RELAY_DIR, workerId + '-meta'), 'utf-8');
689
- meta = JSON.parse(raw);
690
- } catch {}
691
-
692
- let output;
693
- try {
694
- output = execSync(\`tmux -L genie capture-pane -p -t '\${paneId}' -S -80\`, { encoding: 'utf-8' }).trim();
695
- } catch {
696
- // Pane gone \u2014 final relay if we had previous content
697
- const lastContent = lastHashes.get(workerId + ':content');
698
- if (lastContent) {
699
- const summary = extractSummary(lastContent, 'idle');
700
- writeInbox(meta, lastContent, '[finished] ' + summary);
701
- }
702
- stoppedWorkers.add(workerId);
703
- continue;
704
- }
705
- if (!output) continue;
706
-
707
- // Detect state \u2014 only relay on idle or permission
708
- const state = detectState(output);
709
- if (state === 'working') continue;
710
-
711
- // Bootstrap detection: require 2 consecutive idle polls before marking done.
712
- // Permission prompts are ALWAYS relayed (they block progress).
713
- // Brief idle states between actions are skipped (codex shows \u203A between tasks).
714
- if (!bootstrapDone.has(workerId)) {
715
- if (state === 'idle') {
716
- const count = (bootstrapIdleCount.get(workerId) || 0) + 1;
717
- bootstrapIdleCount.set(workerId, count);
718
- if (count >= 2) {
719
- bootstrapDone.add(workerId);
720
- // Fall through to relay this stable idle (bootstrap complete)
721
- } else {
722
- continue; // First idle poll \u2014 might be brief between actions
723
- }
724
- } else if (state === 'permission') {
725
- bootstrapIdleCount.set(workerId, 0); // Reset \u2014 codex is still working
726
- // Permission during bootstrap \u2014 falls through to relay
727
- } else {
728
- bootstrapIdleCount.set(workerId, 0); // Reset on working state
729
- continue;
730
- }
731
- }
732
-
733
- // Skip if content unchanged
734
- const hash = createHash('md5').update(output).digest('hex');
735
- if (lastHashes.get(workerId) === hash) continue;
736
- lastHashes.set(workerId, hash);
737
- lastHashes.set(workerId + ':content', output);
738
-
739
- const summary = extractSummary(output, state);
740
- writeInbox(meta, output, summary);
741
- }
742
- }
743
-
744
- function writeInbox(meta, text, summary) {
745
- mkdirSync(INBOX_DIR, { recursive: true });
746
- let messages = [];
747
- try { messages = JSON.parse(readFileSync(INBOX, 'utf-8')); } catch {}
748
- // Trim old read messages to prevent inbox bloat \u2014 keep only last 5 read + all unread
749
- const unread = messages.filter(m => !m.read);
750
- const read = messages.filter(m => m.read);
751
- messages = [...read.slice(-5), ...unread];
752
- messages.push({
753
- from: meta.agent,
754
- text,
755
- summary,
756
- timestamp: new Date().toISOString(),
757
- color: meta.color,
758
- read: false,
759
- });
760
- writeFileSync(INBOX, JSON.stringify(messages, null, 2));
761
- }
762
-
763
- // Reset in_progress groups assigned to a dead worker and notify team-lead (PG-backed)
764
- async function handleDeadWorkerLiveness(workerId, meta) {
765
- if (!meta || !meta.repoPath) return;
766
- try {
767
- const wishState = await import('../lib/wish-state.js');
768
- // Try both workerId and agent name
769
- const match =
770
- (await wishState.findAnyGroupByAssignee(workerId, meta.repoPath)) ??
771
- (meta.agent ? await wishState.findAnyGroupByAssignee(meta.agent, meta.repoPath) : null);
772
- if (!match) return;
773
-
774
- await wishState.resetGroup(match.slug, match.groupName, meta.repoPath);
775
- const agentLabel = meta.agent || workerId;
776
- writeInbox(
777
- { agent: 'genie-relay', color: 'red' },
778
- 'Agent ' + agentLabel + ' crashed while working on group ' + match.groupName + ' of wish ' + match.slug + '. Group has been reset to ready for retry.',
779
- '[crash] ' + agentLabel + ' crashed on ' + match.slug + '#' + match.groupName + '. Reset to ready.'
780
- );
781
- } catch {}
782
- }
783
-
784
- // Clean up dead panes every 30s
785
- setInterval(async () => {
786
- let paneFiles;
787
- try { paneFiles = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane')); }
788
- catch { return; }
789
- for (const file of paneFiles) {
790
- try {
791
- const paneId = readFileSync(join(RELAY_DIR, file), 'utf-8').trim();
792
- if (!/^%\\d+$/.test(paneId)) throw new Error('invalid pane id');
793
- execSync(\`tmux -L genie display -t '\${paneId}' -p '#{pane_id}'\`, { stdio: 'ignore' });
794
- } catch {
795
- const workerId = file.replace(/-pane$/, '');
796
- // Read meta before cleanup (for liveness reset)
797
- let meta = null;
798
- try { meta = JSON.parse(readFileSync(join(RELAY_DIR, workerId + '-meta'), 'utf-8')); } catch {}
799
- for (const suffix of ['-pane', '-meta']) {
800
- try { unlinkSync(join(RELAY_DIR, workerId + suffix)); } catch {}
801
- }
802
- lastHashes.delete(workerId);
803
- workerFirstSeen.delete(workerId);
804
- bootstrapDone.delete(workerId);
805
- stoppedWorkers.add(workerId);
806
- // Liveness check: reset in_progress groups assigned to this dead worker
807
- await handleDeadWorkerLiveness(workerId, meta);
808
- }
809
- }
810
- try {
811
- const remaining = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane'));
812
- if (remaining.length === 0) process.exit(0);
813
- } catch {}
814
- }, 30000);
815
-
816
- const server = createServer((req, res) => {
817
- const chunks = [];
818
- req.on('data', (chunk) => chunks.push(chunk));
819
- req.on('end', () => {
820
- if (silenceTimer) clearTimeout(silenceTimer);
821
- silenceTimer = setTimeout(() => relayAll(), SILENCE_MS);
822
- res.writeHead(200);
823
- res.end();
824
- });
825
- });
826
-
827
- server.listen(PORT, '127.0.0.1', () => {
828
- writeFileSync(PID_FILE, String(process.pid));
829
- });
830
-
831
- process.on('SIGTERM', () => { server.close(); process.exit(0); });
832
- process.on('SIGINT', () => { server.close(); process.exit(0); });
833
- `,{mode:420});let{spawn:spawnChild}=__require("child_process");spawnChild("node",[scriptFile],{detached:!0,stdio:"ignore"}).unref();for(let i2=0;i2<30;i2++)if(await new Promise((r)=>setTimeout(r,100)),isRelayAlive(pidFile))return!0;return!1}catch{return!1}}async function generateWorkerId2(team,role){let base=role?`${team}-${role}`:team;if(!(await list()).some((w)=>w.id===base))return base;let suffix=crypto.randomUUID().slice(0,8);return`${base}-${suffix}`}async function capturePanePid(paneId){if(paneId==="inline")return null;try{let{execSync:execSync4}=__require("child_process"),output=execSync4(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),pid=Number.parseInt(output,10);return pid>0?pid:null}catch{return null}}function resolveExecutorTransport(provider,spawnTransport){if(provider==="codex")return"api";return spawnTransport==="inline"?"process":"tmux"}async function terminateActiveExecutorWithCleanup(agentIdentityId){try{let currentExec=await getCurrentExecutor(agentIdentityId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let provider=getProvider(currentExec.provider);if(provider)try{await provider.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentityId)}catch{}}async function createAndLinkExecutor(agentIdentityId,provider,transport,opts){try{let executor=await createExecutor(agentIdentityId,provider,transport,opts);return await setCurrentExecutor(agentIdentityId,executor.id),executor.id}catch{return null}}async function registerSpawnWorker(ctx,paneId,windowInfo){let nt=ctx.validated.nativeTeam,workerEntry={id:ctx.workerId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role,skill:ctx.validated.skill,team:ctx.validated.team,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,claudeSessionId:ctx.claudeSessionId,nativeTeamEnabled:nt?.enabled??!1,nativeAgentId:`${ctx.agentName}@${ctx.validated.team}`,nativeColor:nt?.color??ctx.spawnColor,parentSessionId:nt?.parentSessionId??ctx.parentSessionId,window:windowInfo?.windowName,windowName:windowInfo?.windowName,windowId:windowInfo?.windowId,autoResume:ctx.autoResume===!1?!1:void 0,resumeAttempts:0};await register(workerEntry);let role=ctx.validated.role??ctx.agentName;if(role!=="council")try{await hireAgent(ctx.validated.team,role)}catch{}return workerEntry}async function notifySpawnJoin(ctx,paneId){let nt=ctx.validated.nativeTeam;if(!nt?.enabled)return;await registerNativeMember(ctx.validated.team,{agentName:ctx.agentName,agentType:nt.agentType??ctx.validated.role??"general-purpose",color:nt.color??ctx.spawnColor??"blue",tmuxPaneId:paneId,cwd:ctx.cwd,planModeRequired:nt.planModeRequired});let leaderName=await resolveTeamLeaderName(ctx.validated.team);await writeNativeInbox(ctx.validated.team,leaderName,{from:ctx.agentName,text:`Worker ${ctx.agentName} (${ctx.validated.provider}) joined team ${ctx.validated.team}. cwd: ${ctx.cwd}. Ready for tasks.`,summary:`${ctx.agentName} (${ctx.validated.provider}) joined`,timestamp:new Date().toISOString(),color:nt.color??ctx.spawnColor??"blue",read:!1})}function registerOtelRelayPane(workerId,paneId,agentName,spawnColor,repoPath){let{writeFileSync:wfs}=__require("fs"),{join:pjoin}=__require("path"),{homedir:hdir}=__require("os"),rd=pjoin(hdir(),".genie","relay");wfs(pjoin(rd,`${workerId}-pane`),paneId),wfs(pjoin(rd,`${workerId}-meta`),JSON.stringify({agent:agentName,color:spawnColor,repoPath}))}function printSpawnInfo(ctx,paneId,workerEntry){let nt=ctx.validated.nativeTeam;if(console.log(`Agent "${ctx.workerId}" spawned.`),console.log(` Provider: ${ctx.launch.provider}`),console.log(` Command: ${ctx.fullCommand}`),console.log(` Team: ${ctx.validated.team}`),console.log(` Pane: ${paneId}`),ctx.validated.role)console.log(` Role: ${ctx.validated.role}`);if(ctx.validated.skill)console.log(` Skill: ${ctx.validated.skill}`);if(workerEntry.claudeSessionId)console.log(` Session: ${workerEntry.claudeSessionId}`);if(console.log(` Layout: ${ctx.layoutMode}`),nt?.enabled)console.log(" Native: enabled"),console.log(` AgentID: ${workerEntry.nativeAgentId}`),console.log(` Color: ${nt.color}`);if(ctx.otelRelayActive)console.log(` OTel: relay on port ${OTEL_RELAY_PORT}`)}function shellQuote2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function writeTmuxLaunchScript(workerId,fullCommand){let{chmodSync:chmodSync2,mkdirSync:mkdirSync7,writeFileSync:writeFileSync6}=__require("fs"),{join:join22}=__require("path"),{homedir:homedir16}=__require("os"),dir=join22(homedir16(),".genie","spawn-scripts");mkdirSync7(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join22(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync6(scriptPath,`#!/bin/sh
834
- exec ${fullCommand}
835
- `,{mode:448}),chmodSync2(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync4}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync4(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync4(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync4}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync4(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync4(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"";if(useLaunchScript){let splitCmd2=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync4(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync4(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync4}=__require("child_process"),session=await getCurrentSessionName()??ctx.validated.team,layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{execSync4(genieTmuxCmd(buildLayoutCommand(layoutTarget,ctx.layoutMode)),{stdio:"ignore"})}catch{}}async function createTmuxExecutor(ctx,paneId,pid,teamWindow){if(!ctx.agentIdentityId||!ctx.executorId)return;await createAndLinkExecutor(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport(ctx.validated.provider,"tmux"),{id:ctx.executorId,pid,tmuxSession:ctx.validated.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd,paneColor:ctx.spawnColor})}async function finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry){if(ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(await saveTemplate({id:ctx.validated.role??ctx.workerId,provider:ctx.validated.provider,team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,extraArgs:ctx.extraArgs,nativeTeamEnabled:workerEntry.nativeTeamEnabled,lastSpawnedAt:new Date().toISOString()}),ctx.otelRelayActive&&paneId!=="%0")registerOtelRelayPane(ctx.workerId,paneId,ctx.agentName,ctx.spawnColor,ctx.cwd);if(teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`);printSpawnInfo(ctx,paneId,workerEntry)}async function awaitAgentReadiness(paneId){if(paneId==="inline")return;let result=await waitForAgentReady(paneId);if(result.ready)console.log(` \u2713 Agent ready (${(result.elapsedMs/1000).toFixed(1)}s)`);else console.log(` \u26A0 Agent readiness timeout (${Math.round(result.elapsedMs/1000)}s) \u2014 proceeding anyway`)}async function launchTmuxSpawn(ctx){let teamWindow=ctx.spawnIntoCurrentWindow?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){return console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}let pid=await capturePanePid(paneId);if(await createTmuxExecutor(ctx,paneId,pid,teamWindow),await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);return await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry),await awaitAgentReadiness(paneId),paneId}async function launchInlineSpawn(ctx){let nt=ctx.validated.nativeTeam,paneId="inline";if(ctx.agentIdentityId&&ctx.executorId)await createAndLinkExecutor(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport(ctx.validated.provider,"inline"),{id:ctx.executorId,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd});let workerEntry=await registerSpawnWorker(ctx,"inline");if(await notifySpawnJoin(ctx,"inline"),console.log(`Agent "${ctx.workerId}" starting inline...`),console.log(` Provider: ${ctx.launch.provider} | Team: ${ctx.validated.team} | Role: ${ctx.validated.role??"-"}`),nt?.enabled)console.log(` Native: enabled | AgentID: ${workerEntry.nativeAgentId}`);console.log("");let{spawnSync}=__require("child_process"),envVars={...process.env,...ctx.launch.env??{}},result=spawnSync("sh",["-c",ctx.launch.command],{env:envVars,stdio:"inherit"});if(ctx.agentIdentityId&&ctx.executorId)await updateExecutorState(ctx.executorId,"done").catch(()=>{});if(await unregister(ctx.workerId),nt?.enabled&&ctx.agentName)await clearNativeInbox(ctx.validated.team,ctx.agentName).catch(()=>{}),await unregisterNativeMember(ctx.validated.team,ctx.agentName).catch(()=>{});return console.log(`
836
- Agent "${ctx.workerId}" session ended.`),process.exit(result.status??0)}function prependEnvVars(command,env){if(!env||Object.keys(env).length===0)return command;return`env ${Object.entries(env).map(([k,v])=>`${k}=${v}`).join(" ")} ${command}`}async function rejectDuplicateRole(team,role){let existing=await list();for(let w of existing)if(w.role===role&&w.team===team){if(await isPaneAlive(w.paneId))console.error(`Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})
837
- Use a different --role name for a second worker, e.g.: --role ${role}-2`),process.exit(1);await unregister(w.id)}}async function resolveNativeTeam(team,_repoPath,options){let parentSessionId=(await getTeam(team))?.nativeTeamParentSessionId;if(!parentSessionId)parentSessionId=await discoverClaudeParentSessionId()??`genie-${team}`;await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=options.color??await assignColor(team),nativeTeam;if(options.provider==="claude")nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:options.role??"general-purpose",planModeRequired:options.planMode,permissionMode:options.permissionMode,agentName:options.role};return{parentSessionId,spawnColor,nativeTeam}}async function resolveAgentForSpawn(name,options){let resolved=await resolve3(name);if(!resolved)console.error(`Error: Agent "${name}" not found in directory or built-ins.`),console.error(` Register with: genie dir add ${name} --dir <path>`),console.error(" Or use a built-in: engineer, reviewer, qa, fix, ..."),process.exit(1);let entry=resolved.entry,identityPath=null;if(resolved.builtin)identityPath=resolveBuiltinAgentPath(name);else if(entry.dir)identityPath=loadIdentity(entry);return{entry,repoPath:options.cwd??(entry.repo||void 0)??(entry.dir||void 0)??process.cwd(),identityPath,model:options.model??entry.model}}async function buildSpawnParams2(name,team,options,agent){let params={provider:options.provider,team,role:name,skill:options.skill,extraArgs:options.extraArgs,model:agent.model,systemPromptFile:agent.identityPath??void 0,promptMode:agent.entry.promptMode,initialPrompt:options.initialPrompt},{parentSessionId,spawnColor,nativeTeam}=await resolveNativeTeam(team,agent.repoPath,{...options,role:name});if(nativeTeam)params.nativeTeam=nativeTeam;try{let{injectTeamHooks:injectTeamHooks2}=await Promise.resolve().then(() => (init_inject(),exports_inject));if(await injectTeamHooks2(team))console.log(` Hooks: injected genie hook dispatch into team "${team}"`)}catch(err){console.warn(`Warning: could not inject hooks for team "${team}": ${err instanceof Error?err.message:err}`)}if(params.provider==="claude")params.sessionId=crypto.randomUUID();if(params.provider==="claude"){if(await startOtelReceiver())params.otelPort=getOtelPort(),params.otelLogPrompts=!0}return{params,parentSessionId,spawnColor}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),teamWasExplicit=Boolean(options.team),team=options.team||await discoverTeamName();if(!team)return console.error("Error: --team is required (or set GENIE_TEAM, or run inside a genie session)"),process.exit(1);await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam(team);if(teamConfig?.worktreePath)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams2(effectiveRole,team,options,agent);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=await generateWorkerId2(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX||options.session),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID(),otelRelayActive=!1;if(!nt?.enabled&&validated.provider==="codex"&&insideTmux)ensureCodexOtelConfig(),otelRelayActive=await ensureOtelRelay(validated.team);let fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&insideTmux&&!options.session,sessionOverride:options.session,autoResume:options.autoResume,agentIdentityId:agentIdentity.id,executorId};if(recordAuditEvent("worker",workerId,"spawn",getActor(),{name,team:validated.team,provider:validated.provider}).catch(()=>{}),insideTmux)return await launchTmuxSpawn(ctx);return await launchInlineSpawn(ctx)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync4}=__require("child_process"),currentPane=execSync4(genieTmuxCmd("display-message -p '#{pane_id}'"),{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync4(genieTmuxCmd(`kill-pane -t ${w.paneId}`),{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join22}=__require("path"),{homedir:homedir16}=__require("os"),{unlinkSync:unlinkSync5}=__require("fs"),relayDir=join22(homedir16(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync5(join22(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie agent list` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`),recordAuditEvent("worker",w.id,"kill",getActor(),{name}).catch(()=>{})}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.id)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`),recordAuditEvent("worker",w.id,"stop",getActor(),{name}).catch(()=>{})}else console.error(`Failed to stop agent "${w.id}".`),process.exit(1)}async function isResumeEligible(w){if(!w.claudeSessionId)return!1;if(w.state==="done")return!1;let paneAlive=await isPaneAlive(w.paneId);if((w.state==="suspended"||w.state==="error")&&!paneAlive)return!0;if(!paneAlive&&(w.state==="working"||w.state==="idle"||w.state==="spawning"))return!0;return!1}async function resumeAllAgents(){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){if(options.all)return resumeAllAgents();if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w)}function buildResumeParams(agent,template){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??"genie";return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:agent.claudeSessionId,name:`${team}-${agentName}`}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
838
- `)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let params=buildResumeParams(agent,template),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentIdentity=await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role);await terminateActiveExecutorWithCleanup(agentIdentity.id);let pid=await capturePanePid(paneId);await createAndLinkExecutor(agentIdentity.id,params.provider,resolveExecutorTransport(params.provider,"tmux"),{pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:agent.claudeSessionId??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}async function resumeAgent(agent){let template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id));await update(agent.id,{resumeAttempts:0});let params=await buildFullResumeParams(agent,template),validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:agent.claudeSessionId,team:agent.team}).catch(()=>{}),console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${agent.claudeSessionId}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.role||w.id;if(await isPaneAlive(w.paneId))statusMap.set(name,{state:w.state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),entries=[];for(let entry of dirEntries){let running=statusMap.get(entry.name);entries.push({name:entry.name,dir:entry.dir||"-",status:running?running.state:"offline",team:running?.team||"-",model:entry.model||"-",resumeAttempts:running?.resumeAttempts,maxResumeAttempts:running?.maxResumeAttempts,autoResume:running?.autoResume}),statusMap.delete(entry.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_executor_registry();init_otel_receiver();init_protocol_router_spawn();init_provider_adapters();init_registry();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux()});var exports_workspace={};__export(exports_workspace,{scanAgents:()=>scanAgents2,getWorkspaceConfig:()=>getWorkspaceConfig,findWorkspace:()=>findWorkspace});import{existsSync as existsSync19,readFileSync as readFileSync9,readdirSync as readdirSync5}from"fs";import{dirname as dirname5,join as join22,resolve as resolve4,sep}from"path";function findWorkspace(cwd){let startDir=resolve4(cwd??process.cwd()),current=startDir;while(!0){let candidate=join22(current,WORKSPACE_MARKER);if(existsSync19(candidate)){let agent=detectAgent(startDir,current);return{root:current,agent:agent??void 0}}let parent=dirname5(current);if(parent===current)break;current=parent}return null}function detectAgent(startDir,workspaceRoot){let agentsDir=join22(workspaceRoot,"agents"),relative=startDir.slice(agentsDir.length);if(!startDir.startsWith(agentsDir)||relative.length>0&&relative[0]!==sep)return null;let parts=relative.split(sep).filter(Boolean);if(parts.length===0)return null;let agentName=parts[0],agentsMd=join22(agentsDir,agentName,"AGENTS.md");if(existsSync19(agentsMd))return agentName;return null}function getWorkspaceConfig(root){let configPath2=join22(root,WORKSPACE_MARKER),raw=readFileSync9(configPath2,"utf-8");return JSON.parse(raw)}function scanAgents2(root){let agentsDir=join22(root,"agents");if(!existsSync19(agentsDir))return[];try{return readdirSync5(agentsDir,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&existsSync19(join22(agentsDir,d.name,"AGENTS.md"))).map((d)=>d.name).sort()}catch{return[]}}var WORKSPACE_MARKER=".genie/workspace.json";var init_workspace=()=>{};import{existsSync as existsSync20,readFileSync as readFileSync10,renameSync as renameSync2,writeFileSync as writeFileSync6}from"fs";import{homedir as homedir16}from"os";import{join as join23}from"path";async function regenerateAgentCache(){try{if(!await isAvailable())return;let entries=(await(await getConnection())`
556
+ `;if(rows.length>0&&rows[0].ended_at){let endedAt=rows[0].ended_at;return endedAt instanceof Date?endedAt.toISOString():String(endedAt)}}return new Date(Date.now()-86400000).toISOString()}async function getTaskMessages(team,since){return(await(await getConnection())`
557
+ SELECT m.body, m.sender_type, m.sender_id, m.created_at,
558
+ t.id AS task_id, t.title AS task_title
559
+ FROM messages m
560
+ JOIN conversations c ON m.conversation_id = c.id
561
+ JOIN tasks t ON c.linked_entity_id = t.id
562
+ WHERE c.linked_entity = 'task'
563
+ AND t.team_name = ${team}
564
+ AND m.created_at > ${since}
565
+ ORDER BY m.created_at ASC
566
+ LIMIT 100
567
+ `).map((r)=>({taskId:r.task_id,taskTitle:r.task_title,senderType:r.sender_type,senderId:r.sender_id,body:r.body,createdAt:r.created_at instanceof Date?r.created_at.toISOString():String(r.created_at)}))}async function getTeamRoster(teamName){return(await(await getConnection())`
568
+ SELECT a.custom_name AS agent_id, a.role, e.state AS executor_state, e.started_at AS executor_started_at
569
+ FROM agents a
570
+ LEFT JOIN executors e ON a.current_executor_id = e.id
571
+ WHERE a.team = ${teamName}
572
+ ORDER BY a.custom_name
573
+ `).map((r)=>({agentId:r.agent_id,role:r.role??null,executorState:r.executor_state??null,executorStartedAt:r.executor_started_at?r.executor_started_at instanceof Date?r.executor_started_at.toISOString():String(r.executor_started_at):null}))}async function getPendingRequestMessages(team){return(await(await getConnection())`
574
+ SELECT m.id, m.request_type, m.sender_id, m.body, m.created_at
575
+ FROM messages m
576
+ JOIN conversations c ON m.conversation_id = c.id
577
+ JOIN tasks t ON c.linked_entity_id = t.id
578
+ WHERE c.linked_entity = 'task'
579
+ AND t.team_name = ${team}
580
+ AND m.request_type IS NOT NULL
581
+ AND m.request_status = 'pending'
582
+ ORDER BY m.created_at DESC
583
+ LIMIT 100
584
+ `).map((r)=>({id:r.id,requestType:r.request_type,senderId:r.sender_id,body:r.body,createdAt:r.created_at instanceof Date?r.created_at.toISOString():String(r.created_at)}))}async function generateBrief(options){let teamConfig=await getTeam(options.team);if(!teamConfig)throw Error(`Team not found: ${options.team}`);let since=await resolveSince(options),repoPath=options.repoPath??teamConfig.repo,agentName=options.agent??teamConfig.leader??null,agentIdentifiers=agentName?[agentName]:[],[unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster]=await Promise.all([agentIdentifiers.length>0?getUnread(repoPath,agentIdentifiers):Promise.resolve([]),getTaskMessages(options.team,since),listRuntimeEvents({team:options.team,since,limit:100}),getPendingRequestMessages(options.team),getTeamRoster(options.team)]);return{team:options.team,agent:agentName,since,unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster}}function truncate(text,max){return text.length>max?`${text.slice(0,max)}...`:text}function formatUnreadSection(messages2){if(messages2.length===0)return[];let lines=[`## Unread Messages (${messages2.length})`];for(let msg of messages2)lines.push(`- **${msg.from}**: ${truncate(msg.body,120)}`);return lines.push(""),lines}function formatTaskMessagesSection(messages2){if(messages2.length===0)return[];let lines=[`## Task Updates (${messages2.length})`],byTask=new Map;for(let msg of messages2){let existing=byTask.get(msg.taskId)??[];existing.push(msg),byTask.set(msg.taskId,existing)}for(let[taskId,msgs]of byTask){lines.push(`### ${taskId}: ${msgs[0].taskTitle}`);for(let msg of msgs)lines.push(`- [${msg.senderType}:${msg.senderId}] ${truncate(msg.body,100)}`)}return lines.push(""),lines}function formatRequestsSection(requests){if(requests.length===0)return[];let lines=[`## Pending Requests (${requests.length})`];for(let req of requests)lines.push(`- [${req.requestType}] ${req.senderId}: ${truncate(req.body,80)}`);return lines.push(""),lines}function formatRosterSection(roster){if(roster.length===0)return[];let lines=[`## Team Roster (${roster.length})`];for(let member of roster){let state=member.executorState??"offline",icon=STATE_ICONS[state]??"\u25CC";lines.push(`- ${icon} **${member.agentId}** (${member.role??"unassigned"}): ${state}`)}return lines.push(""),lines}function formatEventsSection(events){if(events.length===0)return[];let lines=[`## Recent Events (${events.length})`],tail=events.slice(-10);for(let evt of tail){let ts3=evt.timestamp.slice(11,16);lines.push(`- ${ts3} [${evt.kind}] ${evt.agent}: ${truncate(evt.text,80)}`)}if(events.length>10)lines.push(` _(${events.length-10} more events)_`);return lines.push(""),lines}function formatBrief(brief){let lines=[`# BRIEF \u2014 ${brief.team}${brief.agent?` ${brief.agent}`:""}`,`Since: ${brief.since}`,"",...formatUnreadSection(brief.unreadMessages),...formatTaskMessagesSection(brief.taskMessages),...formatRequestsSection(brief.pendingRequests),...formatRosterSection(brief.teamRoster),...formatEventsSection(brief.recentEvents)];if(!(brief.unreadMessages.length+brief.taskMessages.length+brief.pendingRequests.length+brief.recentEvents.length>0))lines.push("_No activity since last session._","");return lines.join(`
585
+ `)}var STATE_ICONS;var init_brief=__esm(()=>{init_db();init_mailbox();init_runtime_events();init_team_manager();STATE_ICONS={working:"\u25CF",idle:"\u25CB",error:"\u2718"}});import{existsSync as existsSync18,readFileSync as readFileSync9,renameSync as renameSync2,writeFileSync as writeFileSync6}from"fs";import{homedir as homedir15}from"os";import{join as join19}from"path";async function regenerateAgentCache(){try{if(!await isAvailable())return;let entries=(await(await getConnection())`
839
586
  SELECT name, install_path, manifest, installed_at
840
587
  FROM app_store
841
588
  WHERE item_type = 'agent'
842
589
  ORDER BY name
843
- `).map((r)=>{let manifest=r.manifest??{},entry={name:r.name,dir:r.install_path??"",promptMode:manifest.promptMode??"append",registeredAt:r.installed_at?new Date(r.installed_at).toISOString():new Date().toISOString()};if(manifest.repo)entry.repo=manifest.repo;if(manifest.model)entry.model=manifest.model;if(Array.isArray(manifest.roles)&&manifest.roles.length>0)entry.roles=manifest.roles;return entry});writeFileSync6(join23(GENIE_HOME3,CACHE_FILE),JSON.stringify(entries,null,2))}catch{}}async function migrateAgentDirectory(){let sourcePath=join23(GENIE_HOME3,CACHE_FILE),backupPath=join23(GENIE_HOME3,CACHE_BACKUP);if(existsSync20(backupPath)||!existsSync20(sourcePath))return;try{let raw=readFileSync10(sourcePath,"utf-8"),entries=JSON.parse(raw);if(!Array.isArray(entries)||entries.length===0)return;let sql=await getConnection();for(let entry of entries){let manifest={};if(entry.promptMode)manifest.promptMode=entry.promptMode;if(entry.model)manifest.model=entry.model;if(entry.roles)manifest.roles=entry.roles;if(entry.repo)manifest.repo=entry.repo;await sql`
590
+ `).map((r)=>{let manifest=r.manifest??{},entry={name:r.name,dir:r.install_path??"",promptMode:manifest.promptMode??"append",registeredAt:r.installed_at?new Date(r.installed_at).toISOString():new Date().toISOString()};if(manifest.repo)entry.repo=manifest.repo;if(manifest.model)entry.model=manifest.model;if(Array.isArray(manifest.roles)&&manifest.roles.length>0)entry.roles=manifest.roles;return entry});writeFileSync6(join19(GENIE_HOME3,CACHE_FILE),JSON.stringify(entries,null,2))}catch{}}async function migrateAgentDirectory(){let sourcePath=join19(GENIE_HOME3,CACHE_FILE),backupPath=join19(GENIE_HOME3,CACHE_BACKUP);if(existsSync18(backupPath)||!existsSync18(sourcePath))return;try{let raw=readFileSync9(sourcePath,"utf-8"),entries=JSON.parse(raw);if(!Array.isArray(entries)||entries.length===0)return;let sql=await getConnection();for(let entry of entries){let manifest={};if(entry.promptMode)manifest.promptMode=entry.promptMode;if(entry.model)manifest.model=entry.model;if(entry.roles)manifest.roles=entry.roles;if(entry.repo)manifest.repo=entry.repo;await sql`
844
591
  INSERT INTO app_store (name, item_type, version, install_path, manifest)
845
592
  VALUES (${entry.name}, 'agent', '0.0.0', ${entry.dir??null}, ${sql.json(manifest)})
846
593
  ON CONFLICT (name) DO NOTHING
@@ -865,9 +612,9 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
865
612
  ${item.dependencies??[]}
866
613
  )
867
614
  RETURNING id
868
- `;if(rows.length===0)throw Error(`Failed to insert item "${item.name}" \u2014 no id returned.`);return rows[0].id}async function removeItemFromStore(name){return(await(await getConnection())`DELETE FROM app_store WHERE name = ${name}`).count>0}async function updateItemInStore(name,updates){let sql=await getConnection(),s={};if(updates.name!==void 0)s.name=updates.name;if(updates.itemType!==void 0)s.item_type=updates.itemType;if(updates.version!==void 0)s.version=updates.version;if(updates.description!==void 0)s.description=updates.description;if(updates.authorName!==void 0)s.author_name=updates.authorName;if(updates.authorUrl!==void 0)s.author_url=updates.authorUrl;if(updates.gitUrl!==void 0)s.git_url=updates.gitUrl;if(updates.installPath!==void 0)s.install_path=updates.installPath;if(updates.manifest!==void 0)s.manifest=sql.json(updates.manifest);if(updates.tags!==void 0)s.tags=updates.tags;if(updates.category!==void 0)s.category=updates.category;if(updates.license!==void 0)s.license=updates.license;if(updates.dependencies!==void 0)s.dependencies=updates.dependencies;if(Object.keys(s).length===0)return!1;return s.updated_at=sql`now()`,(await sql`UPDATE app_store SET ${sql(s)} WHERE name = ${name}`).count>0}async function getItemFromStore(name){let rows=await(await getConnection())`SELECT * FROM app_store WHERE name = ${name}`;return rows.length>0?rows[0]:null}async function listItemsFromStore(itemType){let sql=await getConnection();return itemType?await sql`SELECT * FROM app_store WHERE item_type = ${itemType} ORDER BY name`:await sql`SELECT * FROM app_store ORDER BY name`}var GENIE_HOME3,CACHE_FILE="agent-directory.json",CACHE_BACKUP="agent-directory.json.bak";var init_agent_cache=__esm(()=>{init_audit();init_db();GENIE_HOME3=process.env.GENIE_HOME??join23(homedir16(),".genie")});var exports_session_capture={};__export(exports_session_capture,{setLiveWorkPending:()=>setLiveWorkPending,liveWorkPending:()=>liveWorkPending,ingestFileFull:()=>ingestFileFull,ingestFile:()=>ingestFile,discoverAllJsonlFiles:()=>discoverAllJsonlFiles,buildWorkerMap:()=>buildWorkerMap});import{open as open2,readdir as readdir4,stat as stat3}from"fs/promises";import{homedir as homedir17}from"os";import{basename as basename3,join as join24}from"path";function setLiveWorkPending(v){liveWorkPending=v}function extractSubTool(toolName,input){let obj=input;switch(toolName){case"Bash":return(obj?.command??"").split(`
615
+ `;if(rows.length===0)throw Error(`Failed to insert item "${item.name}" \u2014 no id returned.`);return rows[0].id}async function removeItemFromStore(name){return(await(await getConnection())`DELETE FROM app_store WHERE name = ${name}`).count>0}async function updateItemInStore(name,updates){let sql=await getConnection(),s={};if(updates.name!==void 0)s.name=updates.name;if(updates.itemType!==void 0)s.item_type=updates.itemType;if(updates.version!==void 0)s.version=updates.version;if(updates.description!==void 0)s.description=updates.description;if(updates.authorName!==void 0)s.author_name=updates.authorName;if(updates.authorUrl!==void 0)s.author_url=updates.authorUrl;if(updates.gitUrl!==void 0)s.git_url=updates.gitUrl;if(updates.installPath!==void 0)s.install_path=updates.installPath;if(updates.manifest!==void 0)s.manifest=sql.json(updates.manifest);if(updates.tags!==void 0)s.tags=updates.tags;if(updates.category!==void 0)s.category=updates.category;if(updates.license!==void 0)s.license=updates.license;if(updates.dependencies!==void 0)s.dependencies=updates.dependencies;if(Object.keys(s).length===0)return!1;return s.updated_at=sql`now()`,(await sql`UPDATE app_store SET ${sql(s)} WHERE name = ${name}`).count>0}async function getItemFromStore(name){let rows=await(await getConnection())`SELECT * FROM app_store WHERE name = ${name}`;return rows.length>0?rows[0]:null}async function listItemsFromStore(itemType){let sql=await getConnection();return itemType?await sql`SELECT * FROM app_store WHERE item_type = ${itemType} ORDER BY name`:await sql`SELECT * FROM app_store ORDER BY name`}var GENIE_HOME3,CACHE_FILE="agent-directory.json",CACHE_BACKUP="agent-directory.json.bak";var init_agent_cache=__esm(()=>{init_audit();init_db();GENIE_HOME3=process.env.GENIE_HOME??join19(homedir15(),".genie")});var exports_session_capture={};__export(exports_session_capture,{setLiveWorkPending:()=>setLiveWorkPending,liveWorkPending:()=>liveWorkPending,ingestFileFull:()=>ingestFileFull,ingestFile:()=>ingestFile,discoverAllJsonlFiles:()=>discoverAllJsonlFiles,buildWorkerMap:()=>buildWorkerMap});import{open as open2,readdir as readdir3,stat as stat2}from"fs/promises";import{homedir as homedir16}from"os";import{basename as basename3,join as join20}from"path";function setLiveWorkPending(v){liveWorkPending=v}function extractSubTool(toolName,input){let obj=input;switch(toolName){case"Bash":return(obj?.command??"").split(`
869
616
  `)[0]?.trim()||null;case"Read":case"Write":case"Edit":return obj?.file_path||null;case"Grep":return obj?.pattern||null;case"Glob":return obj?.pattern||null;case"Agent":return obj?.subagent_type||null;case"Skill":return obj?.skill||null;default:return null}}function extractTextContent(content){if(typeof content==="string")return content;if(Array.isArray(content)){let texts=[];for(let block of content)if(typeof block==="string")texts.push(block);else if(block?.type==="text"&&typeof block.text==="string")texts.push(block.text);return texts.length>0?texts.join(`
870
- `):null}return null}async function discoverMainSession(filePath,projectPath,name){try{let st=await stat3(filePath);return{sessionId:basename3(name,".jsonl"),jsonlPath:filePath,projectPath,parentSessionId:null,isSubagent:!1,mtime:Math.floor(st.mtimeMs),fileSize:st.size}}catch{return null}}async function discoverSubagentSessions(projectPath,parentName){let results=[],subagentsDir=join24(projectPath,parentName,"subagents");try{let subFiles=await readdir4(subagentsDir);for(let subFile of subFiles){if(!subFile.endsWith(".jsonl"))continue;try{let filePath=join24(subagentsDir,subFile),st=await stat3(filePath);results.push({sessionId:basename3(subFile,".jsonl"),jsonlPath:filePath,projectPath,parentSessionId:parentName,isSubagent:!0,mtime:Math.floor(st.mtimeMs),fileSize:st.size})}catch{}}}catch{}return results}async function discoverProjectSessions(projectPath){let results=[];try{let entries=await readdir4(projectPath,{withFileTypes:!0});for(let entry of entries){if(entry.isFile()&&entry.name.endsWith(".jsonl")){let file=await discoverMainSession(join24(projectPath,entry.name),projectPath,entry.name);if(file)results.push(file);continue}if(entry.isDirectory()){let subs=await discoverSubagentSessions(projectPath,entry.name);results.push(...subs)}}}catch{}return results}async function discoverAllJsonlFiles(){let claudeDir=join24(process.env.CLAUDE_CONFIG_DIR??join24(homedir17(),".claude"),"projects"),projects;try{projects=await readdir4(claudeDir)}catch{return[]}let results=[];for(let project of projects){let files=await discoverProjectSessions(join24(claudeDir,project));results.push(...files)}return results}async function buildWorkerMap(sql){if(workerMapCache&&Date.now()<workerMapCache.expires)return workerMapCache.map;let map2=new Map;try{let rows=await sql`
617
+ `):null}return null}async function discoverMainSession(filePath,projectPath,name){try{let st=await stat2(filePath);return{sessionId:basename3(name,".jsonl"),jsonlPath:filePath,projectPath,parentSessionId:null,isSubagent:!1,mtime:Math.floor(st.mtimeMs),fileSize:st.size}}catch{return null}}async function discoverSubagentSessions(projectPath,parentName){let results=[],subagentsDir=join20(projectPath,parentName,"subagents");try{let subFiles=await readdir3(subagentsDir);for(let subFile of subFiles){if(!subFile.endsWith(".jsonl"))continue;try{let filePath=join20(subagentsDir,subFile),st=await stat2(filePath);results.push({sessionId:basename3(subFile,".jsonl"),jsonlPath:filePath,projectPath,parentSessionId:parentName,isSubagent:!0,mtime:Math.floor(st.mtimeMs),fileSize:st.size})}catch{}}}catch{}return results}async function discoverProjectSessions(projectPath){let results=[];try{let entries=await readdir3(projectPath,{withFileTypes:!0});for(let entry of entries){if(entry.isFile()&&entry.name.endsWith(".jsonl")){let file=await discoverMainSession(join20(projectPath,entry.name),projectPath,entry.name);if(file)results.push(file);continue}if(entry.isDirectory()){let subs=await discoverSubagentSessions(projectPath,entry.name);results.push(...subs)}}}catch{}return results}async function discoverAllJsonlFiles(){let claudeDir=join20(process.env.CLAUDE_CONFIG_DIR??join20(homedir16(),".claude"),"projects"),projects;try{projects=await readdir3(claudeDir)}catch{return[]}let results=[];for(let project of projects){let files=await discoverProjectSessions(join20(claudeDir,project));results.push(...files)}return results}async function buildWorkerMap(sql){if(workerMapCache&&Date.now()<workerMapCache.expires)return workerMapCache.map;let map2=new Map;try{let rows=await sql`
871
618
  SELECT e.id as executor_id, e.agent_id, e.claude_session_id, a.team, a.wish_slug, a.task_id, a.role
872
619
  FROM executors e
873
620
  JOIN agents a ON e.agent_id = a.id
@@ -911,12 +658,12 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
911
658
  ${sql.array(rows.map((r)=>r.task_id??""))}::text[]
912
659
  )
913
660
  ON CONFLICT (session_id, tool_use_id) DO NOTHING
914
- `}async function ingestFile(sql,sessionId,jsonlPath,projectPath,fromOffset,opts){let chunkSize=opts?.chunkSize??DEFAULT_CHUNK_SIZE,workerMap=opts?.workerMap??await buildWorkerMap(sql),session=await ensureSession(sql,sessionId,jsonlPath,projectPath,workerMap,{parentSessionId:opts?.parentSessionId,isSubagent:opts?.isSubagent,fileSize:opts?.fileSize,mtime:opts?.mtime}),fileSize;try{fileSize=(await stat3(jsonlPath)).size}catch{return{newOffset:fromOffset,contentRowsInserted:0,toolEventsInserted:0}}let effectiveOffset=Math.max(fromOffset,session.lastOffset);if(fileSize<=effectiveOffset)return{newOffset:effectiveOffset,contentRowsInserted:0,toolEventsInserted:0};let bytesAvailable=fileSize-effectiveOffset,bytesToRead=Math.min(bytesAvailable,chunkSize),fh=await open2(jsonlPath,"r");try{let buf=Buffer.alloc(bytesToRead),{bytesRead}=await fh.read(buf,0,bytesToRead,effectiveOffset);if(bytesRead===0)return{newOffset:effectiveOffset,contentRowsInserted:0,toolEventsInserted:0};let raw=buf.subarray(0,bytesRead).toString("utf-8"),safeEnd=raw.length;if(bytesRead===chunkSize&&bytesAvailable>chunkSize){let lastNewline=raw.lastIndexOf(`
661
+ `}async function ingestFile(sql,sessionId,jsonlPath,projectPath,fromOffset,opts){let chunkSize=opts?.chunkSize??DEFAULT_CHUNK_SIZE,workerMap=opts?.workerMap??await buildWorkerMap(sql),session=await ensureSession(sql,sessionId,jsonlPath,projectPath,workerMap,{parentSessionId:opts?.parentSessionId,isSubagent:opts?.isSubagent,fileSize:opts?.fileSize,mtime:opts?.mtime}),fileSize;try{fileSize=(await stat2(jsonlPath)).size}catch{return{newOffset:fromOffset,contentRowsInserted:0,toolEventsInserted:0}}let effectiveOffset=Math.max(fromOffset,session.lastOffset);if(fileSize<=effectiveOffset)return{newOffset:effectiveOffset,contentRowsInserted:0,toolEventsInserted:0};let bytesAvailable=fileSize-effectiveOffset,bytesToRead=Math.min(bytesAvailable,chunkSize),fh=await open2(jsonlPath,"r");try{let buf=Buffer.alloc(bytesToRead),{bytesRead}=await fh.read(buf,0,bytesToRead,effectiveOffset);if(bytesRead===0)return{newOffset:effectiveOffset,contentRowsInserted:0,toolEventsInserted:0};let raw=buf.subarray(0,bytesRead).toString("utf-8"),safeEnd=raw.length;if(bytesRead===chunkSize&&bytesAvailable>chunkSize){let lastNewline=raw.lastIndexOf(`
915
662
  `);if(lastNewline===-1){let skipBuf=Buffer.alloc(Math.min(bytesAvailable,chunkSize*4)),{bytesRead:skipRead}=await fh.read(skipBuf,0,skipBuf.length,effectiveOffset),skipStr=skipBuf.subarray(0,skipRead).toString("utf-8"),nlPos=skipStr.indexOf(`
916
663
  `);if(nlPos===-1)return{newOffset:fileSize,contentRowsInserted:0,toolEventsInserted:0};return{newOffset:effectiveOffset+Buffer.byteLength(skipStr.slice(0,nlPos+1),"utf-8"),contentRowsInserted:0,toolEventsInserted:0}}safeEnd=lastNewline+1}let safeData=raw.slice(0,safeEnd),newOffset=effectiveOffset+Buffer.byteLength(safeData,"utf-8"),{contentRows,toolEvents,turnCount}=parseJsonlChunk(safeData,sessionId,session.totalTurns,{agentId:session.agentId,team:session.team,wishSlug:session.wishSlug,taskId:session.taskId});return await sql.begin(async(tx)=>{await batchInsertContent(tx,contentRows),await batchInsertToolEvents(tx,toolEvents),await tx`
917
664
  UPDATE sessions SET last_ingested_offset = ${newOffset}, total_turns = ${session.totalTurns+turnCount}, updated_at = now()
918
665
  WHERE id = ${sessionId}
919
- `}),{newOffset,contentRowsInserted:contentRows.length,toolEventsInserted:toolEvents.length}}finally{await fh.close()}}async function ingestFileFull(sql,sessionId,jsonlPath,projectPath,fromOffset,opts){return ingestFile(sql,sessionId,jsonlPath,projectPath,fromOffset,{...opts,chunkSize:Number.MAX_SAFE_INTEGER})}var liveWorkPending=!1,workerMapCache=null,WORKER_MAP_TTL_MS=300000,DEFAULT_CHUNK_SIZE=65536;var init_session_capture=()=>{};var exports_session_backfill={};__export(exports_session_backfill,{stopBackfill:()=>stopBackfill,startBackfill:()=>startBackfill,getBackfillStatus:()=>getBackfillStatus});function sleep(ms){return new Promise((resolve5)=>setTimeout(resolve5,ms))}async function updateSyncState(sql,progress){await sql`
666
+ `}),{newOffset,contentRowsInserted:contentRows.length,toolEventsInserted:toolEvents.length}}finally{await fh.close()}}async function ingestFileFull(sql,sessionId,jsonlPath,projectPath,fromOffset,opts){return ingestFile(sql,sessionId,jsonlPath,projectPath,fromOffset,{...opts,chunkSize:Number.MAX_SAFE_INTEGER})}var liveWorkPending=!1,workerMapCache=null,WORKER_MAP_TTL_MS=300000,DEFAULT_CHUNK_SIZE=65536;var init_session_capture=()=>{};var exports_session_backfill={};__export(exports_session_backfill,{stopBackfill:()=>stopBackfill,startBackfill:()=>startBackfill,getBackfillStatus:()=>getBackfillStatus});function sleep2(ms){return new Promise((resolve4)=>setTimeout(resolve4,ms))}async function updateSyncState(sql,progress){await sql`
920
667
  INSERT INTO session_sync (id, status, total_files, processed_files, total_bytes, processed_bytes, errors, updated_at)
921
668
  VALUES ('backfill', ${progress.status}, ${progress.totalFiles}, ${progress.processedFiles}, ${progress.totalBytes}, ${progress.processedBytes}, ${progress.errors}, now())
922
669
  ON CONFLICT (id) DO UPDATE SET
@@ -927,7 +674,7 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
927
674
  processed_bytes = ${progress.processedBytes},
928
675
  errors = ${progress.errors},
929
676
  updated_at = now()
930
- `}async function shouldSkipBackfill(sql){try{let existing=await sql`SELECT status FROM session_sync WHERE id = 'backfill'`;if(existing.length>0&&existing[0].status==="complete")return!0}catch{}try{let[{count}]=await sql`SELECT count(*)::int as count FROM sessions`;if(count>0){let existing=await sql`SELECT status FROM session_sync WHERE id = 'backfill'`;if(existing.length===0||existing[0].status==="complete")return!0}}catch{return!0}return!1}async function yieldToLiveWork(){while(liveWorkPending)await sleep(LIVE_YIELD_POLL_MS)}async function getFileStartOffset(sql,file){let existing=await sql`SELECT last_ingested_offset FROM sessions WHERE id = ${file.sessionId}`;if(existing.length>0)return existing[0].last_ingested_offset??0;return 0}async function processBackfillFile(sql,file,progress,workerMap){let offset=await getFileStartOffset(sql,file);if(offset>=file.fileSize){progress.processedFiles++,progress.processedBytes+=file.fileSize;return}let currentOffset=offset;while(currentOffset<file.fileSize){await yieldToLiveWork();let result=await ingestFile(sql,file.sessionId,file.jsonlPath,file.projectPath,currentOffset,{chunkSize:CHUNK_SIZE,parentSessionId:file.parentSessionId,isSubagent:file.isSubagent,fileSize:file.fileSize,mtime:file.mtime,workerMap});if(result.newOffset<=currentOffset)break;progress.processedBytes+=result.newOffset-currentOffset,currentOffset=result.newOffset}progress.processedFiles++}async function processAllFiles(sql,allFiles,progress,workerMap){for(let file of allFiles){if(!running)break;await yieldToLiveWork();try{await processBackfillFile(sql,file,progress,workerMap)}catch(err){progress.errors++;let message=err instanceof Error?err.message:String(err);console.error(`[backfill] error on ${file.jsonlPath}: ${message}`)}if(progress.processedFiles%50===0)await updateSyncState(sql,progress);await sleep(SLEEP_BETWEEN_FILES_MS)}}function resolveBackfillStatus(progress){if(!running)progress.status="paused",console.log(`[backfill] paused: ${progress.processedFiles}/${progress.totalFiles} files (will resume on next daemon start)`);else if(progress.errors>0&&progress.errors>=progress.totalFiles)progress.status="failed",console.error(`[backfill] failed: ${progress.errors}/${progress.totalFiles} files errored \u2014 will retry on next daemon start`);else progress.status="complete",console.log(`[backfill] complete: ${progress.processedFiles}/${progress.totalFiles} files, ${progress.errors} errors`)}async function startBackfill(sql){if(running)return;if(await shouldSkipBackfill(sql))return;running=!0,console.log("[backfill] starting session backfill...");try{let allFiles=await discoverAllJsonlFiles();allFiles.sort((a,b2)=>b2.mtime-a.mtime);let totalBytes=allFiles.reduce((sum,f)=>sum+f.fileSize,0),progress={totalFiles:allFiles.length,processedFiles:0,totalBytes,processedBytes:0,errors:0,status:"running"};await updateSyncState(sql,progress),console.log(`[backfill] discovered ${allFiles.length} files (${(totalBytes/1024/1024).toFixed(1)} MB)`);let workerMap=await buildWorkerMap(sql);await processAllFiles(sql,allFiles,progress,workerMap),resolveBackfillStatus(progress),await updateSyncState(sql,progress)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`[backfill] fatal error: ${message}`)}finally{running=!1}}function stopBackfill(){running=!1}async function getBackfillStatus(sql){try{let rows=await sql`SELECT * FROM session_sync WHERE id = 'backfill'`;if(rows.length===0)return null;let row=rows[0];return{totalFiles:row.total_files,processedFiles:row.processed_files,totalBytes:row.total_bytes,processedBytes:row.processed_bytes,errors:row.errors,status:row.status}}catch{return null}}var CHUNK_SIZE=65536,SLEEP_BETWEEN_FILES_MS=100,LIVE_YIELD_POLL_MS=200,running=!1;var init_session_backfill=__esm(()=>{init_session_capture()});var exports_agent_sync={};__export(exports_agent_sync,{watchAgentDirectory:()=>watchAgentDirectory,syncAgentDirectory:()=>syncAgentDirectory,printSyncResult:()=>printSyncResult});import{execSync as execSync4}from"child_process";import{existsSync as existsSync21,watch as fsWatch,readdirSync as readdirSync6,realpathSync as realpathSync2}from"fs";import{join as join25}from"path";function getGitRemoteUrl(dir){try{return execSync4(`git -C "${dir}" config --get remote.origin.url`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function extractOrgRepo(remoteUrl){let sshMatch=remoteUrl.match(/[^/:]+\/[^/]+?(?:\.git)?$/);if(sshMatch)return sshMatch[0].replace(/\.git$/,"");return null}function getRepoPathForAgent(agentDir){let reposLink=join25(agentDir,"repos");try{if(!existsSync21(reposLink))return null;let target=realpathSync2(reposLink);if(!existsSync21(target))return null;return target}catch{return null}}function discoverAgents(workspaceRoot){let agentsDir=join25(workspaceRoot,"agents");if(!existsSync21(agentsDir))return[];let agents=[];try{let entries=readdirSync6(agentsDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let agentDir=join25(agentsDir,entry.name);if(!existsSync21(join25(agentDir,"AGENTS.md")))continue;agents.push({name:entry.name,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)})}}catch{}return agents}function discoverSingleAgent(workspaceRoot,agentName){let agentDir=join25(workspaceRoot,"agents",agentName);if(!existsSync21(join25(agentDir,"AGENTS.md")))return null;return{name:agentName,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)}}async function archiveAgent(name){let archived=!1;try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));if((await(await getConnection2())`
677
+ `}async function shouldSkipBackfill(sql){try{let existing=await sql`SELECT status FROM session_sync WHERE id = 'backfill'`;if(existing.length>0&&existing[0].status==="complete")return!0}catch{}try{let[{count}]=await sql`SELECT count(*)::int as count FROM sessions`;if(count>0){let existing=await sql`SELECT status FROM session_sync WHERE id = 'backfill'`;if(existing.length===0||existing[0].status==="complete")return!0}}catch{return!0}return!1}async function yieldToLiveWork(){while(liveWorkPending)await sleep2(LIVE_YIELD_POLL_MS)}async function getFileStartOffset(sql,file){let existing=await sql`SELECT last_ingested_offset FROM sessions WHERE id = ${file.sessionId}`;if(existing.length>0)return existing[0].last_ingested_offset??0;return 0}async function processBackfillFile(sql,file,progress,workerMap){let offset=await getFileStartOffset(sql,file);if(offset>=file.fileSize){progress.processedFiles++,progress.processedBytes+=file.fileSize;return}let currentOffset=offset;while(currentOffset<file.fileSize){await yieldToLiveWork();let result=await ingestFile(sql,file.sessionId,file.jsonlPath,file.projectPath,currentOffset,{chunkSize:CHUNK_SIZE,parentSessionId:file.parentSessionId,isSubagent:file.isSubagent,fileSize:file.fileSize,mtime:file.mtime,workerMap});if(result.newOffset<=currentOffset)break;progress.processedBytes+=result.newOffset-currentOffset,currentOffset=result.newOffset}progress.processedFiles++}async function processAllFiles(sql,allFiles,progress,workerMap){for(let file of allFiles){if(!running)break;await yieldToLiveWork();try{await processBackfillFile(sql,file,progress,workerMap)}catch(err){progress.errors++;let message=err instanceof Error?err.message:String(err);console.error(`[backfill] error on ${file.jsonlPath}: ${message}`)}if(progress.processedFiles%50===0)await updateSyncState(sql,progress);await sleep2(SLEEP_BETWEEN_FILES_MS)}}function resolveBackfillStatus(progress){if(!running)progress.status="paused",console.log(`[backfill] paused: ${progress.processedFiles}/${progress.totalFiles} files (will resume on next daemon start)`);else if(progress.errors>0&&progress.errors>=progress.totalFiles)progress.status="failed",console.error(`[backfill] failed: ${progress.errors}/${progress.totalFiles} files errored \u2014 will retry on next daemon start`);else progress.status="complete",console.log(`[backfill] complete: ${progress.processedFiles}/${progress.totalFiles} files, ${progress.errors} errors`)}async function startBackfill(sql){if(running)return;if(await shouldSkipBackfill(sql))return;running=!0,console.log("[backfill] starting session backfill...");try{let allFiles=await discoverAllJsonlFiles();allFiles.sort((a,b2)=>b2.mtime-a.mtime);let totalBytes=allFiles.reduce((sum,f)=>sum+f.fileSize,0),progress={totalFiles:allFiles.length,processedFiles:0,totalBytes,processedBytes:0,errors:0,status:"running"};await updateSyncState(sql,progress),console.log(`[backfill] discovered ${allFiles.length} files (${(totalBytes/1024/1024).toFixed(1)} MB)`);let workerMap=await buildWorkerMap(sql);await processAllFiles(sql,allFiles,progress,workerMap),resolveBackfillStatus(progress),await updateSyncState(sql,progress)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`[backfill] fatal error: ${message}`)}finally{running=!1}}function stopBackfill(){running=!1}async function getBackfillStatus(sql){try{let rows=await sql`SELECT * FROM session_sync WHERE id = 'backfill'`;if(rows.length===0)return null;let row=rows[0];return{totalFiles:row.total_files,processedFiles:row.processed_files,totalBytes:row.total_bytes,processedBytes:row.processed_bytes,errors:row.errors,status:row.status}}catch{return null}}var CHUNK_SIZE=65536,SLEEP_BETWEEN_FILES_MS=100,LIVE_YIELD_POLL_MS=200,running=!1;var init_session_backfill=__esm(()=>{init_session_capture()});var exports_agent_sync={};__export(exports_agent_sync,{watchAgentDirectory:()=>watchAgentDirectory,syncAgentDirectory:()=>syncAgentDirectory,printSyncResult:()=>printSyncResult});import{execSync as execSync4}from"child_process";import{existsSync as existsSync19,watch as fsWatch,readdirSync as readdirSync5,realpathSync as realpathSync2}from"fs";import{join as join21}from"path";function getGitRemoteUrl(dir){try{return execSync4(`git -C "${dir}" config --get remote.origin.url`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function extractOrgRepo(remoteUrl){let sshMatch=remoteUrl.match(/[^/:]+\/[^/]+?(?:\.git)?$/);if(sshMatch)return sshMatch[0].replace(/\.git$/,"");return null}function getRepoPathForAgent(agentDir){let reposLink=join21(agentDir,"repos");try{if(!existsSync19(reposLink))return null;let target=realpathSync2(reposLink);if(!existsSync19(target))return null;return target}catch{return null}}function discoverAgents(workspaceRoot){let agentsDir=join21(workspaceRoot,"agents");if(!existsSync19(agentsDir))return[];let agents=[];try{let entries=readdirSync5(agentsDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let agentDir=join21(agentsDir,entry.name);if(!existsSync19(join21(agentDir,"AGENTS.md")))continue;agents.push({name:entry.name,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)})}}catch{}return agents}function discoverSingleAgent(workspaceRoot,agentName){let agentDir=join21(workspaceRoot,"agents",agentName);if(!existsSync19(join21(agentDir,"AGENTS.md")))return null;return{name:agentName,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)}}async function archiveAgent(name){let archived=!1;try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));if((await(await getConnection2())`
931
678
  UPDATE agents SET state = 'archived', updated_at = now()
932
679
  WHERE (custom_name = ${name} OR role = ${name})
933
680
  AND (state IS NULL OR state != 'archived')
@@ -936,7 +683,7 @@ Use a different --role name for a second worker, e.g.: --role ${role}-2`),proces
936
683
  WHERE (custom_name = ${agent.name} OR role = ${agent.name})
937
684
  AND state = 'archived'
938
685
  `}catch{}await triggerSessionBackfill(agent).catch(()=>{})}async function triggerSessionBackfill(_agent){try{let{getConnection:getConnection2,isAvailable:isAvailable2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!await isAvailable2())return;let sql=await getConnection2(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql)}catch{}}async function syncAgentDirectory(workspaceRoot){let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]},agents=discoverAgents(workspaceRoot),discoveredNames=new Set(agents.map((a)=>a.name));for(let agent of agents)try{await syncSingleAgent(agent,result)}catch(err){result.errors.push({name:agent.name,error:err instanceof Error?err.message:String(err)})}if(await archiveMissingAgents(workspaceRoot,discoveredNames,result),result.registered.length+result.updated.length+result.archived.length+result.reactivated.length>0)await regenerateAgentCache().catch(()=>{});return result}function printSyncResult(result){if(result.registered.length>0)console.log(` Registered: ${result.registered.join(", ")}`);if(result.updated.length>0)console.log(` Updated: ${result.updated.join(", ")}`);if(result.reactivated.length>0)console.log(` Reactivated: ${result.reactivated.join(", ")}`);if(result.archived.length>0)console.log(` Archived: ${result.archived.join(", ")}`);if(result.unchanged.length>0)console.log(` Unchanged: ${result.unchanged.join(", ")}`);for(let err of result.errors)console.error(` Error (${err.name}): ${err.error}`);let total=result.registered.length+result.updated.length+result.unchanged.length+result.reactivated.length;console.log(`
939
- Sync complete: ${total} active agent(s), ${result.archived.length} archived.`)}async function archiveMissingAgents(workspaceRoot,discoveredNames,result){try{let storeItems=await listItemsFromStore("agent"),agentsDir=join25(workspaceRoot,"agents");for(let item of storeItems){if(discoveredNames.has(item.name))continue;let manifest=item.manifest??{};if(manifest.archived)continue;if(manifest.source!=="auto-sync")continue;if(item.install_path&&!item.install_path.startsWith(agentsDir))continue;if(await archiveAgent(item.name))result.archived.push(item.name)}}catch{}}async function syncSingleAgentByName(workspaceRoot,agentName){let agent=discoverSingleAgent(workspaceRoot,agentName);if(!agent)return"not-found";let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]};if(await syncSingleAgent(agent,result),result.registered.length>0||result.updated.length>0||result.reactivated.length>0)await regenerateAgentCache().catch(()=>{});if(result.reactivated.length>0)return"reactivated";if(result.registered.length>0)return"registered";if(result.updated.length>0)return"updated";return"unchanged"}async function syncSingleAgent(agent,result){let repoPath=(agent.repoUrl?extractOrgRepo(agent.repoUrl):null)??agent.repoUrl??agent.dir,existing=await getItemFromStore(agent.name).catch(()=>null);if(!existing){await registerItemInStore({name:agent.name,itemType:"agent",installPath:agent.dir,gitUrl:agent.repoUrl??void 0,manifest:{promptMode:"append",repo:repoPath,productRepo:agent.productRepo,source:"auto-sync"}}),result.registered.push(agent.name);return}let manifest=existing.manifest??{};if(manifest.archived){await reactivateAgent(agent),result.reactivated.push(agent.name);return}if(manifest.repo!==repoPath||existing.install_path!==agent.dir||agent.productRepo&&manifest.productRepo!==agent.productRepo)await updateItemInStore(agent.name,{installPath:agent.dir,gitUrl:agent.repoUrl??void 0,manifest:{...manifest,repo:repoPath,productRepo:agent.productRepo,source:"auto-sync"}}),result.updated.push(agent.name);else result.unchanged.push(agent.name)}async function processWatchedAgent(workspaceRoot,agentsDir,name){let agentDir=join25(agentsDir,name);if(existsSync21(agentDir)&&existsSync21(join25(agentDir,"AGENTS.md"))){let action=await syncSingleAgentByName(workspaceRoot,name);return action!=="unchanged"&&action!=="not-found"?action:null}if(!existsSync21(agentDir)){if(await archiveAgent(name))return await regenerateAgentCache().catch(()=>{}),"archived"}return null}function watchAgentDirectory(workspaceRoot,options){let agentsDir=join25(workspaceRoot,"agents");if(!existsSync21(agentsDir))return null;let debounceTimer=null,pendingChanges=new Set,processChanges=async()=>{let names=[...pendingChanges];pendingChanges.clear();for(let name of names)try{let action=await processWatchedAgent(workspaceRoot,agentsDir,name);if(action)options?.onSync?.(name,action)}catch{}},watcher=fsWatch(agentsDir,{persistent:!1},(_event,filename)=>{if(!filename)return;let name=filename.split("/")[0];if(!name||name.startsWith("."))return;if(pendingChanges.add(name),debounceTimer)clearTimeout(debounceTimer);debounceTimer=setTimeout(processChanges,2000)});return{close:()=>{if(debounceTimer)clearTimeout(debounceTimer);watcher.close()}}}var init_agent_sync=__esm(()=>{init_agent_cache()});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});var exports_task_service={};__export(exports_task_service,{updateTask:()=>updateTask,updateMessage:()=>updateMessage,untagTask:()=>untagTask,unblockTask:()=>unblockTask,unarchiveTask:()=>unarchiveTask,unarchiveProject:()=>unarchiveProject,tagTask:()=>tagTask,setRelease:()=>setRelease,setPreference:()=>setPreference,sendMessage:()=>sendMessage,resolveTaskId:()=>resolveTaskId,resolveChannels:()=>resolveChannels,removeMember:()=>removeMember,removeDependency:()=>removeDependency,removeActor:()=>removeActor,releaseTask:()=>releaseTask,moveTask:()=>moveTask,markDone:()=>markDone,listTypes:()=>listTypes,listTasksForActor:()=>listTasksForActor,listTasks:()=>listTasks,listTags:()=>listTags,listReleases:()=>listReleases,listProjectsFiltered:()=>listProjectsFiltered,listProjects:()=>listProjects,listConversations:()=>listConversations,linkTask:()=>linkTask,getType:()=>getType,getTaskTags:()=>getTaskTags,getTaskActors:()=>getTaskActors,getTask:()=>getTask,getStageLog:()=>getStageLog,getProjectByRepoPath:()=>getProjectByRepoPath,getProjectByName:()=>getProjectByName,getPreferences:()=>getPreferences,getMessages:()=>getMessages,getMessage:()=>getMessage,getMembers:()=>getMembers,getDependents:()=>getDependents,getConversation:()=>getConversation,getCheckoutOwner:()=>getCheckoutOwner,getBlockingDependencies:()=>getBlockingDependencies,getBlockers:()=>getBlockers,forceUnlockTask:()=>forceUnlockTask,findOrCreateConversation:()=>findOrCreateConversation,expireStaleCheckouts:()=>expireStaleCheckouts,ensureProject:()=>ensureProject,deletePreference:()=>deletePreference,createType:()=>createType,createTask:()=>createTask,createTag:()=>createTag,createProject:()=>createProject,commentOnTask:()=>commentOnTask,checkoutTask:()=>checkoutTask,blockTask:()=>blockTask,assignTask:()=>assignTask,archiveTask:()=>archiveTask,archiveProject:()=>archiveProject,archiveBoard:()=>archiveBoard,addMember:()=>addMember,addDependency:()=>addDependency});import{execSync as execSync5}from"child_process";function str2(v){return v!=null?String(v):null}function strOrDefault(v,def){return v!=null?String(v):def}function mapTask(row){return{id:row.id,seq:row.seq,parentId:str2(row.parent_id),repoPath:row.repo_path,projectId:str2(row.project_id),genieOsFolderId:str2(row.genie_os_folder_id),wishFile:str2(row.wish_file),groupName:str2(row.group_name),title:row.title,description:str2(row.description),acceptanceCriteria:str2(row.acceptance_criteria),typeId:row.type_id,stage:row.stage,status:row.status,priority:row.priority,startDate:str2(row.start_date),dueDate:str2(row.due_date),estimatedEffort:str2(row.estimated_effort),startedAt:str2(row.started_at),endedAt:str2(row.ended_at),blockedReason:str2(row.blocked_reason),releaseId:str2(row.release_id),checkoutRunId:str2(row.checkout_run_id),executionLockedAt:str2(row.execution_locked_at),checkoutTimeoutMs:row.checkout_timeout_ms??600000,sessionId:str2(row.session_id),paneId:str2(row.pane_id),traceId:str2(row.trace_id),boardId:str2(row.board_id),columnId:str2(row.column_id),externalId:str2(row.external_id),externalUrl:str2(row.external_url),archivedAt:str2(row.archived_at),metadata:row.metadata??{},createdAt:strOrDefault(row.created_at,""),updatedAt:strOrDefault(row.updated_at,"")}}function mapConversation(row){return{id:row.id,parentMessageId:row.parent_message_id!=null?Number(row.parent_message_id):null,name:row.name??null,type:row.type,linkedEntity:row.linked_entity??null,linkedEntityId:row.linked_entity_id??null,createdByType:row.created_by_type??null,createdById:row.created_by_id??null,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapMessage(row){return{id:Number(row.id),conversationId:row.conversation_id,replyToId:row.reply_to_id!=null?Number(row.reply_to_id):null,senderType:row.sender_type,senderId:row.sender_id,body:row.body,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapTaskActor(row){return{taskId:row.task_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,permissions:row.permissions??{},createdAt:String(row.created_at)}}function mapDependency(row){return{taskId:row.task_id,dependsOnId:row.depends_on_id,depType:row.dep_type,createdAt:String(row.created_at)}}function mapTag(row){return{id:row.id,name:row.name,color:row.color??"#9ca3af",typeId:row.type_id??null,createdAt:String(row.created_at)}}function mapTaskType(row){return{id:row.id,name:row.name,description:row.description??null,icon:row.icon??null,stages:row.stages,isBuiltin:row.is_builtin,createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapNotificationPref(row){return{actorType:row.actor_type,actorId:row.actor_id,channel:row.channel,priorityThreshold:row.priority_threshold,isDefault:row.is_default,enabled:row.enabled,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapStageLog(row){return{id:Number(row.id),taskId:row.task_id,fromStage:row.from_stage??null,toStage:row.to_stage,actorType:row.actor_type??null,actorId:row.actor_id??null,runId:row.run_id??null,gateType:row.gate_type??null,createdAt:String(row.created_at)}}function mapConversationMember(row){return{conversationId:row.conversation_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,joinedAt:String(row.joined_at)}}function mapProject(row){return{id:row.id,name:row.name,repoPath:str2(row.repo_path),description:str2(row.description),status:strOrDefault(row.status,"active"),archivedAt:str2(row.archived_at),createdAt:String(row.created_at)}}function getRepoPath(){try{return execSync5("git rev-parse --show-toplevel",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return process.cwd()}}async function resolveTaskId(idOrSeq,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),projectSeqMatch=idOrSeq.match(/^([^#]+)#(\d+)$/);if(projectSeqMatch&&!idOrSeq.startsWith("#")){let[,projectName,seqStr]=projectSeqMatch,seq2=Number.parseInt(seqStr,10);if(Number.isNaN(seq2))return null;let rows2=await sql`
686
+ Sync complete: ${total} active agent(s), ${result.archived.length} archived.`)}async function archiveMissingAgents(workspaceRoot,discoveredNames,result){try{let storeItems=await listItemsFromStore("agent"),agentsDir=join21(workspaceRoot,"agents");for(let item of storeItems){if(discoveredNames.has(item.name))continue;let manifest=item.manifest??{};if(manifest.archived)continue;if(manifest.source!=="auto-sync")continue;if(item.install_path&&!item.install_path.startsWith(agentsDir))continue;if(await archiveAgent(item.name))result.archived.push(item.name)}}catch{}}async function syncSingleAgentByName(workspaceRoot,agentName){let agent=discoverSingleAgent(workspaceRoot,agentName);if(!agent)return"not-found";let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]};if(await syncSingleAgent(agent,result),result.registered.length>0||result.updated.length>0||result.reactivated.length>0)await regenerateAgentCache().catch(()=>{});if(result.reactivated.length>0)return"reactivated";if(result.registered.length>0)return"registered";if(result.updated.length>0)return"updated";return"unchanged"}async function syncSingleAgent(agent,result){let repoPath=(agent.repoUrl?extractOrgRepo(agent.repoUrl):null)??agent.repoUrl??agent.dir,existing=await getItemFromStore(agent.name).catch(()=>null);if(!existing){await registerItemInStore({name:agent.name,itemType:"agent",installPath:agent.dir,gitUrl:agent.repoUrl??void 0,manifest:{promptMode:"append",repo:repoPath,productRepo:agent.productRepo,source:"auto-sync"}}),result.registered.push(agent.name);return}let manifest=existing.manifest??{};if(manifest.archived){await reactivateAgent(agent),result.reactivated.push(agent.name);return}if(manifest.repo!==repoPath||existing.install_path!==agent.dir||agent.productRepo&&manifest.productRepo!==agent.productRepo)await updateItemInStore(agent.name,{installPath:agent.dir,gitUrl:agent.repoUrl??void 0,manifest:{...manifest,repo:repoPath,productRepo:agent.productRepo,source:"auto-sync"}}),result.updated.push(agent.name);else result.unchanged.push(agent.name)}async function processWatchedAgent(workspaceRoot,agentsDir,name){let agentDir=join21(agentsDir,name);if(existsSync19(agentDir)&&existsSync19(join21(agentDir,"AGENTS.md"))){let action=await syncSingleAgentByName(workspaceRoot,name);return action!=="unchanged"&&action!=="not-found"?action:null}if(!existsSync19(agentDir)){if(await archiveAgent(name))return await regenerateAgentCache().catch(()=>{}),"archived"}return null}function watchAgentDirectory(workspaceRoot,options){let agentsDir=join21(workspaceRoot,"agents");if(!existsSync19(agentsDir))return null;let debounceTimer=null,pendingChanges=new Set,processChanges=async()=>{let names=[...pendingChanges];pendingChanges.clear();for(let name of names)try{let action=await processWatchedAgent(workspaceRoot,agentsDir,name);if(action)options?.onSync?.(name,action)}catch{}},watcher=fsWatch(agentsDir,{persistent:!1},(_event,filename)=>{if(!filename)return;let name=filename.split("/")[0];if(!name||name.startsWith("."))return;if(pendingChanges.add(name),debounceTimer)clearTimeout(debounceTimer);debounceTimer=setTimeout(processChanges,2000)});return{close:()=>{if(debounceTimer)clearTimeout(debounceTimer);watcher.close()}}}var init_agent_sync=__esm(()=>{init_agent_cache()});var exports_workspace={};__export(exports_workspace,{scanAgents:()=>scanAgents2,getWorkspaceConfig:()=>getWorkspaceConfig,findWorkspace:()=>findWorkspace});import{existsSync as existsSync20,readFileSync as readFileSync10,readdirSync as readdirSync6}from"fs";import{dirname as dirname5,join as join22,resolve as resolve4,sep}from"path";function findWorkspace(cwd){let startDir=resolve4(cwd??process.cwd()),current=startDir;while(!0){let candidate=join22(current,WORKSPACE_MARKER);if(existsSync20(candidate)){let agent=detectAgent(startDir,current);return{root:current,agent:agent??void 0}}let parent=dirname5(current);if(parent===current)break;current=parent}return null}function detectAgent(startDir,workspaceRoot){let agentsDir=join22(workspaceRoot,"agents"),relative=startDir.slice(agentsDir.length);if(!startDir.startsWith(agentsDir)||relative.length>0&&relative[0]!==sep)return null;let parts=relative.split(sep).filter(Boolean);if(parts.length===0)return null;let agentName=parts[0],agentsMd=join22(agentsDir,agentName,"AGENTS.md");if(existsSync20(agentsMd))return agentName;return null}function getWorkspaceConfig(root){let configPath2=join22(root,WORKSPACE_MARKER),raw=readFileSync10(configPath2,"utf-8");return JSON.parse(raw)}function scanAgents2(root){let agentsDir=join22(root,"agents");if(!existsSync20(agentsDir))return[];try{return readdirSync6(agentsDir,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&existsSync20(join22(agentsDir,d.name,"AGENTS.md"))).map((d)=>d.name).sort()}catch{return[]}}var WORKSPACE_MARKER=".genie/workspace.json";var init_workspace=()=>{};function padRight(str2,len){return str2.length>=len?str2:str2+" ".repeat(len-str2.length)}function truncate2(str2,len){return str2.length<=len?str2:`${str2.slice(0,len-1)}\u2026`}function formatDate(iso){if(!iso)return"-";return new Date(iso).toLocaleDateString("en-US",{month:"short",day:"numeric"})}function formatRelativeTimestamp(ts3){let d=new Date(ts3),diffMs=Date.now()-d.getTime();if(diffMs<60000)return`${Math.floor(diffMs/1000)}s ago`;if(diffMs<3600000)return`${Math.floor(diffMs/60000)}m ago`;if(diffMs<86400000)return`${Math.floor(diffMs/3600000)}h ago`;return d.toISOString().replace("T"," ").slice(0,19)}function formatTimestamp(iso,opts){if(!iso)return opts?.fallback??"-";let d=iso instanceof Date?iso:new Date(iso),fmt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return d.toLocaleString("en-US",fmt)}function formatTime(iso,opts){try{let date=new Date(iso),fmt={hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return date.toLocaleTimeString("en-US",fmt)}catch{return opts?.fallback??"??:??"}}var exports_task_service={};__export(exports_task_service,{updateTask:()=>updateTask,updateMessage:()=>updateMessage,untagTask:()=>untagTask,unblockTask:()=>unblockTask,unarchiveTask:()=>unarchiveTask,unarchiveProject:()=>unarchiveProject,tagTask:()=>tagTask,setRelease:()=>setRelease,setPreference:()=>setPreference,sendMessage:()=>sendMessage,resolveTaskId:()=>resolveTaskId,resolveChannels:()=>resolveChannels,removeMember:()=>removeMember,removeDependency:()=>removeDependency,removeActor:()=>removeActor,releaseTask:()=>releaseTask,moveTask:()=>moveTask,markDone:()=>markDone,listTypes:()=>listTypes,listTasksForActor:()=>listTasksForActor,listTasks:()=>listTasks,listTags:()=>listTags,listReleases:()=>listReleases,listProjectsFiltered:()=>listProjectsFiltered,listProjects:()=>listProjects,listConversations:()=>listConversations,linkTask:()=>linkTask,getType:()=>getType,getTaskTags:()=>getTaskTags,getTaskActors:()=>getTaskActors,getTask:()=>getTask,getStageLog:()=>getStageLog,getProjectByRepoPath:()=>getProjectByRepoPath,getProjectByName:()=>getProjectByName,getPreferences:()=>getPreferences,getMessages:()=>getMessages,getMessage:()=>getMessage,getMembers:()=>getMembers,getDependents:()=>getDependents,getConversation:()=>getConversation,getCheckoutOwner:()=>getCheckoutOwner,getBlockingDependencies:()=>getBlockingDependencies,getBlockers:()=>getBlockers,forceUnlockTask:()=>forceUnlockTask,findOrCreateConversation:()=>findOrCreateConversation,expireStaleCheckouts:()=>expireStaleCheckouts,ensureProject:()=>ensureProject,deletePreference:()=>deletePreference,createType:()=>createType,createTask:()=>createTask,createTag:()=>createTag,createProject:()=>createProject,commentOnTask:()=>commentOnTask,checkoutTask:()=>checkoutTask,blockTask:()=>blockTask,assignTask:()=>assignTask,archiveTask:()=>archiveTask,archiveProject:()=>archiveProject,archiveBoard:()=>archiveBoard,addMember:()=>addMember,addDependency:()=>addDependency});import{execSync as execSync5}from"child_process";function str2(v){return v!=null?String(v):null}function strOrDefault(v,def){return v!=null?String(v):def}function mapTask(row){return{id:row.id,seq:row.seq,parentId:str2(row.parent_id),repoPath:row.repo_path,projectId:str2(row.project_id),genieOsFolderId:str2(row.genie_os_folder_id),wishFile:str2(row.wish_file),groupName:str2(row.group_name),title:row.title,description:str2(row.description),acceptanceCriteria:str2(row.acceptance_criteria),typeId:row.type_id,stage:row.stage,status:row.status,priority:row.priority,startDate:str2(row.start_date),dueDate:str2(row.due_date),estimatedEffort:str2(row.estimated_effort),startedAt:str2(row.started_at),endedAt:str2(row.ended_at),blockedReason:str2(row.blocked_reason),releaseId:str2(row.release_id),checkoutRunId:str2(row.checkout_run_id),executionLockedAt:str2(row.execution_locked_at),checkoutTimeoutMs:row.checkout_timeout_ms??600000,sessionId:str2(row.session_id),paneId:str2(row.pane_id),traceId:str2(row.trace_id),boardId:str2(row.board_id),columnId:str2(row.column_id),externalId:str2(row.external_id),externalUrl:str2(row.external_url),archivedAt:str2(row.archived_at),metadata:row.metadata??{},createdAt:strOrDefault(row.created_at,""),updatedAt:strOrDefault(row.updated_at,"")}}function mapConversation(row){return{id:row.id,parentMessageId:row.parent_message_id!=null?Number(row.parent_message_id):null,name:row.name??null,type:row.type,linkedEntity:row.linked_entity??null,linkedEntityId:row.linked_entity_id??null,createdByType:row.created_by_type??null,createdById:row.created_by_id??null,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapMessage(row){return{id:Number(row.id),conversationId:row.conversation_id,replyToId:row.reply_to_id!=null?Number(row.reply_to_id):null,senderType:row.sender_type,senderId:row.sender_id,body:row.body,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapTaskActor(row){return{taskId:row.task_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,permissions:row.permissions??{},createdAt:String(row.created_at)}}function mapDependency(row){return{taskId:row.task_id,dependsOnId:row.depends_on_id,depType:row.dep_type,createdAt:String(row.created_at)}}function mapTag(row){return{id:row.id,name:row.name,color:row.color??"#9ca3af",typeId:row.type_id??null,createdAt:String(row.created_at)}}function mapTaskType(row){return{id:row.id,name:row.name,description:row.description??null,icon:row.icon??null,stages:row.stages,isBuiltin:row.is_builtin,createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapNotificationPref(row){return{actorType:row.actor_type,actorId:row.actor_id,channel:row.channel,priorityThreshold:row.priority_threshold,isDefault:row.is_default,enabled:row.enabled,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapStageLog(row){return{id:Number(row.id),taskId:row.task_id,fromStage:row.from_stage??null,toStage:row.to_stage,actorType:row.actor_type??null,actorId:row.actor_id??null,runId:row.run_id??null,gateType:row.gate_type??null,createdAt:String(row.created_at)}}function mapConversationMember(row){return{conversationId:row.conversation_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,joinedAt:String(row.joined_at)}}function mapProject(row){return{id:row.id,name:row.name,repoPath:str2(row.repo_path),description:str2(row.description),status:strOrDefault(row.status,"active"),archivedAt:str2(row.archived_at),createdAt:String(row.created_at)}}function getRepoPath(){try{return execSync5("git rev-parse --show-toplevel",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return process.cwd()}}async function resolveTaskId(idOrSeq,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),projectSeqMatch=idOrSeq.match(/^([^#]+)#(\d+)$/);if(projectSeqMatch&&!idOrSeq.startsWith("#")){let[,projectName,seqStr]=projectSeqMatch,seq2=Number.parseInt(seqStr,10);if(Number.isNaN(seq2))return null;let rows2=await sql`
940
687
  SELECT t.id FROM tasks t
941
688
  JOIN projects p ON t.project_id = p.id
942
689
  WHERE p.name = ${projectName} AND t.seq = ${seq2}
@@ -1234,7 +981,376 @@ Sync complete: ${total} active agent(s), ${result.archived.length} archived.`)}a
1234
981
  WHERE actor_type = ${actor.actorType}
1235
982
  AND actor_id = ${actor.actorId}
1236
983
  AND channel = ${channel}
1237
- `).count>0}async function listTasksForActor(actor,filters={}){let sql=await getConnection(),conditions=[],values2=[],paramIdx=1;if(filters.allProjects);else if(filters.projectName)conditions.push(`t.project_id = (SELECT id FROM projects WHERE name = $${paramIdx++})`),values2.push(filters.projectName);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_audit();init_db()});import{randomUUID as randomUUID4}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[];return(await listTeams2()).filter((t)=>t.status==="in_progress").map((t)=>t.name)}async function writeMailbox(repoPath,leader,message,traceId){try{await(await getConnection())`
984
+ `).count>0}async function listTasksForActor(actor,filters={}){let sql=await getConnection(),conditions=[],values2=[],paramIdx=1;if(filters.allProjects);else if(filters.projectName)conditions.push(`t.project_id = (SELECT id FROM projects WHERE name = $${paramIdx++})`),values2.push(filters.projectName);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_audit();init_db()});import{createHash as createHash2}from"crypto";function parseRoutingHeader(text){if(!text)return null;let match=text.split(`
985
+ `)[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 createHash2("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"]});import{writeFileSync as writeFileSync7}from"fs";import{join as join23}from"path";function scaffoldAgentFiles(targetDir,agentName){writeFileSync7(join23(targetDir,"SOUL.md"),SOUL_TEMPLATE),writeFileSync7(join23(targetDir,"HEARTBEAT.md"),HEARTBEAT_TEMPLATE);let agentsMd=agentName?AGENTS_TEMPLATE.replace("name: my-agent",`name: ${agentName}`):AGENTS_TEMPLATE;writeFileSync7(join23(targetDir,"AGENTS.md"),agentsMd)}var SOUL_TEMPLATE=`# Soul
986
+
987
+ You are an AI assistant. Define your role, personality, and approach here.
988
+
989
+ Replace this with your agent's identity \u2014 who they are, how they communicate, and what they care about.
990
+ `,HEARTBEAT_TEMPLATE=`# Heartbeat
991
+
992
+ Run this checklist on every iteration. Exit early if nothing actionable.
993
+
994
+ ## Checklist
995
+
996
+ ### 1. Check Assignments
997
+ Review your task queue. What's assigned to you? Prioritize by urgency and impact.
998
+
999
+ ### 2. Do Work
1000
+ Execute on your current tasks. Focus on the highest-priority item first.
1001
+
1002
+ ### 3. Report Progress
1003
+ Update status on completed or blocked items. Keep it factual.
1004
+
1005
+ ### 4. Exit If Nothing Actionable
1006
+ If all work is done and no new tasks exist \u2014 exit. Don't create busywork.
1007
+ `,AGENTS_TEMPLATE=`---
1008
+ name: my-agent
1009
+ description: "Describe what this agent does."
1010
+ model: inherit
1011
+ color: blue
1012
+ promptMode: system
1013
+ ---
1014
+
1015
+ @HEARTBEAT.md
1016
+
1017
+ <mission>
1018
+ Define your agent's mission here. What is their primary goal? What do they own?
1019
+ </mission>
1020
+
1021
+ <principles>
1022
+ - **Clarity over ambiguity.** Be specific about expectations and outcomes.
1023
+ - **Quality over speed.** Do it right the first time.
1024
+ </principles>
1025
+
1026
+ <constraints>
1027
+ - List any hard constraints or rules this agent must follow.
1028
+ </constraints>
1029
+ `;var init_templates=()=>{};var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync}from"child_process";import{createHash as createHash3}from"crypto";import{existsSync as existsSync21}from"fs";import{basename as basename4,join as join24}from"path";function shortPathHash(p){return createHash3("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join24(process.cwd(),"AGENTS.md");if(existsSync21(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"team-lead"}}async function ensureNativeTeamForLeader(teamName,cwd){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeam(teamName,`Genie team: ${teamName}`,"pending",leaderName),await registerNativeMember(teamName,{agentName:basename4(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName})}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);await terminateActiveExecutor(agentIdentity.id);let 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{}let executor=await createExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir});await setCurrentExecutor(agentIdentity.id,executor.id)}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename4(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){if(await ensureNativeTeamForLeader(windowName,workspaceDir),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=basename4(workspaceDir),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`),await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,systemPromptFile){let continueName=sanitizeTeamName(windowName),hasPriorSession=sessionExists(continueName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,hasPriorSession?continueName:void 0);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),hasPriorSession){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 freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir),await ensureNativeTeamForLeader(windowName,workingDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,systemPromptFile),console.log(`Started Claude Code as ${basename4(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`),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...`),await ensureNativeTeamForLeader(windowName,workingDir);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,systemPromptFile),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(basename4(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));spawnSync("tmux",[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function sessionCommand(options={}){let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename4(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default2({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join24(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),attachToWindow(sessionName,windowName);else if(process.env.TMUX){let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile),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_esm6();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync22}from"fs";import{join as join25}from"path";function getSystemPromptFile(workingDir){let agentsPath=join25(workingDir,"AGENTS.md");if(existsSync22(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let current=await getCurrentSessionName();if(current)return current;let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam3(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}let sessionName=sanitizeTeamName(teamName);if(await findSessionByName(sessionName))return sessionName;if(!await createSession(sessionName))throw Error(`Failed to create tmux session "${sessionName}"`);return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let sessionName=await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let currentSession=await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:currentSession,window:sanitizeWindowName(teamName)};let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=(await getTeam3(teamName))?.leader||"team-lead";await ensureNativeTeam(teamName,`Genie team: ${teamName}`,"pending",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});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`)}return{created:teamWindow.created,session,window:windowName}}var init_team_auto_spawn=__esm(()=>{init_session();init_claude_native_teams();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}async function checkInboxes(deps=defaultDeps){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}try{await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey,0),spawned.push(teamName)}catch(err){let newCount=failures+1;spawnFailures.set(sessionKey,newCount);let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`)}}return spawned}function startInboxWatcher(deps=defaultDeps){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,spawnFailures;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map});var exports_msg={};__export(exports_msg,{registerSendInboxCommands:()=>registerSendInboxCommands,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile3}from"fs/promises";import{homedir as homedir17}from"os";import{join as join26}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??join26(homedir17(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join26(configDir,"teams",sanitized,"config.json");try{let raw=await readFile3(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 teamManager=await getTeamManager(),teamName=teamContext??process.env.GENIE_TEAM;if(teamName){let config=await teamManager.getTeam(teamName);if(config?.leader)return config.leader}return"team-lead"}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function resolveSenderTeams(teams,sender){let senderTeams=teams.filter((t)=>t.members.includes(sender));if(sender==="team-lead"||teams.some((t)=>t.leader===sender)){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!senderTeams.some((t)=>t.name===leaderTeam.name))senderTeams=[...senderTeams,leaderTeam]}}return senderTeams}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient==="team-lead"||recipient===team.leader)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 resolveTeamName(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(`
1030
+ ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team-lead)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").addHelpText("after",`
1031
+ Examples:
1032
+ genie send 'start task #3' --to engineer # Message a specific agent
1033
+ genie send 'status update' --to team-lead # Report to team lead
1034
+ genie send 'deploy ready' --team my-feature # Message within team context`).action(async(body,options)=>{try{await handleSend(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts3=await getTaskService(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),teamName=await resolveTeamName(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let inbox2=program2.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
1035
+ `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result},warn:(msg)=>console.log(msg)}),shutdown2=()=>{console.log(`
1036
+ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _registry,_taskService,_teamManager,_mailbox;var init_msg=()=>{};function buildLayoutCommand(windowTarget,mode="mosaic"){return`select-layout -t '${windowTarget}' ${mode==="vertical"?"even-horizontal":"tiled"}`}function resolveLayoutMode(layoutFlag){if(layoutFlag==="vertical")return"vertical";return"mosaic"}var exports_claude_logs={};__export(exports_claude_logs,{projectPathToHash:()=>projectPathToHash,parseLogEntry:()=>parseLogEntry,listSessions:()=>listSessions2,findClaudeProjectDir:()=>findClaudeProjectDir,findActiveSession:()=>findActiveSession,claudeTranscriptProvider:()=>claudeTranscriptProvider,claudeEntryToTranscript:()=>claudeEntryToTranscript});import{access,readFile as readFile4,readdir as readdir4,stat as stat3}from"fs/promises";import{join as join27}from"path";function projectPathToHash(projectPath){let normalized=projectPath.replace(/\/+$/,"");if(!normalized)normalized="/";return normalized.replace(/[/.]/g,"-")}function getClaudeDir(){return join27(process.env.HOME||"",".claude")}function getProjectsDir(claudeDir){return join27(claudeDir||getClaudeDir(),"projects")}async function findClaudeProjectDir(projectPath,claudeDir){let projectsDir=getProjectsDir(claudeDir),expectedHash=projectPathToHash(projectPath);try{await access(projectsDir);let projectDir=join27(projectsDir,expectedHash);return await access(projectDir),projectDir}catch{return null}}async function listSessions2(projectDir){let indexPath=join27(projectDir,"sessions-index.json");try{let content=await readFile4(indexPath,"utf-8");return JSON.parse(content).entries}catch{return await scanForSessions(projectDir)}}async function scanForSessions(projectDir){let sessions=[];try{let entries=await readdir4(projectDir);for(let entry of entries)if(entry.endsWith(".jsonl")&&!entry.startsWith(".")){let filePath=join27(projectDir,entry),stats=await stat3(filePath),sessionId=entry.replace(".jsonl","");sessions.push({sessionId,fullPath:filePath,fileMtime:stats.mtimeMs,messageCount:0,created:stats.birthtime.toISOString(),modified:stats.mtime.toISOString(),projectPath:"",isSidechain:!1})}}catch{}return sessions}async function findActiveSession(projectDir){let sessions=await listSessions2(projectDir);if(sessions.length===0)return null;return sessions.sort((a,b2)=>{let timeA=new Date(a.modified).getTime();return new Date(b2.modified).getTime()-timeA}),sessions[0]}async function findSessionById(projectDir,sessionId){return(await listSessions2(projectDir)).find((session)=>session.sessionId===sessionId)??null}function extractToolCalls(content){let toolCalls=[];for(let item of content)if(item.type==="tool_use")toolCalls.push({id:item.id||"",name:item.name||"",input:item.input||{}});return toolCalls.length>0?toolCalls:void 0}function populateAssistantFields(entry,raw){if(raw.type!=="assistant")return;if(Array.isArray(raw.message.content))entry.toolCalls=extractToolCalls(raw.message.content);if(raw.message.model)entry.model=raw.message.model;if(raw.message.usage)entry.usage=raw.message.usage}function parseLogEntry(line){if(!line||!line.trim())return null;try{let raw=JSON.parse(line);if(!raw.type)return null;let entry={type:raw.type,sessionId:raw.sessionId||"",uuid:raw.uuid||"",parentUuid:raw.parentUuid||null,timestamp:raw.timestamp||"",cwd:raw.cwd||"",gitBranch:raw.gitBranch,version:raw.version,raw};if(raw.message)entry.message={role:raw.message.role,content:raw.message.content},populateAssistantFields(entry,raw);if(raw.data)entry.data=raw.data;return entry}catch{return null}}async function readLogFile(logPath){let entries=[];try{let lines=(await readFile4(logPath,"utf-8")).split(`
1037
+ `);for(let line of lines){let entry=parseLogEntry(line);if(entry)entries.push(entry)}}catch{}return entries}async function findLogsForWorkspace(workspacePath,claudeDir){let projectDir=await findClaudeProjectDir(workspacePath,claudeDir);if(!projectDir)return null;let session=await findActiveSession(projectDir);if(!session)return null;return{projectDir,session}}async function getLogsForPane(paneWorkdir,claudeDir){let result=await findLogsForWorkspace(paneWorkdir,claudeDir);if(!result)return null;return{logPath:result.session.fullPath,session:result.session,projectDir:result.projectDir}}function extractText(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"&&"text"in item)parts.push(String(item.text));return parts.join(" ")}function convertAssistant(entry,base){let results=[],text=entry.message?extractText(entry.message.content):"";if(text)results.push({...base,role:"assistant",timestamp:entry.timestamp,text,usage:entry.usage?{input:entry.usage.input_tokens,output:entry.usage.output_tokens}:void 0});if(entry.toolCalls)for(let tc of entry.toolCalls)results.push({...base,role:"tool_call",timestamp:entry.timestamp,text:`${tc.name}: ${JSON.stringify(tc.input).slice(0,200)}`,toolCall:{id:tc.id,name:tc.name,input:tc.input}});return results}function claudeEntryToTranscript(entry){let base={provider:"claude",model:entry.model,raw:entry.raw};if(entry.type==="user"&&entry.message){let text=extractText(entry.message.content);return text?[{...base,role:"user",timestamp:entry.timestamp,text}]:[]}if(entry.type==="assistant")return convertAssistant(entry,base);if(entry.type==="system"||entry.type==="progress"){let text=entry.message?extractText(entry.message.content):"";return text?[{...base,role:"system",timestamp:entry.timestamp,text}]:[]}return[]}var claudeTranscriptProvider;var init_claude_logs=__esm(()=>{claudeTranscriptProvider={async discoverLogPath(worker){let workspacePath=worker.worktree||worker.repoPath,projectDir=await findClaudeProjectDir(workspacePath);if(!projectDir)return null;if(worker.claudeSessionId){let directPath=join27(projectDir,`${worker.claudeSessionId}.jsonl`);try{return await access(directPath),directPath}catch{let session=await findSessionById(projectDir,worker.claudeSessionId);if(session?.fullPath)return session.fullPath}}return(await getLogsForPane(workspacePath))?.logPath??null},async readEntries(logPath){return(await readLogFile(logPath)).flatMap(claudeEntryToTranscript)}}});class AppPtyProvider{name="app-pty";transport="process";buildSpawnCommand(ctx){let params=spawnContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let{access:access2,readdir:readdir5}=await import("fs/promises"),{join:join28}=await import("path"),claudeDir=join28(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return{sessionId}}let jsonlName=`${sessionId}.jsonl`;if(executor.repoPath||executor.worktree){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),path2=executor.repoPath??executor.worktree,hash=projectPathToHash2(path2),candidate=join28(claudeDir,hash,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}try{let dirs=await readdir5(claudeDir);for(let dir of dirs){let candidate=join28(claudeDir,dir,jsonlName);try{return await access2(candidate),{sessionId,logPath:candidate}}catch{}}}catch{}return{sessionId}}async detectState(executor){if(!executor.pid)return"terminated";try{process.kill(executor.pid,0);let metadata=executor.metadata;if(metadata.appPtyState&&typeof metadata.appPtyState==="string")return metadata.appPtyState;return"running"}catch{return"terminated"}}async terminate(executor){if(!executor.pid)return;try{process.kill(executor.pid,"SIGTERM"),await new Promise((r)=>setTimeout(r,500))}catch{}try{process.kill(executor.pid,0),process.kill(executor.pid,"SIGKILL")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams(ctx),launch=buildClaudeCommand(params);return{...launch,env:{...launch.env,GENIE_APP_PTY:"true"}}}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
1038
+ SELECT a.custom_name, a.team
1039
+ FROM executors e
1040
+ JOIN agents a ON e.agent_id = a.id
1041
+ WHERE e.id = ${executorId}
1042
+ LIMIT 1
1043
+ `;if(rows.length===0)return;let{custom_name:agentName,team:teamName}=rows[0];if(!agentName||!teamName)return;let{writeNativeInbox:writeNativeInbox2}=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams));await writeNativeInbox2(teamName,agentName,{from:"system",text:message.text,summary:message.text.slice(0,120),timestamp:new Date().toISOString(),color:"red",read:!1})}}function spawnContextToParams(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,sessionId:ctx.sessionId,systemPromptFile:ctx.systemPromptFile,systemPrompt:ctx.systemPrompt,promptMode:ctx.promptMode,initialPrompt:ctx.initialPrompt,name:ctx.name,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}function resumeContextToParams(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,resume:ctx.claudeSessionId,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}var init_app_pty=__esm(()=>{init_provider_adapters()});class ClaudeCodeProvider{name="claude-code";transport="tmux";buildSpawnCommand(ctx){let params=spawnContextToParams2(ctx);return buildClaudeCommand(params)}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;let logPath=await findSessionLogPath(sessionId,executor.repoPath??executor.worktree);return{sessionId,logPath:logPath??void 0}}async detectState(executor){let paneId=executor.tmuxPaneId;if(!paneId)return"terminated";try{let{capturePaneContent:capturePaneContent2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),content=await capturePaneContent2(paneId,50);if(!content||!content.trim())return"idle";let{detectState:detect}=await Promise.resolve().then(() => (init_state_detector(),exports_state_detector)),result=detect(content);return mapDetectedState(result.type)}catch{return"terminated"}}async terminate(executor){let{executeTmux:executeTmux3}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),paneId=executor.tmuxPaneId;if(paneId){try{await executeTmux3(`send-keys -t '${paneId}' C-c`),await new Promise((r)=>setTimeout(r,500))}catch{}try{await executeTmux3(`kill-pane -t '${paneId}'`)}catch{}}if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!0}buildResumeCommand(ctx){let params=resumeContextToParams2(ctx);return buildClaudeCommand(params)}async deliverMessage(executorId,message){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),rows=await(await getConnection2())`
1044
+ SELECT a.custom_name, a.team
1045
+ FROM executors e
1046
+ JOIN agents a ON e.agent_id = a.id
1047
+ WHERE e.id = ${executorId}
1048
+ LIMIT 1
1049
+ `;if(rows.length===0)return;let{custom_name:agentName,team:teamName}=rows[0];if(!agentName||!teamName)return;let{writeNativeInbox:writeNativeInbox2}=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams));await writeNativeInbox2(teamName,agentName,{from:"system",text:message.text,summary:message.text.slice(0,120),timestamp:new Date().toISOString(),color:"red",read:!1})}}function spawnContextToParams2(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,sessionId:ctx.sessionId,systemPromptFile:ctx.systemPromptFile,systemPrompt:ctx.systemPrompt,promptMode:ctx.promptMode,initialPrompt:ctx.initialPrompt,name:ctx.name,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}function resumeContextToParams2(ctx){return{provider:"claude",team:ctx.team,role:ctx.role,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs,model:ctx.model,resume:ctx.claudeSessionId,nativeTeam:ctx.nativeTeam,otelPort:ctx.otelPort,otelLogPrompts:ctx.otelLogPrompts,otelWishSlug:ctx.otelWishSlug}}async function findSessionLogPath(sessionId,projectPath){let{access:access2,readdir:readdir5}=await import("fs/promises"),{join:join28}=await import("path"),claudeDir=join28(process.env.HOME||"",".claude","projects");try{await access2(claudeDir)}catch{return null}let jsonlName=`${sessionId}.jsonl`;if(projectPath){let{projectPathToHash:projectPathToHash2}=await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs)),hash=projectPathToHash2(projectPath),candidate=join28(claudeDir,hash,jsonlName);try{return await access2(candidate),candidate}catch{}}try{let dirs=await readdir5(claudeDir);for(let dir of dirs){let candidate=join28(claudeDir,dir,jsonlName);try{return await access2(candidate),candidate}catch{}}}catch{}return null}function mapDetectedState(detectedType){switch(detectedType){case"idle":return"idle";case"working":case"tool_use":return"working";case"permission":return"permission";case"question":return"question";case"error":return"error";case"complete":return"done";case"unknown":return"running"}}var init_claude_code=__esm(()=>{init_provider_adapters()});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty)});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext});import{exec as exec2}from"child_process";import{readFile as readFile5}from"fs/promises";import{join as join28}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let teamConfig=await getTeam(team);if(teamConfig?.nativeTeamParentSessionId)return teamConfig.nativeTeamParentSessionId;return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role),currentExec=await getCurrentExecutor(agentIdentity.id);if(currentExec&&currentExec.state!=="terminated"&&currentExec.state!=="done"){let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentity.id)}let pid=null;try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);if(parsed>0)pid=parsed}catch{}let executorTransport=template.provider==="codex"?"api":"tmux",executor=await createExecutor(agentIdentity.id,template.provider,executorTransport,{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),session=(await getTeam(team))?.tmuxSessionName??await resolveRepoSession(repoPath)??await getCurrentSessionName()??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,claudeSessionId:effectiveSessionId,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude){await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget="team-lead";try{let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig2=await getTeam3(team);if(teamConfig2?.leader)leaderInboxTarget=teamConfig2.leader}catch{}await writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await findAnyGroupByAssignee(workerId,repoPath)??await findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join28(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile5(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
1050
+ ${groupSection}`:"","",gitLog?`Last git log:
1051
+ ${gitLog}`:"","",gitStatus?`Uncommitted changes:
1052
+ ${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
1053
+ `);await send(repoPath,"genie",workerId,resumePrompt)}catch{}}var execAsync;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_claude_native_teams();init_executor_registry();init_mailbox();init_provider_adapters();init_registry();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2)});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000;var init_spawn_command=__esm(()=>{init_orchestrator();init_tmux()});var exports_inject={};__export(exports_inject,{isTeamHooked:()=>isTeamHooked,injectTeamHooks:()=>injectTeamHooks});import{existsSync as existsSync23}from"fs";import{mkdir as mkdir4,readFile as readFile6,writeFile as writeFile2}from"fs/promises";import{homedir as homedir18}from"os";import{join as join29}from"path";import{fileURLToPath}from"url";function escapeShellArg2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function buildDispatchCommand(){let entrypoint=fileURLToPath(new URL("../genie.ts",import.meta.url));if(!existsSync23(entrypoint))return"genie hook dispatch";let bun=process.execPath||"bun";return`${escapeShellArg2(bun)} run ${escapeShellArg2(entrypoint)} hook dispatch`}function isGenieDispatchCommand(command){return typeof command==="string"&&/(?:^|\s)hook\s+dispatch(?:\s|$)/.test(command)}function claudeConfigDir2(){return process.env.CLAUDE_CONFIG_DIR??join29(homedir18(),".claude")}function teamSettingsPath(teamName){let sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase();return join29(claudeConfigDir2(),"teams",sanitized,"settings.json")}function buildHooksConfig(){let hooks={},dispatchCommand=buildDispatchCommand();for(let event of DISPATCHED_EVENTS)hooks[event]=[{matcher:"*",hooks:[{type:"command",command:dispatchCommand,timeout:DISPATCH_TIMEOUT}]}];return hooks}async function injectIntoFile(settingsPath){let settings={};if(existsSync23(settingsPath))try{let content=await readFile6(settingsPath,"utf-8");settings=JSON.parse(content)}catch{}let hooksConfig=buildHooksConfig(),existingHooks=settings.hooks;if(existingHooks){if(DISPATCHED_EVENTS.every((event)=>{let existing=existingHooks[event],desiredCommand=hooksConfig[event][0].hooks[0].command;return existing?.some((m)=>m.hooks?.some((h)=>h.command===desiredCommand))}))return!1}let mergedHooks=existingHooks?{...existingHooks}:{};for(let event of DISPATCHED_EVENTS){let genieEntry=hooksConfig[event][0],existingEntries=(mergedHooks[event]??[]).map((matcher)=>({...matcher,hooks:matcher.hooks?.map((hook)=>isGenieDispatchCommand(hook.command)?{...hook,command:genieEntry.hooks[0].command,timeout:DISPATCH_TIMEOUT}:hook)}));if(!existingEntries.some((m)=>m.hooks?.some((h)=>isGenieDispatchCommand(h.command))))mergedHooks[event]=[...existingEntries,genieEntry];else mergedHooks[event]=existingEntries}settings.hooks=mergedHooks;let dir=join29(settingsPath,"..");return await mkdir4(dir,{recursive:!0}),await writeFile2(settingsPath,JSON.stringify(settings,null,2)),!0}async function injectTeamHooks(teamName){let path2=teamSettingsPath(teamName);return injectIntoFile(path2)}async function isTeamHooked(teamName){let path2=teamSettingsPath(teamName);if(!existsSync23(path2))return!1;try{let content=await readFile6(path2,"utf-8"),hooks=JSON.parse(content).hooks;if(!hooks)return!1;return DISPATCHED_EVENTS.every((event)=>{return hooks[event]?.some((m)=>m.hooks?.some((h)=>isGenieDispatchCommand(h.command)))})}catch{return!1}}var DISPATCH_TIMEOUT=15;var init_inject=__esm(()=>{init_types3()});var exports_idle_timeout={};__export(exports_idle_timeout,{suspendWorker:()=>suspendWorker,getIdleTimeoutMs:()=>getIdleTimeoutMs,checkIdleWorkers:()=>checkIdleWorkers,WATCHDOG_POLL_INTERVAL_MS:()=>WATCHDOG_POLL_INTERVAL_MS});function getIdleTimeoutMs(){let env=process.env.GENIE_IDLE_TIMEOUT_MS;if(env!==void 0){if(env==="")return DEFAULT_IDLE_TIMEOUT_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return DEFAULT_IDLE_TIMEOUT_MS}async function suspendWorker(executorId,deps=defaultDeps2){let executor=(await deps.listExecutors()).find((e)=>e.id===executorId);if(!executor)return!1;if(executor.state==="terminated")return!0;if(executor.tmuxPaneId)try{await deps.executeTmux(`kill-pane -t '${executor.tmuxPaneId}'`)}catch{}return await deps.terminateExecutor(executorId),!0}async function checkIdleWorkers(deps=defaultDeps2){let timeoutMs=getIdleTimeoutMs();if(timeoutMs===0)return[];let executors=await deps.listExecutors(),suspended=[];for(let e of executors){if(e.state!=="idle")continue;if(Date.now()-new Date(e.updatedAt).getTime()<timeoutMs)continue;if(e.tmuxPaneId){if(!await deps.isPaneAlive(e.tmuxPaneId)){await deps.terminateExecutor(e.id),suspended.push(e.id);continue}}if(await suspendWorker(e.id,deps))suspended.push(e.id)}return suspended}var defaultDeps2,DEFAULT_IDLE_TIMEOUT_MS=1800000,WATCHDOG_POLL_INTERVAL_MS=60000;var init_idle_timeout=__esm(()=>{init_executor_registry();init_tmux();defaultDeps2={listExecutors:()=>listExecutors(),terminateExecutor,updateExecutorState,executeTmux:executeTmux2,isPaneAlive}});var exports_agents={};__export(exports_agents,{handleWorkerStop:()=>handleWorkerStop,handleWorkerSpawn:()=>handleWorkerSpawn,handleWorkerResume:()=>handleWorkerResume,handleWorkerKill:()=>handleWorkerKill,handleLsCommand:()=>handleLsCommand,buildResumeContext:()=>buildResumeContext,buildInitialSplitWindowCommand:()=>buildInitialSplitWindowCommand});async function resolveTeamLeaderName(teamNameOrDefault){try{return(await getTeam(teamNameOrDefault))?.leader||"team-lead"}catch{return"team-lead"}}function isRelayAlive(pidFile){let{readFileSync:readFileSync11,existsSync:existsSync24}=__require("fs");if(!existsSync24(pidFile))return!1;try{let pid=Number.parseInt(readFileSync11(pidFile,"utf-8").trim());if(pid>0)return process.kill(pid,0),!0}catch{}return!1}async function ensureOtelRelay(team){let{writeFileSync:writeFileSync8,mkdirSync:mkdirSync7}=__require("fs"),{join:join30}=__require("path"),{homedir:homedir19}=__require("os"),relayDir=join30(homedir19(),".genie","relay");mkdirSync7(relayDir,{recursive:!0});let pidFile=join30(relayDir,"otel-relay.pid"),scriptFile=join30(relayDir,"otel-relay.mjs");if(isRelayAlive(pidFile))return!0;let inboxDir=join30(process.env.CLAUDE_CONFIG_DIR??join30(homedir19(),".claude"),"teams",team,"inboxes"),leaderInboxName=sanitizeTeamName(await resolveTeamLeaderName(team)),escapedRelayDir=relayDir.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),escapedInboxDir=inboxDir.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),escapedPidFile=pidFile.replace(/\\/g,"\\\\").replace(/'/g,"\\'");try{writeFileSync8(scriptFile,`import { createServer } from 'http';
1054
+ import { execSync } from 'child_process';
1055
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs';
1056
+ import { createHash } from 'crypto';
1057
+ import { join } from 'path';
1058
+
1059
+ const RELAY_DIR = '${escapedRelayDir}';
1060
+ const INBOX_DIR = '${escapedInboxDir}';
1061
+ const INBOX = join(INBOX_DIR, '${leaderInboxName}.json');
1062
+ const PID_FILE = '${escapedPidFile}';
1063
+ const PORT = ${OTEL_RELAY_PORT};
1064
+ const SILENCE_MS = 5000;
1065
+ const BOOTSTRAP_GRACE_MS = 20000; // Hard skip during first 20s after worker appears
1066
+ // After grace period, wait for first idle state (= bootstrap done) before relaying.
1067
+ // This avoids noise from codex reading context files and asking for permission.
1068
+
1069
+ let silenceTimer = null;
1070
+ const lastHashes = new Map(); // workerId \u2192 content hash
1071
+ const workerFirstSeen = new Map(); // workerId \u2192 timestamp (ms)
1072
+ const bootstrapDone = new Set(); // Workers whose bootstrap is complete (stable idle seen)
1073
+ const bootstrapIdleCount = new Map(); // workerId \u2192 consecutive idle poll count during bootstrap
1074
+ const stoppedWorkers = new Set(); // Workers that finished \u2014 no more relays
1075
+
1076
+ // Detect codex state from pane content
1077
+ function detectState(output) {
1078
+ const lines = output.split('\\n').filter(l => l.trim());
1079
+ const tail = lines.slice(-8).join('\\n');
1080
+
1081
+ // Permission prompt \u2014 codex is asking for approval
1082
+ if (/Press enter to confirm or esc to cancel/.test(tail)) return 'permission';
1083
+ if (/Would you like to run/.test(tail)) return 'permission';
1084
+
1085
+ // Working indicators \u2014 check BEFORE idle because the \u203A prompt placeholder
1086
+ // is visible even while codex is actively processing
1087
+ if (/[\u25E6\u25D0\u25D1\u25D2\u25D3\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F]/.test(tail)) return 'working'; // Spinner chars
1088
+ if (/esc to interrupt/.test(tail)) return 'working'; // Active processing hint
1089
+
1090
+ // Idle \u2014 codex prompt waiting for input (\u203A at start of a line near bottom)
1091
+ // The status bar (gpt-5.3-codex...) appears below the prompt
1092
+ if (/^\\s*[>\u203A]\\s/m.test(tail)) return 'idle';
1093
+
1094
+ // Still working
1095
+ return 'working';
1096
+ }
1097
+
1098
+ // Extract meaningful summary from codex pane output
1099
+ function extractSummary(output, state) {
1100
+ const lines = output.split('\\n');
1101
+
1102
+ if (state === 'permission') {
1103
+ // Find the command being requested (lines with $ or \u2718 near the bottom)
1104
+ const tail = lines.slice(-15);
1105
+ // Look for the command line (starts with $ or contains the command after "Run")
1106
+ const cmdLine = tail.reverse().find(l =>
1107
+ /^\\s*\\$\\s/.test(l) || /^\\s*[\u2022\u2718].*(?:Run|run|patch|write|exec)/.test(l)
1108
+ );
1109
+ if (cmdLine) {
1110
+ const cleaned = cmdLine.replace(/^\\s*\\$\\s*/, '').replace(/^\\s*[\u2022\u2718]\\s*/, '').trim().slice(0, 80);
1111
+ return '[needs approval] ' + cleaned;
1112
+ }
1113
+ return '[needs approval]';
1114
+ }
1115
+
1116
+ // Idle state \u2014 find the last codex response (\u2022 prefixed lines)
1117
+ // Work backwards from the idle prompt to find the response block
1118
+ const responseLines = [];
1119
+ let foundPrompt = false;
1120
+ for (let i = lines.length - 1; i >= 0; i--) {
1121
+ const line = lines[i].trim();
1122
+ // Skip empty lines, status bar, and idle prompt
1123
+ if (!line) continue;
1124
+ if (/^[>\u203A]\\s/.test(line)) {
1125
+ if (foundPrompt) break; // Hit the user's input prompt \u2014 stop
1126
+ foundPrompt = true;
1127
+ continue;
1128
+ }
1129
+ if (/gpt-\\d|codex|left\\s*\xB7|^Tip:/.test(line)) continue;
1130
+ // Collect response lines (\u2022 prefixed or continuation)
1131
+ if (foundPrompt || /^[\u2022\u2714\u2718\u2500]/.test(line)) {
1132
+ foundPrompt = true;
1133
+ responseLines.unshift(line);
1134
+ if (responseLines.length >= 3) break; // Enough for summary
1135
+ }
1136
+ }
1137
+
1138
+ if (responseLines.length > 0) {
1139
+ // Clean up the \u2022 prefix for summary
1140
+ const summary = responseLines
1141
+ .map(l => l.replace(/^[\u2022\u2714\u2718]\\s*/, '').trim())
1142
+ .filter(Boolean)
1143
+ .join(' ')
1144
+ .slice(0, 120);
1145
+ return summary || '(idle)';
1146
+ }
1147
+
1148
+ return '(idle)';
1149
+ }
1150
+
1151
+ function relayAll() {
1152
+ let paneFiles;
1153
+ try { paneFiles = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane')); }
1154
+ catch { return; }
1155
+
1156
+ const now = Date.now();
1157
+
1158
+ for (const file of paneFiles) {
1159
+ const workerId = file.replace(/-pane$/, '');
1160
+ if (stoppedWorkers.has(workerId)) continue;
1161
+
1162
+ // Bootstrap grace period \u2014 skip relaying during first 25s
1163
+ if (!workerFirstSeen.has(workerId)) {
1164
+ // Use file mtime as registration time
1165
+ try {
1166
+ const stat = statSync(join(RELAY_DIR, file));
1167
+ workerFirstSeen.set(workerId, stat.mtimeMs);
1168
+ } catch {
1169
+ workerFirstSeen.set(workerId, now);
1170
+ }
1171
+ }
1172
+ const age = now - workerFirstSeen.get(workerId);
1173
+ if (age < BOOTSTRAP_GRACE_MS) continue;
1174
+
1175
+ let paneId;
1176
+ try { paneId = readFileSync(join(RELAY_DIR, file), 'utf-8').trim(); }
1177
+ catch { continue; }
1178
+ if (!paneId || !/^%\\d+$/.test(paneId)) continue;
1179
+
1180
+ let meta = { agent: workerId, color: 'blue' };
1181
+ try {
1182
+ const raw = readFileSync(join(RELAY_DIR, workerId + '-meta'), 'utf-8');
1183
+ meta = JSON.parse(raw);
1184
+ } catch {}
1185
+
1186
+ let output;
1187
+ try {
1188
+ output = execSync(\`tmux -L genie capture-pane -p -t '\${paneId}' -S -80\`, { encoding: 'utf-8' }).trim();
1189
+ } catch {
1190
+ // Pane gone \u2014 final relay if we had previous content
1191
+ const lastContent = lastHashes.get(workerId + ':content');
1192
+ if (lastContent) {
1193
+ const summary = extractSummary(lastContent, 'idle');
1194
+ writeInbox(meta, lastContent, '[finished] ' + summary);
1195
+ }
1196
+ stoppedWorkers.add(workerId);
1197
+ continue;
1198
+ }
1199
+ if (!output) continue;
1200
+
1201
+ // Detect state \u2014 only relay on idle or permission
1202
+ const state = detectState(output);
1203
+ if (state === 'working') continue;
1204
+
1205
+ // Bootstrap detection: require 2 consecutive idle polls before marking done.
1206
+ // Permission prompts are ALWAYS relayed (they block progress).
1207
+ // Brief idle states between actions are skipped (codex shows \u203A between tasks).
1208
+ if (!bootstrapDone.has(workerId)) {
1209
+ if (state === 'idle') {
1210
+ const count = (bootstrapIdleCount.get(workerId) || 0) + 1;
1211
+ bootstrapIdleCount.set(workerId, count);
1212
+ if (count >= 2) {
1213
+ bootstrapDone.add(workerId);
1214
+ // Fall through to relay this stable idle (bootstrap complete)
1215
+ } else {
1216
+ continue; // First idle poll \u2014 might be brief between actions
1217
+ }
1218
+ } else if (state === 'permission') {
1219
+ bootstrapIdleCount.set(workerId, 0); // Reset \u2014 codex is still working
1220
+ // Permission during bootstrap \u2014 falls through to relay
1221
+ } else {
1222
+ bootstrapIdleCount.set(workerId, 0); // Reset on working state
1223
+ continue;
1224
+ }
1225
+ }
1226
+
1227
+ // Skip if content unchanged
1228
+ const hash = createHash('md5').update(output).digest('hex');
1229
+ if (lastHashes.get(workerId) === hash) continue;
1230
+ lastHashes.set(workerId, hash);
1231
+ lastHashes.set(workerId + ':content', output);
1232
+
1233
+ const summary = extractSummary(output, state);
1234
+ writeInbox(meta, output, summary);
1235
+ }
1236
+ }
1237
+
1238
+ function writeInbox(meta, text, summary) {
1239
+ mkdirSync(INBOX_DIR, { recursive: true });
1240
+ let messages = [];
1241
+ try { messages = JSON.parse(readFileSync(INBOX, 'utf-8')); } catch {}
1242
+ // Trim old read messages to prevent inbox bloat \u2014 keep only last 5 read + all unread
1243
+ const unread = messages.filter(m => !m.read);
1244
+ const read = messages.filter(m => m.read);
1245
+ messages = [...read.slice(-5), ...unread];
1246
+ messages.push({
1247
+ from: meta.agent,
1248
+ text,
1249
+ summary,
1250
+ timestamp: new Date().toISOString(),
1251
+ color: meta.color,
1252
+ read: false,
1253
+ });
1254
+ writeFileSync(INBOX, JSON.stringify(messages, null, 2));
1255
+ }
1256
+
1257
+ // Reset in_progress groups assigned to a dead worker and notify team-lead (PG-backed)
1258
+ async function handleDeadWorkerLiveness(workerId, meta) {
1259
+ if (!meta || !meta.repoPath) return;
1260
+ try {
1261
+ const wishState = await import('../lib/wish-state.js');
1262
+ // Try both workerId and agent name
1263
+ const match =
1264
+ (await wishState.findAnyGroupByAssignee(workerId, meta.repoPath)) ??
1265
+ (meta.agent ? await wishState.findAnyGroupByAssignee(meta.agent, meta.repoPath) : null);
1266
+ if (!match) return;
1267
+
1268
+ await wishState.resetGroup(match.slug, match.groupName, meta.repoPath);
1269
+ const agentLabel = meta.agent || workerId;
1270
+ writeInbox(
1271
+ { agent: 'genie-relay', color: 'red' },
1272
+ 'Agent ' + agentLabel + ' crashed while working on group ' + match.groupName + ' of wish ' + match.slug + '. Group has been reset to ready for retry.',
1273
+ '[crash] ' + agentLabel + ' crashed on ' + match.slug + '#' + match.groupName + '. Reset to ready.'
1274
+ );
1275
+ } catch {}
1276
+ }
1277
+
1278
+ // Clean up dead panes every 30s
1279
+ setInterval(async () => {
1280
+ let paneFiles;
1281
+ try { paneFiles = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane')); }
1282
+ catch { return; }
1283
+ for (const file of paneFiles) {
1284
+ try {
1285
+ const paneId = readFileSync(join(RELAY_DIR, file), 'utf-8').trim();
1286
+ if (!/^%\\d+$/.test(paneId)) throw new Error('invalid pane id');
1287
+ execSync(\`tmux -L genie display -t '\${paneId}' -p '#{pane_id}'\`, { stdio: 'ignore' });
1288
+ } catch {
1289
+ const workerId = file.replace(/-pane$/, '');
1290
+ // Read meta before cleanup (for liveness reset)
1291
+ let meta = null;
1292
+ try { meta = JSON.parse(readFileSync(join(RELAY_DIR, workerId + '-meta'), 'utf-8')); } catch {}
1293
+ for (const suffix of ['-pane', '-meta']) {
1294
+ try { unlinkSync(join(RELAY_DIR, workerId + suffix)); } catch {}
1295
+ }
1296
+ lastHashes.delete(workerId);
1297
+ workerFirstSeen.delete(workerId);
1298
+ bootstrapDone.delete(workerId);
1299
+ stoppedWorkers.add(workerId);
1300
+ // Liveness check: reset in_progress groups assigned to this dead worker
1301
+ await handleDeadWorkerLiveness(workerId, meta);
1302
+ }
1303
+ }
1304
+ try {
1305
+ const remaining = readdirSync(RELAY_DIR).filter(f => f.endsWith('-pane'));
1306
+ if (remaining.length === 0) process.exit(0);
1307
+ } catch {}
1308
+ }, 30000);
1309
+
1310
+ const server = createServer((req, res) => {
1311
+ const chunks = [];
1312
+ req.on('data', (chunk) => chunks.push(chunk));
1313
+ req.on('end', () => {
1314
+ if (silenceTimer) clearTimeout(silenceTimer);
1315
+ silenceTimer = setTimeout(() => relayAll(), SILENCE_MS);
1316
+ res.writeHead(200);
1317
+ res.end();
1318
+ });
1319
+ });
1320
+
1321
+ server.listen(PORT, '127.0.0.1', () => {
1322
+ writeFileSync(PID_FILE, String(process.pid));
1323
+ });
1324
+
1325
+ process.on('SIGTERM', () => { server.close(); process.exit(0); });
1326
+ process.on('SIGINT', () => { server.close(); process.exit(0); });
1327
+ `,{mode:420});let{spawn:spawnChild}=__require("child_process");spawnChild("node",[scriptFile],{detached:!0,stdio:"ignore"}).unref();for(let i2=0;i2<30;i2++)if(await new Promise((r)=>setTimeout(r,100)),isRelayAlive(pidFile))return!0;return!1}catch{return!1}}async function generateWorkerId2(team,role){let base=role?`${team}-${role}`:team;if(!(await list()).some((w)=>w.id===base))return base;let suffix=crypto.randomUUID().slice(0,8);return`${base}-${suffix}`}async function capturePanePid(paneId){if(paneId==="inline")return null;try{let{execSync:execSync6}=__require("child_process"),output=execSync6(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),pid=Number.parseInt(output,10);return pid>0?pid:null}catch{return null}}function resolveExecutorTransport(provider,spawnTransport){if(provider==="codex")return"api";return spawnTransport==="inline"?"process":"tmux"}async function terminateActiveExecutorWithCleanup(agentIdentityId){try{let currentExec=await getCurrentExecutor(agentIdentityId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let provider=getProvider(currentExec.provider);if(provider)try{await provider.terminate(currentExec)}catch{}await terminateActiveExecutor(agentIdentityId)}catch{}}async function createAndLinkExecutor(agentIdentityId,provider,transport,opts){try{let executor=await createExecutor(agentIdentityId,provider,transport,opts);return await setCurrentExecutor(agentIdentityId,executor.id),executor.id}catch{return null}}async function registerSpawnWorker(ctx,paneId,windowInfo){let nt=ctx.validated.nativeTeam,workerEntry={id:ctx.workerId,paneId,session:ctx.validated.team,provider:ctx.validated.provider,transport:ctx.transport,role:ctx.validated.role,skill:ctx.validated.skill,team:ctx.validated.team,worktree:null,startedAt:ctx.now,state:"spawning",lastStateChange:ctx.now,repoPath:ctx.cwd,claudeSessionId:ctx.claudeSessionId,nativeTeamEnabled:nt?.enabled??!1,nativeAgentId:`${ctx.agentName}@${ctx.validated.team}`,nativeColor:nt?.color??ctx.spawnColor,parentSessionId:nt?.parentSessionId??ctx.parentSessionId,window:windowInfo?.windowName,windowName:windowInfo?.windowName,windowId:windowInfo?.windowId,autoResume:ctx.autoResume===!1?!1:void 0,resumeAttempts:0};await register(workerEntry);let role=ctx.validated.role??ctx.agentName;if(role!=="council")try{await hireAgent(ctx.validated.team,role)}catch{}return workerEntry}async function notifySpawnJoin(ctx,paneId){let nt=ctx.validated.nativeTeam;if(!nt?.enabled)return;await registerNativeMember(ctx.validated.team,{agentName:ctx.agentName,agentType:nt.agentType??ctx.validated.role??"general-purpose",color:nt.color??ctx.spawnColor??"blue",tmuxPaneId:paneId,cwd:ctx.cwd,planModeRequired:nt.planModeRequired});let leaderName=await resolveTeamLeaderName(ctx.validated.team);await writeNativeInbox(ctx.validated.team,leaderName,{from:ctx.agentName,text:`Worker ${ctx.agentName} (${ctx.validated.provider}) joined team ${ctx.validated.team}. cwd: ${ctx.cwd}. Ready for tasks.`,summary:`${ctx.agentName} (${ctx.validated.provider}) joined`,timestamp:new Date().toISOString(),color:nt.color??ctx.spawnColor??"blue",read:!1})}function registerOtelRelayPane(workerId,paneId,agentName,spawnColor,repoPath){let{writeFileSync:wfs}=__require("fs"),{join:pjoin}=__require("path"),{homedir:hdir}=__require("os"),rd=pjoin(hdir(),".genie","relay");wfs(pjoin(rd,`${workerId}-pane`),paneId),wfs(pjoin(rd,`${workerId}-meta`),JSON.stringify({agent:agentName,color:spawnColor,repoPath}))}function printSpawnInfo(ctx,paneId,workerEntry){let nt=ctx.validated.nativeTeam;if(console.log(`Agent "${ctx.workerId}" spawned.`),console.log(` Provider: ${ctx.launch.provider}`),console.log(` Command: ${ctx.fullCommand}`),console.log(` Team: ${ctx.validated.team}`),console.log(` Pane: ${paneId}`),ctx.validated.role)console.log(` Role: ${ctx.validated.role}`);if(ctx.validated.skill)console.log(` Skill: ${ctx.validated.skill}`);if(workerEntry.claudeSessionId)console.log(` Session: ${workerEntry.claudeSessionId}`);if(console.log(` Layout: ${ctx.layoutMode}`),nt?.enabled)console.log(" Native: enabled"),console.log(` AgentID: ${workerEntry.nativeAgentId}`),console.log(` Color: ${nt.color}`);if(ctx.otelRelayActive)console.log(` OTel: relay on port ${OTEL_RELAY_PORT}`)}function shellQuote2(arg){return`'${arg.replace(/'/g,"'\\''")}'`}function writeTmuxLaunchScript(workerId,fullCommand){let{chmodSync:chmodSync2,mkdirSync:mkdirSync7,writeFileSync:writeFileSync8}=__require("fs"),{join:join30}=__require("path"),{homedir:homedir19}=__require("os"),dir=join30(homedir19(),".genie","spawn-scripts");mkdirSync7(dir,{recursive:!0});let safeId=workerId.replace(/[^a-zA-Z0-9._-]/g,"-"),scriptPath=join30(dir,`${safeId}-${Date.now().toString(36)}.sh`);return writeFileSync8(scriptPath,`#!/bin/sh
1328
+ exec ${fullCommand}
1329
+ `,{mode:448}),chmodSync2(scriptPath,448),scriptPath}function buildInitialSplitWindowCommand(windowId,cwd,fullCommand){let cwdFlag=cwd?` -c ${shellQuote2(cwd)}`:"";return genieTmuxCmd(`split-window -d -t ${shellQuote2(windowId)}${cwdFlag} -P -F '#{pane_id}' ${shellQuote2(fullCommand)}`)}async function resolveSpawnTeamWindow(team,cwd,sessionOverride){if(!team)return null;try{let sessionName=sessionOverride;if(!sessionName)sessionName=(await getTeam(team))?.tmuxSessionName;if(!sessionName)sessionName=await resolveRepoSession(cwd);if(!sessionName)sessionName=team;return await ensureTeamWindow(sessionName,team,cwd)}catch(err){return console.warn(`Warning: could not ensure team window for "${team}": ${err instanceof Error?err.message:err}`),null}}async function autoConfirmTrustPrompt(paneId){let{execSync:execSync6}=__require("child_process"),maxWaitMs=15000,pollMs=500,start=Date.now();while(Date.now()-start<15000){await new Promise((r)=>setTimeout(r,500));let content;try{content=execSync6(genieTmuxCmd(`capture-pane -t '${paneId}' -p`),{encoding:"utf-8"})}catch{return}if(content.includes("trust this folder")||content.includes("Quick safety check")){try{execSync6(genieTmuxCmd(`send-keys -t '${paneId}' Enter`),{encoding:"utf-8"})}catch{}return}if(content.includes("Claude Code")||content.includes("\u276F")||content.includes("Churning"))return}}function createTmuxPane(ctx,teamWindow){let{execSync:execSync6}=__require("child_process"),useLaunchScript=ctx.validated.provider==="claude"&&Boolean(ctx.validated.nativeTeam?.enabled),tmuxCommand=useLaunchScript?shellQuote2(writeTmuxLaunchScript(ctx.workerId,ctx.fullCommand)):shellQuote2(ctx.fullCommand),tmuxPrefix=genieTmuxCmd("");if(teamWindow?.created){let cwdFlag2=ctx.cwd?` -c ${shellQuote2(ctx.cwd)}`:"",paneId=execSync6(`${tmuxPrefix}split-window -d -t ${shellQuote2(teamWindow.windowId)}${cwdFlag2} -P -F '#{pane_id}' ${tmuxCommand}`,{encoding:"utf-8"}).trim();try{execSync6(genieTmuxCmd(`kill-pane -t '${teamWindow.paneId}'`),{stdio:"ignore"})}catch{}return paneId}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",cwdFlag=ctx.cwd?`-c '${ctx.cwd}'`:"";if(useLaunchScript){let splitCmd2=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' ${tmuxCommand}`;return execSync6(splitCmd2,{encoding:"utf-8"}).trim()}let escapedCmd=ctx.fullCommand.replace(/'/g,"'\\''"),splitCmd=`${tmuxPrefix}split-window -d ${splitTarget} ${cwdFlag} -P -F '#{pane_id}' '${escapedCmd}'`;return execSync6(splitCmd,{encoding:"utf-8"}).trim()}async function applySpawnLayout(ctx,teamWindow){let{execSync:execSync6}=__require("child_process"),session=await getCurrentSessionName()??ctx.validated.team,layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{execSync6(genieTmuxCmd(buildLayoutCommand(layoutTarget,ctx.layoutMode)),{stdio:"ignore"})}catch{}}async function createTmuxExecutor(ctx,paneId,pid,teamWindow){if(!ctx.agentIdentityId||!ctx.executorId)return;await createAndLinkExecutor(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport(ctx.validated.provider,"tmux"),{id:ctx.executorId,pid,tmuxSession:ctx.validated.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd,paneColor:ctx.spawnColor})}async function finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry){if(ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(await saveTemplate({id:ctx.validated.role??ctx.workerId,provider:ctx.validated.provider,team:ctx.validated.team,role:ctx.validated.role,skill:ctx.validated.skill,cwd:ctx.cwd,extraArgs:ctx.extraArgs,nativeTeamEnabled:workerEntry.nativeTeamEnabled,lastSpawnedAt:new Date().toISOString()}),ctx.otelRelayActive&&paneId!=="%0")registerOtelRelayPane(ctx.workerId,paneId,ctx.agentName,ctx.spawnColor,ctx.cwd);if(teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`);printSpawnInfo(ctx,paneId,workerEntry)}async function awaitAgentReadiness(paneId){if(paneId==="inline")return;let result=await waitForAgentReady(paneId);if(result.ready)console.log(` \u2713 Agent ready (${(result.elapsedMs/1000).toFixed(1)}s)`);else console.log(` \u26A0 Agent readiness timeout (${Math.round(result.elapsedMs/1000)}s) \u2014 proceeding anyway`)}async function launchTmuxSpawn(ctx){let teamWindow=ctx.spawnIntoCurrentWindow?null:await resolveSpawnTeamWindow(ctx.validated.team,ctx.cwd,ctx.sessionOverride),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){return console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}let pid=await capturePanePid(paneId);if(await createTmuxExecutor(ctx,paneId,pid,teamWindow),await applySpawnLayout(ctx,teamWindow),ctx.validated.provider==="claude")await autoConfirmTrustPrompt(paneId);let workerEntry=await registerSpawnWorker(ctx,paneId,teamWindow);return await notifySpawnJoin(ctx,paneId),await finalizeTmuxSpawn(ctx,paneId,teamWindow,workerEntry),await awaitAgentReadiness(paneId),paneId}async function launchInlineSpawn(ctx){let nt=ctx.validated.nativeTeam,paneId="inline";if(ctx.agentIdentityId&&ctx.executorId)await createAndLinkExecutor(ctx.agentIdentityId,ctx.validated.provider,resolveExecutorTransport(ctx.validated.provider,"inline"),{id:ctx.executorId,claudeSessionId:ctx.claudeSessionId??null,state:"spawning",repoPath:ctx.cwd});let workerEntry=await registerSpawnWorker(ctx,"inline");if(await notifySpawnJoin(ctx,"inline"),console.log(`Agent "${ctx.workerId}" starting inline...`),console.log(` Provider: ${ctx.launch.provider} | Team: ${ctx.validated.team} | Role: ${ctx.validated.role??"-"}`),nt?.enabled)console.log(` Native: enabled | AgentID: ${workerEntry.nativeAgentId}`);console.log("");let{spawnSync:spawnSync2}=__require("child_process"),envVars={...process.env,...ctx.launch.env??{}},result=spawnSync2("sh",["-c",ctx.launch.command],{env:envVars,stdio:"inherit"});if(ctx.agentIdentityId&&ctx.executorId)await updateExecutorState(ctx.executorId,"done").catch(()=>{});if(await unregister(ctx.workerId),nt?.enabled&&ctx.agentName)await clearNativeInbox(ctx.validated.team,ctx.agentName).catch(()=>{}),await unregisterNativeMember(ctx.validated.team,ctx.agentName).catch(()=>{});return console.log(`
1330
+ Agent "${ctx.workerId}" session ended.`),process.exit(result.status??0)}function prependEnvVars(command,env){if(!env||Object.keys(env).length===0)return command;return`env ${Object.entries(env).map(([k,v])=>`${k}=${v}`).join(" ")} ${command}`}async function rejectDuplicateRole(team,role){let existing=await list();for(let w of existing)if(w.role===role&&w.team===team){if(await isPaneAlive(w.paneId))console.error(`Error: Worker with role "${role}" already exists in team "${team}" (state: ${w.state}, pane: ${w.paneId})
1331
+ Use a different --role name for a second worker, e.g.: --role ${role}-2`),process.exit(1);await unregister(w.id)}}async function resolveNativeTeam(team,_repoPath,options){let parentSessionId=(await getTeam(team))?.nativeTeamParentSessionId;if(!parentSessionId)parentSessionId=await discoverClaudeParentSessionId()??`genie-${team}`;await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=options.color??await assignColor(team),nativeTeam;if(options.provider==="claude")nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:options.role??"general-purpose",planModeRequired:options.planMode,permissionMode:options.permissionMode,agentName:options.role};return{parentSessionId,spawnColor,nativeTeam}}async function resolveAgentForSpawn(name,options){let resolved=await resolve3(name);if(!resolved)console.error(`Error: Agent "${name}" not found in directory or built-ins.`),console.error(` Register with: genie dir add ${name} --dir <path>`),console.error(" Or use a built-in: engineer, reviewer, qa, fix, ..."),process.exit(1);let entry=resolved.entry,identityPath=null;if(resolved.builtin)identityPath=resolveBuiltinAgentPath(name);else if(entry.dir)identityPath=loadIdentity(entry);return{entry,repoPath:options.cwd??(entry.repo||void 0)??(entry.dir||void 0)??process.cwd(),identityPath,model:options.model??entry.model}}async function buildSpawnParams2(name,team,options,agent){let params={provider:options.provider,team,role:name,skill:options.skill,extraArgs:options.extraArgs,model:agent.model,systemPromptFile:agent.identityPath??void 0,promptMode:agent.entry.promptMode,initialPrompt:options.initialPrompt},{parentSessionId,spawnColor,nativeTeam}=await resolveNativeTeam(team,agent.repoPath,{...options,role:name});if(nativeTeam)params.nativeTeam=nativeTeam;try{let{injectTeamHooks:injectTeamHooks2}=await Promise.resolve().then(() => (init_inject(),exports_inject));if(await injectTeamHooks2(team))console.log(` Hooks: injected genie hook dispatch into team "${team}"`)}catch(err){console.warn(`Warning: could not inject hooks for team "${team}": ${err instanceof Error?err.message:err}`)}if(params.provider==="claude")params.sessionId=crypto.randomUUID();if(params.provider==="claude"){if(await startOtelReceiver())params.otelPort=getOtelPort(),params.otelLogPrompts=!0}return{params,parentSessionId,spawnColor}}async function handleWorkerSpawn(name,options){let effectiveRole=options.role??name,agent=await resolveAgentForSpawn(name,options),teamWasExplicit=Boolean(options.team),team=options.team||await discoverTeamName();if(!team)return console.error("Error: --team is required (or set GENIE_TEAM, or run inside a genie session)"),process.exit(1);await rejectDuplicateRole(team,effectiveRole);let teamConfig=await getTeam(team);if(teamConfig?.worktreePath)agent={...agent,repoPath:teamConfig.worktreePath};let{params,parentSessionId,spawnColor}=await buildSpawnParams2(effectiveRole,team,options,agent);if(!params.name)params.name=`${params.team}-${effectiveRole}`;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),layoutMode=resolveLayoutMode(options.layout),workerId=await generateWorkerId2(validated.team,effectiveRole),insideTmux=Boolean(process.env.TMUX||options.session),nt=validated.nativeTeam,now=new Date().toISOString(),agentName=nt?.agentName??effectiveRole,agentIdentity=await findOrCreateAgent(agentName,team,effectiveRole);await terminateActiveExecutorWithCleanup(agentIdentity.id);let executorId=crypto.randomUUID(),otelRelayActive=!1;if(!nt?.enabled&&validated.provider==="codex"&&insideTmux)ensureCodexOtelConfig(),otelRelayActive=await ensureOtelRelay(validated.team);let fullCommand=prependEnvVars(launch.command,launch.env),ctx={workerId,validated,launch,layoutMode,fullCommand,agentName,spawnColor,parentSessionId,claudeSessionId:validated.sessionId,otelRelayActive,now,transport:insideTmux?"tmux":"inline",extraArgs:options.extraArgs,cwd:agent.repoPath,spawnIntoCurrentWindow:!teamWasExplicit&&insideTmux&&!options.session,sessionOverride:options.session,autoResume:options.autoResume,agentIdentityId:agentIdentity.id,executorId};if(recordAuditEvent("worker",workerId,"spawn",getActor(),{name,team:validated.team,provider:validated.provider}).catch(()=>{}),insideTmux)return await launchTmuxSpawn(ctx);return await launchInlineSpawn(ctx)}async function cleanupWorkerNativeTeam(w){if(!w.team||!w.nativeAgentId)return;let agentName=w.nativeAgentId.split("@")[0];await clearNativeInbox(w.team,agentName).catch(()=>{}),await unregisterNativeMember(w.team,agentName).catch(()=>{})}function killWorkerPane(w){try{let{execSync:execSync6}=__require("child_process"),currentPane=execSync6(genieTmuxCmd("display-message -p '#{pane_id}'"),{encoding:"utf-8"}).trim();if(w.paneId&&/^(%\d+|inline)$/.test(w.paneId)&&w.paneId!==currentPane)execSync6(genieTmuxCmd(`kill-pane -t ${w.paneId}`),{stdio:"ignore"});else if(w.paneId===currentPane)console.log(" (skipped pane kill \u2014 would kill current session)")}catch{}}function cleanupRelayFiles(id){try{let{join:join30}=__require("path"),{homedir:homedir19}=__require("os"),{unlinkSync:unlinkSync5}=__require("fs"),relayDir=join30(homedir19(),".genie","relay");for(let suffix of["-pane","-meta"])try{unlinkSync5(join30(relayDir,`${id}${suffix}`))}catch{}}catch{}}async function resolveWorkerByName(name){let exact=await get(name);if(exact)return exact;let workers=await list(),byRole=workers.filter((w)=>w.role===name);if(byRole.length===1)return byRole[0];if(byRole.length>1){console.error(`Multiple agents with role "${name}". Specify full ID:`);for(let w of byRole)console.error(` ${w.id} (team: ${w.team})`);process.exit(1)}let bySuffix=workers.filter((w)=>w.id.endsWith(`-${name}`));if(bySuffix.length===1)return bySuffix[0];if(bySuffix.length>1){console.error(`Multiple agents matching "${name}". Specify full ID:`);for(let w of bySuffix)console.error(` ${w.id}`);process.exit(1)}console.error(`Agent "${name}" not found.`),console.error(" Run `genie agent list` to see agents."),process.exit(1)}async function handleWorkerKill(name){let w=await resolveWorkerByName(name);killWorkerPane(w),cleanupRelayFiles(w.id),await cleanupWorkerNativeTeam(w),await unregister(w.id),console.log(`Agent "${w.id}" killed and unregistered (template preserved).`),recordAuditEvent("worker",w.id,"kill",getActor(),{name}).catch(()=>{})}async function handleWorkerStop(name){let w=await resolveWorkerByName(name);if(w.state==="suspended"){console.log(`Agent "${w.id}" is already stopped.`);return}let{suspendWorker:suspendWorker2}=await Promise.resolve().then(() => (init_idle_timeout(),exports_idle_timeout));if(await suspendWorker2(w.id)){if(console.log(`Agent "${w.id}" stopped.`),w.claudeSessionId)console.log(` Session preserved: ${w.claudeSessionId}`);console.log(` Send a message to auto-resume: genie send '...' --to ${w.id}`),recordAuditEvent("worker",w.id,"stop",getActor(),{name}).catch(()=>{})}else console.error(`Failed to stop agent "${w.id}".`),process.exit(1)}async function isResumeEligible(w){if(!w.claudeSessionId)return!1;if(w.state==="done")return!1;let paneAlive=await isPaneAlive(w.paneId);if((w.state==="suspended"||w.state==="error")&&!paneAlive)return!0;if(!paneAlive&&(w.state==="working"||w.state==="idle"||w.state==="spawning"))return!0;return!1}async function resumeAllAgents(){let workers=await list(),toResume=[];for(let w of workers)if(await isResumeEligible(w))toResume.push(w);if(toResume.length===0){console.log("No eligible agents to resume.");return}console.log(`Resuming ${toResume.length} agent(s)...`);for(let w of toResume)try{await resumeAgent(w)}catch(err){console.error(` Failed to resume "${w.id}": ${err instanceof Error?err.message:err}`)}}async function handleWorkerResume(name,options){if(options.all)return resumeAllAgents();if(!name)console.error("Error: provide an agent name, or use --all to resume all eligible agents."),process.exit(1);let w=await resolveWorkerByName(name);if(!w.claudeSessionId)console.error(`Error: Agent "${w.id}" has no Claude session ID \u2014 cannot resume.`),console.error(" Only agents spawned with the Claude provider have resumable sessions."),process.exit(1);if(await isPaneAlive(w.paneId)){console.log(`Agent "${w.id}" is already running (pane ${w.paneId} is alive).`);return}await resumeAgent(w)}function buildResumeParams(agent,template){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??"genie";return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:agent.claudeSessionId,name:`${team}-${agentName}`}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
1332
+ `)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let params=buildResumeParams(agent,template),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentIdentity=await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role);await terminateActiveExecutorWithCleanup(agentIdentity.id);let pid=await capturePanePid(paneId);await createAndLinkExecutor(agentIdentity.id,params.provider,resolveExecutorTransport(params.provider,"tmux"),{pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:agent.claudeSessionId??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}async function resumeAgent(agent){let template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id));await update(agent.id,{resumeAttempts:0});let params=await buildFullResumeParams(agent,template),validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx={workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${params.team}`,claudeSessionId:agent.claudeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume},teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId;try{paneId=createTmuxPane(ctx,teamWindow)}catch(err){console.error(`Failed to create tmux pane: ${err instanceof Error?err.message:"unknown error"}`),process.exit(1)}if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);if(recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:agent.claudeSessionId,team:agent.team}).catch(()=>{}),console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${agent.claudeSessionId}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.role||w.id;if(await isPaneAlive(w.paneId))statusMap.set(name,{state:w.state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function handleLsCommand(options){let dirEntries=await ls(),workers=await list(),statusMap=await buildWorkerStatusMap(workers),entries=[];for(let entry of dirEntries){let running2=statusMap.get(entry.name);entries.push({name:entry.name,dir:entry.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_executor_registry();init_otel_receiver();init_protocol_router_spawn();init_provider_adapters();init_registry();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux()});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile7,readdir as readdir5}from"fs/promises";import{homedir as homedir19}from"os";import{join as join30}from"path";function getCodexDir(){return join30(homedir19(),".codex")}function getSessionsDir(){return join30(getCodexDir(),"sessions")}function getStateDbPath(){return join30(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir5(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result=await scanYear(join30(sessionsDir,year),cwd);if(result)return result}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result=await scanMonth(join30(yearDir,month),cwd);if(result)return result}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result=await scanDay(join30(monthDir,day),cwd);if(result)return result}return null}async function scanDay(dayDir,cwd){let files=(await readdir5(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join30(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile7(filePath,"utf-8"),nlIdx=content.indexOf(`
1333
+ `),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry=JSON.parse(firstLine);if(entry.type==="session_meta"&&entry.payload?.cwd)return{cwd:entry.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile7(logPath,"utf-8")}catch{return[]}return content.split(`
1334
+ `).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result=result.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
1335
+ `).filter((line)=>!isTmuxMarkerOrNoise(line));while(filtered.length>0&&filtered[0].trim()==="")filtered.shift();while(filtered.length>0&&filtered[filtered.length-1].trim()==="")filtered.pop();return filtered.join(`
1336
+ `)}async function resolveActivePaneId(sessionName,session){let windows=await listWindows(session.id);if(!windows||windows.length===0)throw Error(`No windows found in session "${sessionName}"`);let activeWindow=windows.find((w)=>w.active)||windows[0],panes=await listPanes(activeWindow.id);if(!panes||panes.length===0)throw Error(`No panes found in session "${sessionName}"`);return(panes.find((p)=>p.active)||panes[0]).id}function maybeReverse(content,reverse){return reverse?content.split(`
1337
+ `).reverse().join(`
1338
+ `):content}function readRange(paneContent,from,to,reverse){let lines=stripTmuxMarkers(paneContent).split(`
1339
+ `);return maybeReverse(lines.slice(from,to+1).join(`
1340
+ `),reverse)}function searchContent(paneContent,pattern,reverse){let lines=stripTmuxMarkers(paneContent).split(`
1341
+ `);try{let regex=new RegExp(pattern,"i"),matched=lines.filter((line)=>regex.test(line));return maybeReverse(matched.join(`
1342
+ `),reverse)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);throw Error(`Invalid regex pattern: ${message}`)}}async function readSessionLogs(sessionName,options={}){let paneId=options.pane?options.pane.startsWith("%")?options.pane:`%${options.pane}`:await(async()=>{let session=await findSessionByName(sessionName);if(!session)throw Error(`Session "${sessionName}" not found`);return resolveActivePaneId(sessionName,session)})();if(options.range){let parts=options.range.split(":");if(parts.length===2)options.from=Number.parseInt(parts[0],10),options.to=Number.parseInt(parts[1],10)}if(options.all)return stripTmuxMarkers(await capturePaneContent(paneId,1e4));if(options.from!==void 0&&options.to!==void 0)return readRange(await capturePaneContent(paneId,1e4),options.from,options.to,options.reverse);if(options.search||options.grep){let pattern=options.search??options.grep??"";return searchContent(await capturePaneContent(paneId,1e4),pattern,options.reverse)}let content=stripTmuxMarkers(await capturePaneContent(paneId,options.lines||100));return maybeReverse(content,options.reverse)}async function followSessionLogs(sessionName,callback,options={}){let paneId;if(options.pane)paneId=options.pane.startsWith("%")?options.pane:`%${options.pane}`;else{let session=await findSessionByName(sessionName);if(!session)throw Error(`Session "${sessionName}" not found`);let windows=await listWindows(session.id);if(!windows||windows.length===0)throw Error(`No windows found in session "${sessionName}"`);let activeWindow=windows.find((w)=>w.active)||windows[0],panes=await listPanes(activeWindow.id);if(!panes||panes.length===0)throw Error(`No panes found in session "${sessionName}"`);paneId=(panes.find((p)=>p.active)||panes[0]).id}let lastContent="",following=!0;function emitNewLines(oldContent,newContent){let newLines=newContent.split(`
1343
+ `),oldLines=oldContent.split(`
1344
+ `),startIndex=oldLines.length>0?oldLines.length-1:0,lastOldLine=oldLines[oldLines.length-1];for(let line of newLines.slice(startIndex))if(line&&line!==lastOldLine)callback(line)}let pollInterval=setInterval(async()=>{if(!following){clearInterval(pollInterval);return}try{let content=stripTmuxMarkers(await capturePaneContent(paneId,100));if(content!==lastContent)emitNewLines(lastContent,content),lastContent=content}catch{clearInterval(pollInterval),following=!1}},500);return()=>{following=!1,clearInterval(pollInterval)}}var init_log_reader=__esm(()=>{init_tmux()});var exports_read={};__export(exports_read,{readSessionLogs:()=>readSessionLogs2});async function readSessionLogs2(target,options){try{let resolved=await resolveTarget(target),resolvedPaneId=resolved.paneId,sessionName=resolved.session||target,defaultLines=getTerminalConfig().readLines,readOptions={lines:options.lines?Number.parseInt(options.lines,10):defaultLines,from:options.from?Number.parseInt(options.from,10):void 0,to:options.to?Number.parseInt(options.to,10):void 0,range:options.range,search:options.search,grep:options.grep,follow:options.follow,all:options.all,reverse:options.reverse,pane:resolvedPaneId};if(options.follow){console.log(`Following "${target}" (Ctrl+C to stop)...`),console.log("");let stopFollowing=await followSessionLogs(sessionName,(line)=>{console.log(line)},{pane:resolvedPaneId});process.on("SIGINT",()=>{stopFollowing(),console.log(`
1345
+ Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let content=await readSessionLogs(sessionName,readOptions);if(options.json){let lines=content.split(`
1346
+ `);console.log(JSON.stringify({target,session:sessionName,lineCount:lines.length,content:lines},null,2));return}console.log(content)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error reading logs: ${message}`),process.exit(1)}}var init_read=__esm(()=>{init_genie_config2();init_log_reader();init_target_resolver()});var exports_history={};__export(exports_history,{historyCommand:()=>historyCommand});function truncate3(str3,maxLen){if(str3.length<=maxLen)return str3;return`${str3.slice(0,maxLen-3)}...`}function formatPath(path2){let shortened=path2.replace(/^\/home\/\w+\/workspace\//,"~/").replace(/^\/home\/\w+\//,"~/");return truncate3(shortened,50)}function flushPendingReads(ctx){if(ctx.pendingReads.length===0)return;ctx.events.push({timestamp:ctx.lastReadTime,type:"read",summary:ctx.pendingReads.length===1?`Read: ${formatPath(ctx.pendingReads[0])}`:`Read ${ctx.pendingReads.length} files`,details:ctx.pendingReads.length>1?ctx.pendingReads.map(formatPath):void 0}),ctx.pendingReads.length=0}function flushPendingEdits(ctx){if(ctx.pendingEdits.length===0)return;ctx.events.push({timestamp:ctx.lastEditTime,type:"edit",summary:ctx.pendingEdits.length===1?`Edit: ${formatPath(ctx.pendingEdits[0])}`:`Edit ${ctx.pendingEdits.length} files`,details:ctx.pendingEdits.length>1?ctx.pendingEdits.map(formatPath):void 0}),ctx.pendingEdits.length=0}function flushAll(ctx){flushPendingReads(ctx),flushPendingEdits(ctx)}function processToolCallEntry(entry,ctx){if(!entry.toolCall)return;let{name,input}=entry.toolCall,inputRecord=input,normalizedName=name==="shell"||name==="exec_command"?"Bash":name;switch(normalizedName){case"Read":if(ctx.lastReadTime=entry.timestamp,inputRecord.file_path)ctx.pendingReads.push(String(inputRecord.file_path));break;case"Edit":if(flushPendingReads(ctx),ctx.lastEditTime=entry.timestamp,inputRecord.file_path)ctx.pendingEdits.push(String(inputRecord.file_path));break;case"Write":flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"write",summary:`Write: ${formatPath(String(inputRecord.file_path||"unknown"))}`});break;case"Bash":{flushAll(ctx);let cmd=String(inputRecord.command||"").replace(/\n/g," ");ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`Bash: ${truncate3(cmd,60)}`});break}case"AskUserQuestion":{flushAll(ctx);let questions=inputRecord.questions;ctx.events.push({timestamp:entry.timestamp,type:"question",summary:`Question: ${truncate3(questions?.[0]?.question||"question",60)}`});break}default:flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`${normalizedName}: ${truncate3(entry.text.replace(/\n/g," "),60)}`})}}function processTranscriptEntry(entry,ctx){if(entry.role==="user"){flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"prompt",summary:truncate3(entry.text.replace(/\n/g," "),80)});return}if(entry.role==="tool_call"){processToolCallEntry(entry,ctx);return}if(entry.role==="assistant"&&entry.text.length>100)flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"response",summary:truncate3(entry.text.replace(/\n/g," "),80)})}function extractEvents(entries){let ctx={events:[],pendingReads:[],pendingEdits:[],lastReadTime:"",lastEditTime:""};for(let entry of entries)processTranscriptEntry(entry,ctx);return flushAll(ctx),ctx.events}function detectStatus(entries){if(entries.length===0)return"unknown";let lastEntries=entries.slice(-10);for(let entry of lastEntries.reverse())if(entry.role==="tool_call"&&entry.toolCall?.name==="AskUserQuestion")return"question";let last=entries[entries.length-1];if(last.role==="user")return"working";if(last.role==="assistant")return"idle";return"unknown"}function formatEventsForDisplay(events,stats){let lines=[];lines.push(""),lines.push(`Session: ${stats.workerId} [${stats.provider}]${stats.branch?` (${stats.branch})`:""} | ${stats.duration} | ${stats.totalEntries} entries \u2192 ${stats.compressedLines} lines`),lines.push("");for(let event of events){let time=formatTime(event.timestamp),icon=getEventIcon(event.type);if(lines.push(`[${time}] ${icon} ${event.summary}`),event.details&&event.details.length>0){for(let detail of event.details.slice(0,5))lines.push(` ${detail}`);if(event.details.length>5)lines.push(` ... and ${event.details.length-5} more`)}if(event.result)lines.push(` \u2192 ${event.result}`)}return lines.push(""),lines.push(`Status: ${stats.status.toUpperCase()} | ${stats.exchanges} exchanges | ${stats.toolCalls} tool calls | ${stats.compressionRatio.toFixed(0)}x compression`),lines.push(""),lines.join(`
1347
+ `)}function getEventIcon(type2){switch(type2){case"prompt":return"\uD83D\uDCAC";case"read":return"\uD83D\uDCD6";case"edit":return"\u270F\uFE0F";case"write":return"\uD83D\uDCDD";case"bash":return"\u26A1";case"question":return"\u2753";case"answer":return"\u2705";case"permission":return"\uD83D\uDD10";case"thinking":return"\uD83E\uDD14";case"response":return"\uD83D\uDCAD";default:return"\u2022"}}function formatToolDetail(toolCall){let input=toolCall.input;if(toolCall.name==="Bash"||toolCall.name==="shell")return` ${toolCall.name}: ${input.command??""}`;if(["Read","Edit","Write"].includes(toolCall.name))return` ${toolCall.name}: ${input.file_path??""}`;return` ${toolCall.name}`}function formatTranscriptEntryForDisplay(entry){let time=formatTime(entry.timestamp);switch(entry.role){case"user":return[`
1348
+ [${time}] USER:`,entry.text];case"tool_call":return entry.toolCall?[`
1349
+ [${time}] TOOL:`,formatToolDetail(entry.toolCall)]:[];case"assistant":return[`
1350
+ [${time}] ASSISTANT:`,truncate3(entry.text,500)];case"tool_result":return[`
1351
+ [${time}] RESULT:`,` ${truncate3(entry.text,500)}`];case"system":return[`
1352
+ [${time}] SYSTEM:`,entry.text];default:return[]}}function formatFullConversation(entries){return entries.flatMap(formatTranscriptEntryForDisplay).join(`
1353
+ `)}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 entry of filtered){let{raw:_raw,...rest}=entry;console.log(JSON.stringify(options.raw?entry:rest))}return!0}if(options.raw){for(let entry of filtered)console.log(JSON.stringify(entry.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,stats={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,events},null,2));return}console.log(formatEventsForDisplay(events,stats))}var init_history=__esm(()=>{init_agent_registry()});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 randomUUID4}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[];return(await listTeams2()).filter((t)=>t.status==="in_progress").map((t)=>t.name)}async function writeMailbox(repoPath,leader,message,traceId){try{await(await getConnection())`
1238
1354
  INSERT INTO mailbox (id, repo_path, "from", "to", body, trace_id, created_at)
1239
1355
  VALUES (${`mail-${traceId}`}, ${repoPath}, 'system', ${leader}, ${message}, ${traceId}, now())
1240
1356
  `}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`
@@ -1247,52 +1363,7 @@ Sync complete: ${total} active agent(s), ${result.archived.length} archived.`)}a
1247
1363
  AND a.team = ${teamName}
1248
1364
  AND e.state NOT IN ('terminated', 'done', 'error')
1249
1365
  LIMIT 1
1250
- `;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 deliverToTeam(event,teamName){let team=await getTeam(teamName);if(!team)return;let traceId=randomUUID4(),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);try{await deliverViaProvider(team.leader,teamName,message,traceId)}catch{}try{await deliverToHierarchy(team.leader,teamName,message,traceId)}catch{}}try{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}`,data:{channel:event.channel,eventType:event.eventType,traceId,...event.payload}})}catch{}}async function routeEvent(event,handler){if(handler){await handler(event);return}let targets=await resolveTargetTeams(event);for(let teamName of targets)await deliverToTeam(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,CHANNELS;var init_event_router=__esm(()=>{init_db();init_mailbox();init_registry();init_runtime_events();init_task_service();init_team_manager();ACTIONABLE_EVENTS=new Set(["task.blocked","executor.error","executor.permission"]);CHANNELS=["genie_task_stage","genie_executor_state","genie_message","genie_audit_event"]});import{createHash as createHash2}from"crypto";function parseRoutingHeader(text){if(!text)return null;let match=text.split(`
1251
- `)[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 createHash2("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"]});import{writeFileSync as writeFileSync7}from"fs";import{join as join26}from"path";function scaffoldAgentFiles(targetDir,agentName){writeFileSync7(join26(targetDir,"SOUL.md"),SOUL_TEMPLATE),writeFileSync7(join26(targetDir,"HEARTBEAT.md"),HEARTBEAT_TEMPLATE);let agentsMd=agentName?AGENTS_TEMPLATE.replace("name: my-agent",`name: ${agentName}`):AGENTS_TEMPLATE;writeFileSync7(join26(targetDir,"AGENTS.md"),agentsMd)}var SOUL_TEMPLATE=`# Soul
1252
-
1253
- You are an AI assistant. Define your role, personality, and approach here.
1254
-
1255
- Replace this with your agent's identity \u2014 who they are, how they communicate, and what they care about.
1256
- `,HEARTBEAT_TEMPLATE=`# Heartbeat
1257
-
1258
- Run this checklist on every iteration. Exit early if nothing actionable.
1259
-
1260
- ## Checklist
1261
-
1262
- ### 1. Check Assignments
1263
- Review your task queue. What's assigned to you? Prioritize by urgency and impact.
1264
-
1265
- ### 2. Do Work
1266
- Execute on your current tasks. Focus on the highest-priority item first.
1267
-
1268
- ### 3. Report Progress
1269
- Update status on completed or blocked items. Keep it factual.
1270
-
1271
- ### 4. Exit If Nothing Actionable
1272
- If all work is done and no new tasks exist \u2014 exit. Don't create busywork.
1273
- `,AGENTS_TEMPLATE=`---
1274
- name: my-agent
1275
- description: "Describe what this agent does."
1276
- model: inherit
1277
- color: blue
1278
- promptMode: system
1279
- ---
1280
-
1281
- @HEARTBEAT.md
1282
-
1283
- <mission>
1284
- Define your agent's mission here. What is their primary goal? What do they own?
1285
- </mission>
1286
-
1287
- <principles>
1288
- - **Clarity over ambiguity.** Be specific about expectations and outcomes.
1289
- - **Quality over speed.** Do it right the first time.
1290
- </principles>
1291
-
1292
- <constraints>
1293
- - List any hard constraints or rules this agent must follow.
1294
- </constraints>
1295
- `;var init_templates=()=>{};var exports_session={};__export(exports_session,{sessionCommand:()=>sessionCommand,sanitizeWindowName:()=>sanitizeWindowName,getAgentsFilePath:()=>getAgentsFilePath,buildClaudeCommand:()=>buildClaudeCommand2});import{spawnSync}from"child_process";import{createHash as createHash3}from"crypto";import{existsSync as existsSync22}from"fs";import{basename as basename4,join as join27}from"path";function shortPathHash(p){return createHash3("md5").update(p).digest("hex").slice(0,4)}function getAgentsFilePath(){let agentsPath=join27(process.cwd(),"AGENTS.md");if(existsSync22(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"team-lead"}}async function ensureNativeTeamForLeader(teamName,cwd){let leaderName=await resolveSessionLeaderName(teamName);await ensureNativeTeam(teamName,`Genie team: ${teamName}`,"pending",leaderName),await registerNativeMember(teamName,{agentName:basename4(cwd),agentType:leaderName,color:"blue",cwd})}function buildClaudeCommand2(teamName,systemPromptFile,continueName){return buildTeamLeadCommand(teamName,{systemPromptFile,continueName})}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);await terminateActiveExecutor(agentIdentity.id);let 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{}let executor=await createExecutor(agentIdentity.id,"claude","tmux",{pid,tmuxSession:sessionName,tmuxPaneId:paneId,tmuxWindow:windowName,state:"spawning",repoPath:workspaceDir});await setCurrentExecutor(agentIdentity.id,executor.id)}catch{}}async function resolveWindowName(sessionName,cwd){let baseName=sanitizeWindowName(basename4(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){if(await ensureNativeTeamForLeader(windowName,workspaceDir),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=basename4(workspaceDir),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),console.log(`Started Claude Code as ${agentName} in ${workspaceDir}`),await registerSessionInRegistry(sessionName,windowName,workspaceDir)}async function launchWithContinueFallback(target,windowName,systemPromptFile){let continueName=sanitizeTeamName(windowName),hasPriorSession=sessionExists(continueName),cmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,hasPriorSession?continueName:void 0);if(await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),hasPriorSession){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 freshCmd=buildClaudeCommand2(windowName,systemPromptFile||void 0,void 0);await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`)}}}async function focusTeamWindow(sessionName,windowName,workingDir,systemPromptFile){if((await ensureTeamWindow(sessionName,windowName,workingDir)).created){console.log(`Created team window "${windowName}"`),await setWindowEnv(`${sessionName}:${windowName}`,"GENIE_CWD",workingDir),await ensureNativeTeamForLeader(windowName,workingDir);let target=`${sessionName}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,systemPromptFile),console.log(`Started Claude Code as ${basename4(workingDir)}@${sanitizeTeamName(windowName)} in ${workingDir}`),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...`),await ensureNativeTeamForLeader(windowName,workingDir);let cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`),await launchWithContinueFallback(target,windowName,systemPromptFile),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(basename4(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));spawnSync("tmux",[...genieTmuxPrefix2(),cmd,"-t",target],{stdio:"inherit"})}async function sessionCommand(options={}){let workspaceDir=options.dir??process.cwd(),sessionName=options.name??sanitizeWindowName(basename4(workspaceDir));try{let windowName=await deriveWindowName(sessionName,workspaceDir,options.team);if(options.reset)await handleReset(sessionName,windowName);let session=await findSessionByName(sessionName),systemPromptFile=getAgentsFilePath();if(!systemPromptFile)if(await esm_default2({message:"No agent found in this directory. Scaffold one?",default:!0}))scaffoldAgentFiles(workspaceDir),systemPromptFile=join27(workspaceDir,"AGENTS.md"),console.log("Created SOUL.md, HEARTBEAT.md, and AGENTS.md");else console.error("AGENTS.md required. Run `genie` again to scaffold."),process.exit(1);if(!session)await createSession2(sessionName,windowName,workspaceDir,systemPromptFile),attachToWindow(sessionName,windowName);else if(process.env.TMUX){let suffix=Date.now().toString(36).slice(-4),currentWindowName=`${windowName}-${suffix}`;await executeTmux2(`rename-window ${shellQuote(currentWindowName)}`),await ensureNativeTeamForLeader(currentWindowName,workspaceDir);let cmd=buildClaudeCommand2(currentWindowName,systemPromptFile||void 0,void 0),{execSync:execSyncCmd}=__require("child_process");execSyncCmd(cmd,{stdio:"inherit",cwd:workspaceDir})}else console.log(`Session "${sessionName}" already exists`),await focusTeamWindow(sessionName,windowName,workspaceDir,systemPromptFile),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_esm6();init_agent_registry();init_claude_native_teams();init_executor_registry();init_team_lead_command();init_tmux();init_templates()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,ensureTeamLead:()=>ensureTeamLead});import{existsSync as existsSync23}from"fs";import{join as join28}from"path";function getSystemPromptFile(workingDir){let agentsPath=join28(workingDir,"AGENTS.md");if(existsSync23(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let current=await getCurrentSessionName();if(current)return current;let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam3(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}let sessionName=sanitizeTeamName(teamName);if(await findSessionByName(sessionName))return sessionName;if(!await createSession(sessionName))throw Error(`Failed to create tmux session "${sessionName}"`);return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let sessionName=await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let currentSession=await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:currentSession,window:sanitizeWindowName(teamName)};let{getTeam:getTeam3}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=(await getTeam3(teamName))?.leader||"team-lead";await ensureNativeTeam(teamName,`Genie team: ${teamName}`,"pending",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});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`)}return{created:teamWindow.created,session,window:windowName}}var init_team_auto_spawn=__esm(()=>{init_session();init_claude_native_teams();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}try{await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey,0),spawned.push(teamName)}catch(err){let newCount=failures+1;spawnFailures.set(sessionKey,newCount);let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`)}}return spawned}function startInboxWatcher(deps=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,spawnFailures;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg)};spawnFailures=new Map});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});import{watch}from"fs";import{homedir as homedir18}from"os";import{basename as basename5,join as join29}from"path";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=basename5(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){let info=extractSessionInfo(filePath);if(!info)return;let storedOffset=offsetCache.get(info.sessionId)??0;try{setLiveWorkPending(!0);let workerMap=await buildWorkerMap(sql),result=await ingestFileFull(sql,info.sessionId,filePath,info.projectPath,storedOffset,{parentSessionId:info.parentSessionId,isSubagent:info.isSubagent,workerMap});offsetCache.set(info.sessionId,result.newOffset)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`[filewatch] error ingesting ${filePath} at offset ${storedOffset}: ${message}`)}finally{setLiveWorkPending(!1)}}async function startFilewatch(sql){if(watcher)return!0;let claudeDir=join29(process.env.CLAUDE_CONFIG_DIR??join29(homedir18(),".claude"),"projects");await loadOffsets(sql);try{return watcher=watch(claudeDir,{recursive:!0},(_eventType,filename)=>{if(!filename||!filename.endsWith(".jsonl"))return;let fullPath=join29(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;var init_session_filewatch=__esm(()=>{init_session_capture();offsetCache=new Map,debounceTimers=new Map});var exports_scheduler_daemon={};__export(exports_scheduler_daemon,{startDaemon:()=>startDaemon,recoverOnStartup:()=>recoverOnStartup,reconcileOrphans:()=>reconcileOrphans,reconcileOrphanedRuns:()=>reconcileOrphanedRuns,reclaimExpiredLeases:()=>reclaimExpiredLeases,logToFile:()=>logToFile,fireTrigger:()=>fireTrigger,emitWorkerEvents:()=>emitWorkerEvents,collectMachineSnapshot:()=>collectMachineSnapshot,collectHeartbeats:()=>collectHeartbeats,claimDueTriggers:()=>claimDueTriggers,attemptAgentResume:()=>attemptAgentResume,_resetWorkerStatesForTesting:()=>_resetWorkerStatesForTesting});import{randomUUID as randomUUID5}from"crypto";import{appendFileSync as appendFileSync2,mkdirSync as mkdirSync7}from"fs";import{homedir as homedir19}from"os";import{join as join30}from"path";function getLogDir2(){return join30(process.env.GENIE_HOME??join30(homedir19(),".genie"),"logs")}function getLogFile(){return join30(getLogDir2(),"scheduler.log")}function logToFile(entry){let logDir=getLogDir2();mkdirSync7(logDir,{recursive:!0}),appendFileSync2(getLogFile(),`${JSON.stringify(entry)}
1366
+ `;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 deliverToTeam2(event,teamName){let team=await getTeam(teamName);if(!team)return;let traceId=randomUUID4(),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);try{await deliverViaProvider(team.leader,teamName,message,traceId)}catch{}try{await deliverToHierarchy(team.leader,teamName,message,traceId)}catch{}}try{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}`,data:{channel:event.channel,eventType:event.eventType,traceId,...event.payload}})}catch{}}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,CHANNELS;var init_event_router=__esm(()=>{init_db();init_mailbox();init_registry();init_runtime_events();init_task_service();init_team_manager();ACTIONABLE_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});import{watch}from"fs";import{homedir as homedir20}from"os";import{basename as basename5,join as join31}from"path";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=basename5(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){let info=extractSessionInfo(filePath);if(!info)return;let storedOffset=offsetCache.get(info.sessionId)??0;try{setLiveWorkPending(!0);let workerMap=await buildWorkerMap(sql),result=await ingestFileFull(sql,info.sessionId,filePath,info.projectPath,storedOffset,{parentSessionId:info.parentSessionId,isSubagent:info.isSubagent,workerMap});offsetCache.set(info.sessionId,result.newOffset)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`[filewatch] error ingesting ${filePath} at offset ${storedOffset}: ${message}`)}finally{setLiveWorkPending(!1)}}async function startFilewatch(sql){if(watcher)return!0;let claudeDir=join31(process.env.CLAUDE_CONFIG_DIR??join31(homedir20(),".claude"),"projects");await loadOffsets(sql);try{return watcher=watch(claudeDir,{recursive:!0},(_eventType,filename)=>{if(!filename||!filename.endsWith(".jsonl"))return;let fullPath=join31(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;var init_session_filewatch=__esm(()=>{init_session_capture();offsetCache=new Map,debounceTimers=new Map});var exports_scheduler_daemon={};__export(exports_scheduler_daemon,{startDaemon:()=>startDaemon,recoverOnStartup:()=>recoverOnStartup,reconcileOrphans:()=>reconcileOrphans,reconcileOrphanedRuns:()=>reconcileOrphanedRuns,reclaimExpiredLeases:()=>reclaimExpiredLeases,logToFile:()=>logToFile,fireTrigger:()=>fireTrigger,emitWorkerEvents:()=>emitWorkerEvents,collectMachineSnapshot:()=>collectMachineSnapshot,collectHeartbeats:()=>collectHeartbeats,claimDueTriggers:()=>claimDueTriggers,attemptAgentResume:()=>attemptAgentResume,_resetWorkerStatesForTesting:()=>_resetWorkerStatesForTesting});import{randomUUID as randomUUID5}from"crypto";import{appendFileSync as appendFileSync2,mkdirSync as mkdirSync7}from"fs";import{homedir as homedir21}from"os";import{join as join32}from"path";function getLogDir2(){return join32(process.env.GENIE_HOME??join32(homedir21(),".genie"),"logs")}function getLogFile(){return join32(getLogDir2(),"scheduler.log")}function logToFile(entry){let logDir=getLogDir2();mkdirSync7(logDir,{recursive:!0}),appendFileSync2(getLogFile(),`${JSON.stringify(entry)}
1296
1367
  `)}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((resolve5)=>setTimeout(resolve5,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));return(await list2()).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,claudeSessionId:a.claudeSessionId}))}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:execSync6}=await import("child_process"),{genieTmuxCmd:genieTmuxCmd2}=await Promise.resolve().then(() => (init_tmux_wrapper(),exports_tmux_wrapper));return execSync6(`${genieTmuxCmd2("list-sessions")} 2>/dev/null`,{encoding:"utf-8"}).trim().split(`
1297
1368
  `).filter(Boolean).length}catch{return 0}}async function defaultResumeAgent(agentId){try{let{execSync:execSync6}=await import("child_process");return execSync6(`genie agent resume ${agentId}`,{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:randomUUID5,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}}async function claimDueTriggers(deps,config,daemonId){let sql=await deps.getConnection(),now=deps.now(),leaseUntil=new Date(now.getTime()+300000),runningCount=(await sql`
1298
1369
  SELECT count(*)::int AS cnt FROM runs
@@ -1363,23 +1434,23 @@ Define your agent's mission here. What is their primary goal? What do they own?
1363
1434
  `,failedCount++,deps.log({timestamp:now.toISOString(),level:"warn",event:"orphan_run_failed",run_id:run.id,worker_id:run.worker_id,dead_heartbeats:threshold})}if(failedCount>0)deps.log({timestamp:now.toISOString(),level:"info",event:"orphan_reconciliation_completed",failed_count:failedCount});return failedCount}async function collectMachineSnapshot(deps){let sql=await deps.getConnection(),now=deps.now(),snapshotId=deps.generateId(),workers=await deps.listWorkers(),activeWorkers=workers.filter((w)=>!["done","error","suspended"].includes(w.state)).length,teams=new Set(workers.filter((w)=>w.team).map((w)=>w.team)),tmuxSessions=await deps.countTmuxSessions(),cpuPercent=null,memoryMb=null;try{let mem=process.memoryUsage();memoryMb=Math.round(mem.rss/1024/1024)}catch{}try{let cpus=(await import("os")).cpus();if(cpus.length>0){let total=cpus.reduce((acc,cpu)=>{let t=Object.values(cpu.times).reduce((a,b2)=>a+b2,0);return acc+t-cpu.times.idle},0),totalAll=cpus.reduce((acc,cpu)=>acc+Object.values(cpu.times).reduce((a,b2)=>a+b2,0),0);cpuPercent=totalAll>0?Math.round(total/totalAll*100):null}}catch{}await sql`
1364
1435
  INSERT INTO machine_snapshots (id, active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at)
1365
1436
  VALUES (${snapshotId}, ${activeWorkers}, ${teams.size}, ${tmuxSessions}, ${cpuPercent}, ${memoryMb}, ${now})
1366
- `,deps.log({timestamp:now.toISOString(),level:"debug",event:"machine_snapshot",active_workers:activeWorkers,active_teams:teams.size,tmux_sessions:tmuxSessions,cpu_percent:cpuPercent,memory_mb:memoryMb})}async function emitWorkerEvents(deps){let workers=await deps.listWorkers(),now=deps.now().toISOString(),currentIds=new Set;for(let worker of workers){currentIds.add(worker.id);let prev=previousWorkerStates.get(worker.id),repoPath=worker.repoPath??process.cwd();if(!prev)await deps.publishEvent(`genie.agent.${worker.id}.spawned`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} spawned`,data:{state:worker.state},source:"registry"},repoPath);else if(prev.state!==worker.state){if(await deps.publishEvent(`genie.agent.${worker.id}.state`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} state: ${prev.state} \u2192 ${worker.state}`,data:{previousState:prev.state,state:worker.state},source:"registry"},repoPath),worker.state==="done"&&worker.wishSlug&&worker.groupNumber!=null)await deps.publishEvent(`genie.wish.${worker.wishSlug}.group.${worker.groupNumber}.done`,{timestamp:now,kind:"system",agent:worker.id,team:worker.team,text:`Wish ${worker.wishSlug} group ${worker.groupNumber} completed by ${worker.id}`,data:{wishSlug:worker.wishSlug,groupNumber:worker.groupNumber},source:"registry"},repoPath)}previousWorkerStates.set(worker.id,{...worker})}for(let[id,prev]of previousWorkerStates)if(!currentIds.has(id))await deps.publishEvent(`genie.agent.${id}.killed`,{timestamp:now,kind:"state",agent:id,team:prev.team,text:`Agent ${id} killed`,data:{lastState:prev.state},source:"registry"},prev.repoPath??process.cwd()),previousWorkerStates.delete(id)}function _resetWorkerStatesForTesting(){previousWorkerStates.clear()}function startInboxWatcherIfEnabled(deps){let pollMs=getInboxPollIntervalMs();if(pollMs===0)return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_disabled"}),null;return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_started",poll_interval_ms:pollMs}),startInboxWatcher()}function startDaemon(configOverrides,depsOverrides){let config=resolveConfig(configOverrides),deps={...createDefaultDeps(),...depsOverrides},daemonId=deps.generateId(),running2=!0,pollTimeout=null,pollResolve=null,listenConnection=null,heartbeatTimer=null,orphanTimer=null,inboxWatcherHandle=null,captureFallbackTimer=null,eventRouterHandle=null,stop=()=>{if(running2=!1,pollTimeout)clearTimeout(pollTimeout),pollTimeout=null;if(pollResolve)pollResolve(),pollResolve=null;if(heartbeatTimer)clearInterval(heartbeatTimer),heartbeatTimer=null;if(orphanTimer)clearInterval(orphanTimer),orphanTimer=null;if(inboxWatcherHandle)stopInboxWatcher(inboxWatcherHandle),inboxWatcherHandle=null;if(listenConnection)listenConnection.end().catch(()=>{}),listenConnection=null;if(captureFallbackTimer)clearInterval(captureFallbackTimer),captureFallbackTimer=null;eventRouterHandle?.stop().catch(()=>{}),eventRouterHandle=null,Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)).then((m)=>m.stopFilewatch()).catch(()=>{}),Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill)).then((m)=>m.stopBackfill()).catch(()=>{}),Promise.resolve().then(() => (init_db(),exports_db)).then(({getLockfilePath:getLockfilePath2})=>{try{__require("fs").unlinkSync(getLockfilePath2())}catch{}}).catch(()=>{})},processTriggers=async()=>{try{let claimed=await claimDueTriggers(deps,config,daemonId);if(claimed.length===0)return;if(claimed.length>config.jitterThreshold){let jitterMs=deps.jitter(config.maxJitterMs);deps.log({timestamp:deps.now().toISOString(),level:"info",event:"jitter_applied",count:claimed.length,jitter_ms:jitterMs}),await deps.sleep(jitterMs)}for(let trigger of claimed){if(!running2)break;await fireTrigger(deps,trigger,daemonId)}}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"process_cycle_error",error:message})}};async function setupListenNotify(d,onTrigger){try{let sql=await d.getConnection();return await sql.listen("genie_trigger_due",async()=>{if(!running2)return;await onTrigger()}),d.log({timestamp:d.now().toISOString(),level:"info",event:"listen_started",channel:"genie_trigger_due"}),sql}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"listen_failed",error:message}),null}}function startOrphanTimer(d,cfg){return setInterval(async()=>{if(!running2)return;try{await reconcileOrphans(d,cfg)}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"orphan_reconciliation_error",error:message})}},cfg.orphanCheckIntervalMs)}async function startEventRouterSafe(d){try{let handle=await startEventRouter();return d.log({timestamp:d.now().toISOString(),level:"info",event:"event_router_started"}),handle}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"event_router_start_failed",error:message}),null}}async function initSessionCapture(d,cfg){try{let captureSql=await d.getConnection(),{startFilewatch:startFilewatch2}=await Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));if(!await startFilewatch2(captureSql)){let{ingestFileFull:ingestFileFull2,discoverAllJsonlFiles:discoverAllJsonlFiles2,buildWorkerMap:buildWorkerMap2}=await Promise.resolve().then(() => (init_session_capture(),exports_session_capture));d.log({timestamp:d.now().toISOString(),level:"warn",event:"filewatch_failed_fallback_polling"});let timer2=setInterval(async()=>{if(!running2)return;try{let files=await discoverAllJsonlFiles2(),workerMap=await buildWorkerMap2(captureSql);for(let f of files)await ingestFileFull2(captureSql,f.sessionId,f.jsonlPath,f.projectPath,0,{parentSessionId:f.parentSessionId,isSubagent:f.isSubagent,workerMap})}catch{}},cfg.heartbeatIntervalMs);return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),timer2}return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),null}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"session_capture_init_failed",error:message}),null}}async function runHeartbeat(d){if(!running2)return;try{await collectHeartbeats(d),await collectMachineSnapshot(d),await emitWorkerEvents(d);try{let retSql=await d.getConnection();await retSql`DELETE FROM heartbeats WHERE created_at < now() - interval '7 days'`,await retSql`DELETE FROM machine_snapshots WHERE created_at < now() - interval '30 days'`,await retSql`DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days'`}catch{}}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"heartbeat_error",error:message})}}let done=(async()=>{deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_started",daemon_id:daemonId,max_concurrent:config.maxConcurrent,poll_interval_ms:config.pollIntervalMs});try{await recoverOnStartup(deps,daemonId,config)}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"recovery_error",error:message})}listenConnection=await setupListenNotify(deps,processTriggers),heartbeatTimer=setInterval(()=>runHeartbeat(deps),config.heartbeatIntervalMs),orphanTimer=startOrphanTimer(deps,config),inboxWatcherHandle=startInboxWatcherIfEnabled(deps),eventRouterHandle=await startEventRouterSafe(deps),captureFallbackTimer=await initSessionCapture(deps,config),await processTriggers();while(running2){if(await new Promise((resolve5)=>{pollResolve=resolve5,pollTimeout=setTimeout(resolve5,config.pollIntervalMs)}),pollResolve=null,!running2)break;await processTriggers()}deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_stopped",daemon_id:daemonId})})();return{stop,done,daemonId}}var RESUME_COOLDOWN_MS=60000,DEFAULT_MAX_RESUME_ATTEMPTS=3,previousWorkerStates;var init_scheduler_daemon=__esm(()=>{init_cron();init_event_router();init_inbox_watcher();init_run_spec();previousWorkerStates=new Map});var exports_serve={};__export(exports_serve,{registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isServeRunning:()=>isServeRunning,ensureTuiSession:()=>ensureTuiSession,autoStartServe:()=>autoStartServe});import{execSync as execSync6,spawn as spawn3}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync8,readFileSync as readFileSync11,unlinkSync as unlinkSync5,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir20}from"os";import{join as join31}from"path";function genieHome(){return process.env.GENIE_HOME??join31(homedir20(),".genie")}function servePidPath(){return join31(genieHome(),"serve.pid")}function genieTmuxConf(){return[join31(genieHome(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync11(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync8(genieHome(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync5(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function genieTmux(subcmd){return`tmux -L ${GENIE_SOCKET} -f ${genieTmuxConf()} ${subcmd}`}function tuiTmuxConf(){return[join31(genieHome(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmux(subcmd){return`tmux -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync6(genieTmux("list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function applyTuiStyle(){let cmds=[`set-option -t ${TUI_SESSION} pane-border-style 'fg=${TUI_STYLE.inactiveBorder}'`,`set-option -t ${TUI_SESSION} pane-active-border-style 'fg=${TUI_STYLE.activeBorder}'`,`set-option -t ${TUI_SESSION} mouse on`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){let bindings=[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${TUI_SESSION}:0.1" "select-pane -t ${TUI_SESSION}:0.0"`,`bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}"`,`bind-key -T root C-t send-keys -t ${TUI_SESSION}:0.1 C-b c`,"bind-key -T root C-q detach-client"];for(let cmd of bindings)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync6(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1437
+ `,deps.log({timestamp:now.toISOString(),level:"debug",event:"machine_snapshot",active_workers:activeWorkers,active_teams:teams.size,tmux_sessions:tmuxSessions,cpu_percent:cpuPercent,memory_mb:memoryMb})}async function emitWorkerEvents(deps){let workers=await deps.listWorkers(),now=deps.now().toISOString(),currentIds=new Set;for(let worker of workers){currentIds.add(worker.id);let prev=previousWorkerStates.get(worker.id),repoPath=worker.repoPath??process.cwd();if(!prev)await deps.publishEvent(`genie.agent.${worker.id}.spawned`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} spawned`,data:{state:worker.state},source:"registry"},repoPath);else if(prev.state!==worker.state){if(await deps.publishEvent(`genie.agent.${worker.id}.state`,{timestamp:now,kind:"state",agent:worker.id,team:worker.team,text:`Agent ${worker.id} state: ${prev.state} \u2192 ${worker.state}`,data:{previousState:prev.state,state:worker.state},source:"registry"},repoPath),worker.state==="done"&&worker.wishSlug&&worker.groupNumber!=null)await deps.publishEvent(`genie.wish.${worker.wishSlug}.group.${worker.groupNumber}.done`,{timestamp:now,kind:"system",agent:worker.id,team:worker.team,text:`Wish ${worker.wishSlug} group ${worker.groupNumber} completed by ${worker.id}`,data:{wishSlug:worker.wishSlug,groupNumber:worker.groupNumber},source:"registry"},repoPath)}previousWorkerStates.set(worker.id,{...worker})}for(let[id,prev]of previousWorkerStates)if(!currentIds.has(id))await deps.publishEvent(`genie.agent.${id}.killed`,{timestamp:now,kind:"state",agent:id,team:prev.team,text:`Agent ${id} killed`,data:{lastState:prev.state},source:"registry"},prev.repoPath??process.cwd()),previousWorkerStates.delete(id)}function _resetWorkerStatesForTesting(){previousWorkerStates.clear()}function startInboxWatcherIfEnabled(deps){let pollMs=getInboxPollIntervalMs();if(pollMs===0)return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_disabled"}),null;return deps.log({timestamp:deps.now().toISOString(),level:"info",event:"inbox_watcher_started",poll_interval_ms:pollMs}),startInboxWatcher()}function startDaemon(configOverrides,depsOverrides){let config=resolveConfig(configOverrides),deps={...createDefaultDeps(),...depsOverrides},daemonId=deps.generateId(),running2=!0,pollTimeout=null,pollResolve=null,listenConnection=null,heartbeatTimer=null,orphanTimer=null,inboxWatcherHandle=null,captureFallbackTimer=null,eventRouterHandle=null,stop=()=>{if(running2=!1,pollTimeout)clearTimeout(pollTimeout),pollTimeout=null;if(pollResolve)pollResolve(),pollResolve=null;if(heartbeatTimer)clearInterval(heartbeatTimer),heartbeatTimer=null;if(orphanTimer)clearInterval(orphanTimer),orphanTimer=null;if(inboxWatcherHandle)stopInboxWatcher(inboxWatcherHandle),inboxWatcherHandle=null;if(listenConnection)listenConnection.end().catch(()=>{}),listenConnection=null;if(captureFallbackTimer)clearInterval(captureFallbackTimer),captureFallbackTimer=null;eventRouterHandle?.stop().catch(()=>{}),eventRouterHandle=null,Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)).then((m)=>m.stopFilewatch()).catch(()=>{}),Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill)).then((m)=>m.stopBackfill()).catch(()=>{}),Promise.resolve().then(() => (init_db(),exports_db)).then(({getLockfilePath:getLockfilePath2})=>{try{__require("fs").unlinkSync(getLockfilePath2())}catch{}}).catch(()=>{})},processTriggers=async()=>{try{let claimed=await claimDueTriggers(deps,config,daemonId);if(claimed.length===0)return;if(claimed.length>config.jitterThreshold){let jitterMs=deps.jitter(config.maxJitterMs);deps.log({timestamp:deps.now().toISOString(),level:"info",event:"jitter_applied",count:claimed.length,jitter_ms:jitterMs}),await deps.sleep(jitterMs)}for(let trigger of claimed){if(!running2)break;await fireTrigger(deps,trigger,daemonId)}}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"process_cycle_error",error:message})}};async function setupListenNotify(d,onTrigger){try{let sql=await d.getConnection();return await sql.listen("genie_trigger_due",async()=>{if(!running2)return;await onTrigger()}),d.log({timestamp:d.now().toISOString(),level:"info",event:"listen_started",channel:"genie_trigger_due"}),sql}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"listen_failed",error:message}),null}}function startOrphanTimer(d,cfg){return setInterval(async()=>{if(!running2)return;try{await reconcileOrphans(d,cfg)}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"orphan_reconciliation_error",error:message})}},cfg.orphanCheckIntervalMs)}async function startEventRouterSafe(d){try{let handle=await startEventRouter();return d.log({timestamp:d.now().toISOString(),level:"info",event:"event_router_started"}),handle}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"event_router_start_failed",error:message}),null}}async function initSessionCapture(d,cfg){try{let captureSql=await d.getConnection(),{startFilewatch:startFilewatch2}=await Promise.resolve().then(() => (init_session_filewatch(),exports_session_filewatch)),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));if(!await startFilewatch2(captureSql)){let{ingestFileFull:ingestFileFull2,discoverAllJsonlFiles:discoverAllJsonlFiles2,buildWorkerMap:buildWorkerMap2}=await Promise.resolve().then(() => (init_session_capture(),exports_session_capture));d.log({timestamp:d.now().toISOString(),level:"warn",event:"filewatch_failed_fallback_polling"});let timer2=setInterval(async()=>{if(!running2)return;try{let files=await discoverAllJsonlFiles2(),workerMap=await buildWorkerMap2(captureSql);for(let f of files)await ingestFileFull2(captureSql,f.sessionId,f.jsonlPath,f.projectPath,0,{parentSessionId:f.parentSessionId,isSubagent:f.isSubagent,workerMap})}catch{}},cfg.heartbeatIntervalMs);return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),timer2}return startBackfill2(captureSql).catch((err)=>{let msg=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"backfill_error",error:msg})}),null}catch(err){let message=err instanceof Error?err.message:String(err);return d.log({timestamp:d.now().toISOString(),level:"warn",event:"session_capture_init_failed",error:message}),null}}async function runHeartbeat(d){if(!running2)return;try{await collectHeartbeats(d),await collectMachineSnapshot(d),await emitWorkerEvents(d);try{let retSql=await d.getConnection();await retSql`DELETE FROM heartbeats WHERE created_at < now() - interval '7 days'`,await retSql`DELETE FROM machine_snapshots WHERE created_at < now() - interval '30 days'`,await retSql`DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days'`}catch{}}catch(err){let message=err instanceof Error?err.message:String(err);d.log({timestamp:d.now().toISOString(),level:"error",event:"heartbeat_error",error:message})}}let done=(async()=>{deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_started",daemon_id:daemonId,max_concurrent:config.maxConcurrent,poll_interval_ms:config.pollIntervalMs});try{await recoverOnStartup(deps,daemonId,config)}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"error",event:"recovery_error",error:message})}listenConnection=await setupListenNotify(deps,processTriggers),heartbeatTimer=setInterval(()=>runHeartbeat(deps),config.heartbeatIntervalMs),orphanTimer=startOrphanTimer(deps,config),inboxWatcherHandle=startInboxWatcherIfEnabled(deps),eventRouterHandle=await startEventRouterSafe(deps),captureFallbackTimer=await initSessionCapture(deps,config),await processTriggers();while(running2){if(await new Promise((resolve5)=>{pollResolve=resolve5,pollTimeout=setTimeout(resolve5,config.pollIntervalMs)}),pollResolve=null,!running2)break;await processTriggers()}deps.log({timestamp:deps.now().toISOString(),level:"info",event:"daemon_stopped",daemon_id:daemonId})})();return{stop,done,daemonId}}var RESUME_COOLDOWN_MS=60000,DEFAULT_MAX_RESUME_ATTEMPTS=3,previousWorkerStates;var init_scheduler_daemon=__esm(()=>{init_cron();init_event_router();init_inbox_watcher();init_run_spec();previousWorkerStates=new Map});var exports_serve={};__export(exports_serve,{registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isServeRunning:()=>isServeRunning,ensureTuiSession:()=>ensureTuiSession,autoStartServe:()=>autoStartServe});import{execSync as execSync6,spawn as spawn3}from"child_process";import{existsSync as existsSync24,mkdirSync as mkdirSync8,readFileSync as readFileSync11,unlinkSync as unlinkSync5,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir22}from"os";import{join as join33}from"path";function genieHome(){return process.env.GENIE_HOME??join33(homedir22(),".genie")}function servePidPath(){return join33(genieHome(),"serve.pid")}function genieTmuxConf(){return[join33(genieHome(),"tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function readServePid(){let path2=servePidPath();if(!existsSync24(path2))return null;let raw=readFileSync11(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writeServePid(pid){mkdirSync8(genieHome(),{recursive:!0}),writeFileSync8(servePidPath(),String(pid),"utf-8")}function removeServePid(){let path2=servePidPath();if(existsSync24(path2))try{unlinkSync5(path2)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function genieTmux(subcmd){return`tmux -L ${GENIE_SOCKET} -f ${genieTmuxConf()} ${subcmd}`}function tuiTmuxConf(){return[join33(genieHome(),"tui-tmux.conf")].find((p)=>existsSync24(p))??"/dev/null"}function tuiTmux(subcmd){return`tmux -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync6(genieTmux("list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function applyTuiStyle(){let cmds=[`set-option -t ${TUI_SESSION} pane-border-style 'fg=${TUI_STYLE.inactiveBorder}'`,`set-option -t ${TUI_SESSION} pane-active-border-style 'fg=${TUI_STYLE.activeBorder}'`,`set-option -t ${TUI_SESSION} mouse on`,`set-option -t ${TUI_SESSION} status off`,`set-option -t ${TUI_SESSION} pane-border-status off`];for(let cmd of cmds)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){let bindings=[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${TUI_SESSION}:0.1" "select-pane -t ${TUI_SESSION}:0.0"`,`bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${TUI_SESSION}:0.0) -gt 5 ]" "resize-pane -t ${TUI_SESSION}:0.0 -x 0" "resize-pane -t ${TUI_SESSION}:0.0 -x ${NAV_WIDTH}"`,`bind-key -T root C-t send-keys -t ${TUI_SESSION}:0.1 C-b c`,"bind-key -T root C-q detach-client"];for(let cmd of bindings)try{execSync6(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync6(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1367
1438
  `);return{leftPane:panes2[0],rightPane:panes2[1]||panes2[0]}}catch{}let cols=120;execSync6(tuiTmux(`new-session -d -s ${TUI_SESSION} -x ${cols} -y ${40}`),{stdio:"ignore"}),execSync6(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols-NAV_WIDTH-1}`),{stdio:"ignore"});let panes=execSync6(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
1368
- `);applyTuiStyle(),setupTuiKeybindings();try{execSync6(tuiTmux(`select-pane -t ${panes[0]}`),{stdio:"ignore"})}catch{}return{leftPane:panes[0],rightPane:panes[1]||panes[0]}}function sendTuiLaunchScript(leftPane,rightPane,workspaceRoot){let home=genieHome(),bunPath=process.execPath||"bun",genieBin=process.argv[1]||"genie",scriptPath=join31(home,"tui-launch.sh"),envVars=["GENIE_TUI_PANE=left",`GENIE_TUI_RIGHT=${rightPane}`];if(workspaceRoot)envVars.push(`GENIE_TUI_WORKSPACE=${workspaceRoot}`);let content=`#!/bin/sh
1439
+ `);applyTuiStyle(),setupTuiKeybindings();try{execSync6(tuiTmux(`select-pane -t ${panes[0]}`),{stdio:"ignore"})}catch{}return{leftPane:panes[0],rightPane:panes[1]||panes[0]}}function sendTuiLaunchScript(leftPane,rightPane,workspaceRoot){let home=genieHome(),bunPath=process.execPath||"bun",genieBin=process.argv[1]||"genie",scriptPath=join33(home,"tui-launch.sh"),envVars=["GENIE_TUI_PANE=left",`GENIE_TUI_RIGHT=${rightPane}`];if(workspaceRoot)envVars.push(`GENIE_TUI_WORKSPACE=${workspaceRoot}`);let content=`#!/bin/sh
1369
1440
  export ${envVars.join(`
1370
1441
  export `)}
1371
1442
  exec ${bunPath} ${genieBin}
1372
1443
  `;writeFileSync8(scriptPath,content,{mode:493});try{execSync6(tuiTmux(`send-keys -t '${leftPane}' '${scriptPath}' Enter`),{stdio:"ignore"})}catch{}}function killTuiSession(){try{execSync6(tuiTmux("kill-server"),{stdio:"ignore"})}catch{}}function listAgentSessions(){try{return execSync6(genieTmux("list-sessions -F '#{session_name}'"),{encoding:"utf-8"}).trim().split(`
1373
1444
  `).filter(Boolean)}catch{return[]}}function isServeRunning(){let pid=readServePid();return pid!==null&&isProcessAlive(pid)}async function autoStartServe(){if(isServeRunning())return;let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",{spawn:spawnChild}=await import("child_process");spawnChild(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}}).unref();let deadline=Date.now()+15000;while(Date.now()<deadline)if(await new Promise((resolve5)=>setTimeout(resolve5,500)),isServeRunning()&&isTuiSessionReady())return;if(!isServeRunning())throw Error("genie serve failed to start within 15s. Run `genie serve` manually.")}function isTuiSessionReady(){try{return execSync6(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"}),!0}catch{return!1}}function ensureTuiSession(workspaceRoot){if(isTuiSessionReady())return;let{leftPane,rightPane}=startTuiTmuxServer();sendTuiLaunchScript(leftPane,rightPane,workspaceRoot)}async function startAgentSync(){try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace)),ws=findWorkspace2();if(!ws)return null;let{syncAgentDirectory:syncAgentDirectory2,watchAgentDirectory:watchAgentDirectory2}=await Promise.resolve().then(() => (init_agent_sync(),exports_agent_sync)),syncResult=await syncAgentDirectory2(ws.root);if(syncResult.registered.length+syncResult.updated.length>0)console.log(` Agent sync: ${syncResult.registered.length} registered, ${syncResult.updated.length} updated`);let watcher2=watchAgentDirectory2(ws.root,{onSync:(name,action)=>{console.log(` [agent-watcher] ${name}: ${action}`)}});if(watcher2)console.log(" Agent watcher started (watching agents/ directory)");return watcher2}catch(err){let msg=err instanceof Error?err.message:String(err);return console.error(` Agent sync failed: ${msg}`),null}}async function startForeground(){let existingPid=readServePid();if(existingPid&&isProcessAlive(existingPid))console.log(`genie serve already running (PID ${existingPid})`),process.exit(0);if(existingPid)removeServePid();process.env.GENIE_IS_DAEMON="1",writeServePid(process.pid),console.log(`genie serve starting (PID ${process.pid})`),console.log(" Starting pgserve...");try{let{ensurePgserve:ensurePgserve2}=await Promise.resolve().then(() => (init_db(),exports_db)),port=await ensurePgserve2();console.log(` pgserve ready on port ${port}`)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(` pgserve failed: ${msg}`)}let sessions=listAgentSessions();if(sessions.length>0)console.log(` Agent server (-L ${GENIE_SOCKET}): ${sessions.length} sessions`);else console.log(` Agent server (-L ${GENIE_SOCKET}): no sessions yet (created on first spawn)`);handles.agentWatcher=await startAgentSync(),console.log(" Setting up TUI session...");let{leftPane,rightPane}=startTuiTmuxServer(),ws=(()=>{try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace));return findWorkspace2()}catch{return null}})();sendTuiLaunchScript(leftPane,rightPane,ws?.root),console.log(" TUI server ready (session: genie-tui)"),console.log(" Starting scheduler daemon...");try{let{startDaemon:startDaemon2}=await Promise.resolve().then(() => (init_scheduler_daemon(),exports_scheduler_daemon));handles.schedulerHandle=startDaemon2(),console.log(" Scheduler started (includes event-router + inbox-watcher)")}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(` Scheduler failed: ${msg}`)}console.log(`
1374
1445
  genie serve is running. Press Ctrl+C to stop.`);let shutdown2=()=>{console.log(`
1375
- Shutting down genie serve...`),handles.agentWatcher?.close(),handles.schedulerHandle?.stop(),killTuiSession(),removeServePid(),console.log("genie serve stopped.")};if(process.on("SIGTERM",()=>{shutdown2(),process.exit(143)}),process.on("SIGINT",()=>{shutdown2(),process.exit(130)}),handles.schedulerHandle)await handles.schedulerHandle.done;else await new Promise(()=>{});removeServePid()}async function startBackground(){let existingPid=readServePid();if(existingPid&&isProcessAlive(existingPid))console.log(`genie serve already running (PID ${existingPid})`),process.exit(0);if(existingPid)removeServePid();let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",child=spawn3(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}});if(child.unref(),child.pid)if(await new Promise((resolve5)=>setTimeout(resolve5,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)}async function stopServe(){let pid=readServePid();if(!pid){console.log("genie serve is not running (no PID file).");return}if(!isProcessAlive(pid)){console.log(`Stale PID file (PID ${pid} not running). Cleaning up.`),removeServePid(),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((resolve5)=>setTimeout(resolve5,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(),removeServePid(),console.log("genie serve stopped.")}async function printPgserveStatus(){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 printTmuxStatus(){let agentRunning=isGenieTmuxRunning(),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L ${GENIE_SOCKET}: ${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=join31(genieHome(),"scheduler.pid");if(existsSync24(schedulerPidPath)){let sPid=Number.parseInt(readFileSync11(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 statusServe(){let pid=readServePid(),running2=pid!==null&&isProcessAlive(pid);if(console.log(`
1446
+ Shutting down genie serve...`),handles.agentWatcher?.close(),handles.schedulerHandle?.stop(),killTuiSession(),removeServePid(),console.log("genie serve stopped.")};if(process.on("SIGTERM",()=>{shutdown2(),process.exit(143)}),process.on("SIGINT",()=>{shutdown2(),process.exit(130)}),handles.schedulerHandle)await handles.schedulerHandle.done;else await new Promise(()=>{});removeServePid()}async function startBackground(){let existingPid=readServePid();if(existingPid&&isProcessAlive(existingPid))console.log(`genie serve already running (PID ${existingPid})`),process.exit(0);if(existingPid)removeServePid();let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie",child=spawn3(bunPath,[genieBin,"serve","--foreground"],{detached:!0,stdio:"ignore",env:{...process.env,GENIE_IS_DAEMON:"1"}});if(child.unref(),child.pid)if(await new Promise((resolve5)=>setTimeout(resolve5,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)}async function stopServe(){let pid=readServePid();if(!pid){console.log("genie serve is not running (no PID file).");return}if(!isProcessAlive(pid)){console.log(`Stale PID file (PID ${pid} not running). Cleaning up.`),removeServePid(),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((resolve5)=>setTimeout(resolve5,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(),removeServePid(),console.log("genie serve stopped.")}async function printPgserveStatus(){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 printTmuxStatus(){let agentRunning=isGenieTmuxRunning(),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L ${GENIE_SOCKET}: ${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=join33(genieHome(),"scheduler.pid");if(existsSync24(schedulerPidPath)){let sPid=Number.parseInt(readFileSync11(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 statusServe(){let pid=readServePid(),running2=pid!==null&&isProcessAlive(pid);if(console.log(`
1376
1447
  Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${running2?"running":"stopped"}`),running2&&pid)console.log(` PID: ${pid}`);await printPgserveStatus(),printTmuxStatus(),await printDaemonStatus(running2),console.log(` PID file: ${servePidPath()}`),console.log("")}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)").action(async(options)=>{if(options.daemon)await startBackground();else await startForeground()}),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 GENIE_SOCKET="genie",TUI_SESSION="genie-tui",NAV_WIDTH=30,TUI_STYLE,handles;var init_serve=__esm(()=>{TUI_STYLE={activeBorder:"#7c3aed",inactiveBorder:"#414868"};handles={schedulerHandle:null,agentWatcher:null}});var exports_tmux2={};__export(exports_tmux2,{attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{execSync as execSync7,spawnSync as spawnSync2}from"child_process";function resolveRightPane(rightPane){try{return execSync7(`${TMUX} display-message -t ${rightPane} -p ''`,{stdio:"ignore"}),rightPane}catch{try{let panes=execSync7(`${TMUX} list-panes -t ${SESSION_NAME}:0 -F '#{pane_id}'`,{encoding:"utf-8"}).trim().split(`
1377
1448
  `);return panes[1]||panes[0]}catch{return rightPane}}}function attachProjectWindow(rightPane,targetSession,windowIndex){if(targetSession===SESSION_NAME)return;let pane=resolveRightPane(rightPane),agentTmux=`tmux -L ${GENIE_AGENT_SOCKET}`;try{execSync7(`${agentTmux} has-session -t '${targetSession}' 2>/dev/null`,{stdio:"ignore"})}catch{return}if(windowIndex!==void 0)try{execSync7(`${agentTmux} select-window -t '${targetSession}:${windowIndex}'`,{stdio:"ignore"})}catch{}try{execSync7(`${agentTmux} set-option -t '${targetSession}' status off 2>/dev/null`,{stdio:"ignore"})}catch{}try{let cmd=`while true; do TMUX='' ${agentTmux} attach-session -t '${targetSession}' 2>/dev/null; sleep 0.3; done`;execSync7(`${TMUX} respawn-pane -k -t ${pane} "bash -c '${cmd}'"`,{stdio:"ignore"})}catch{}try{execSync7(`${TMUX} select-pane -t ${SESSION_NAME}:0.0`,{stdio:"ignore"})}catch{}}function attachTuiSession(){spawnSync2("tmux",["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,"attach-session","-t",SESSION_NAME],{stdio:"inherit"})}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX;var init_tmux2=__esm(()=>{TUI_TMUX_CONF=(()=>{let{existsSync:existsSync25}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync25(tuiConf)?tuiConf:"/dev/null"})(),TMUX=`tmux -L ${TMUX_SOCKET} -f ${TUI_TMUX_CONF}`});function onBridgeEvent(handler){return listeners.add(handler),()=>{listeners.delete(handler)}}function emit2(event){for(let handler of listeners)handler(event)}async function listAgents2(){return(await getConnection())`
1378
1449
  SELECT a.id, a.custom_name, a.role, a.team, a.title, a.state,
1379
1450
  a.reports_to, a.current_executor_id, a.started_at
1380
1451
  FROM agents a
1381
1452
  ORDER BY a.started_at DESC
1382
- `}async function showAgent(id){let sql=await getConnection(),agents=await sql`
1453
+ `}async function showAgent2(id){let sql=await getConnection(),agents=await sql`
1383
1454
  SELECT id, custom_name, role, team, title, state,
1384
1455
  reports_to, current_executor_id, started_at
1385
1456
  FROM agents WHERE id = ${id}
@@ -1466,7 +1537,7 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
1466
1537
  COUNT(*) FILTER (WHERE status = 'in_progress') AS active,
1467
1538
  COUNT(*) AS total
1468
1539
  FROM teams
1469
- `]);return{agents:{online:Number(agentRows[0].online),total:Number(agentRows[0].total)},tasks:{active:Number(taskRows[0].active),backlog:Number(taskRows[0].backlog),done:Number(taskRows[0].done),total:Number(taskRows[0].total)},teams:{active:Number(teamRows[0].active),total:Number(teamRows[0].total)}}}async function startListening(){if(listenersActive)return;listenersActive=!0;let sql=await getConnection(),executorListener=await sql.listen("genie_executor_state",(payload)=>{try{emit2({type:"executor-state-changed",payload:JSON.parse(payload)})}catch{emit2({type:"executor-state-changed",payload:{raw:payload}})}}),taskListener=await sql.listen("genie_task_stage",(payload)=>{try{emit2({type:"task-stage-changed",payload:JSON.parse(payload)})}catch{emit2({type:"task-stage-changed",payload:{raw:payload}})}}),eventListener=await sql.listen("genie_runtime_event",(payload)=>{try{emit2({type:"runtime-event",payload:JSON.parse(payload)})}catch{emit2({type:"runtime-event",payload:{raw:payload}})}});stopFns=[()=>executorListener.unlisten(),()=>taskListener.unlisten(),()=>eventListener.unlisten()]}async function stopListening(){listenersActive=!1,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var listeners,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();listeners=new Set;stopFns=[]});import{randomUUID as randomUUID6}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=randomUUID6(),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});import{existsSync as existsSync25,mkdirSync as mkdirSync9,readFileSync as readFileSync12,writeFileSync as writeFileSync9}from"fs";import{homedir as homedir21}from"os";import{join as join32}from"path";function ensureAppHome(){if(!existsSync25(APP_HOME))mkdirSync9(APP_HOME,{recursive:!0})}function readRegistry(){if(ensureAppHome(),!existsSync25(REGISTRY_PATH))return[];try{return JSON.parse(readFileSync12(REGISTRY_PATH,"utf-8"))}catch{return[]}}function writeRegistry(workspaces){ensureAppHome(),writeFileSync9(REGISTRY_PATH,JSON.stringify(workspaces,null,2))}async function listWorkspaces(){return readRegistry()}async function initWorkspace(basePath,name,pgUrl){let resolvedName=name??basePath.split("/").pop()??"workspace",now=new Date().toISOString(),genieDir=join32(basePath,".genie");if(!existsSync25(genieDir))mkdirSync9(genieDir,{recursive:!0});let wsConfig={name:resolvedName,pgUrl:pgUrl??null,created:now};writeFileSync9(join32(genieDir,"workspace.json"),JSON.stringify(wsConfig,null,2));let registry=readRegistry(),existing=registry.findIndex((w)=>w.path===basePath),entry={path:basePath,name:resolvedName,pgUrl:pgUrl??null,created:now,lastOpened:now};if(existing>=0)registry[existing]=entry;else registry.push(entry);return writeRegistry(registry),entry}async function openWorkspace(path2){let registry=readRegistry(),idx=registry.findIndex((w)=>w.path===path2);if(idx<0)throw Error(`Workspace not found in registry: ${path2}`);let wsConfigPath=join32(path2,".genie","workspace.json");if(!existsSync25(wsConfigPath))throw Error(`Workspace config missing at ${wsConfigPath}`);return registry[idx].lastOpened=new Date().toISOString(),writeRegistry(registry),registry[idx]}async function removeWorkspace(path2){let registry=readRegistry(),filtered=registry.filter((w)=>w.path!==path2);if(filtered.length===registry.length)return!1;return writeRegistry(filtered),!0}var APP_HOME,REGISTRY_PATH;var init_workspace2=__esm(()=>{APP_HOME=join32(homedir21(),".genie-app"),REGISTRY_PATH=join32(APP_HOME,"workspaces.json")});async function list_agents(){return listAgents2()}async function show_agent(params){return showAgent(params.id)}async function list_tasks(params){return listTasks2(params.boardId)}async function kanban_board(params){return kanbanBoard(params.boardId)}async function list_boards(){return listBoards()}async function move_task(params){return{ok:await moveTask2(params.taskId,params.columnName)}}async function list_teams(){return listTeams3()}async function dashboard_stats(){return dashboardStats()}async function stream_events(params){return streamEvents(params)}async function spawn_terminal(params){if(params.agentName){let session2=await spawnForAgent(params.agentName,{cwd:params.cwd,cols:params.cols,rows:params.rows});return{sessionId:session2.id,agentId:session2.agentId,executorId:session2.executorId}}return{sessionId:spawnBash(params.cwd).id,agentId:null,executorId:null}}function write_terminal(params){return{ok:writeTerminal(params.sessionId,params.data)}}function resize_terminal(params){return{ok:resizeTerminal(params.sessionId,params.cols,params.rows)}}async function kill_terminal(params){return{ok:await killTerminal(params.sessionId)}}async function list_workspaces(){return listWorkspaces()}async function open_workspace(params){return openWorkspace(params.path)}async function init_workspace3(params){return initWorkspace(params.path,params.name,params.pgUrl)}async function remove_workspace(params){return{ok:await removeWorkspace(params.path)}}var commands;var init_ipc=__esm(()=>{init_pg_bridge();init_pty();init_workspace2();commands={list_agents,show_agent,list_tasks,kanban_board,list_boards,move_task,list_teams,dashboard_stats,stream_events,spawn_terminal,write_terminal,resize_terminal,kill_terminal,list_workspaces,open_workspace,init_workspace:init_workspace3,remove_workspace}});var exports_src_backend={};async function start(){console.log("[genie-app] Starting backend sidecar..."),await getConnection(),console.log("[genie-app] PG connected"),await startListening(),console.log("[genie-app] PG LISTEN active (executor_state, task_stage, runtime_event)"),onPtyData((sessionId,data)=>{emitToFrontend("pty-data",{sessionId,data})}),onPtyExit((sessionId,code)=>{emitToFrontend("pty-exit",{sessionId,code})}),onBridgeEvent((event)=>{emitToFrontend(event.type,event.payload)}),listenForCommands(),console.log("[genie-app] Backend ready")}function emitToFrontend(type2,payload){let message=JSON.stringify({type:"event",event:type2,payload});process.stdout.write(`${message}
1540
+ `]);return{agents:{online:Number(agentRows[0].online),total:Number(agentRows[0].total)},tasks:{active:Number(taskRows[0].active),backlog:Number(taskRows[0].backlog),done:Number(taskRows[0].done),total:Number(taskRows[0].total)},teams:{active:Number(teamRows[0].active),total:Number(teamRows[0].total)}}}async function startListening(){if(listenersActive)return;listenersActive=!0;let sql=await getConnection(),executorListener=await sql.listen("genie_executor_state",(payload)=>{try{emit2({type:"executor-state-changed",payload:JSON.parse(payload)})}catch{emit2({type:"executor-state-changed",payload:{raw:payload}})}}),taskListener=await sql.listen("genie_task_stage",(payload)=>{try{emit2({type:"task-stage-changed",payload:JSON.parse(payload)})}catch{emit2({type:"task-stage-changed",payload:{raw:payload}})}}),eventListener=await sql.listen("genie_runtime_event",(payload)=>{try{emit2({type:"runtime-event",payload:JSON.parse(payload)})}catch{emit2({type:"runtime-event",payload:{raw:payload}})}});stopFns=[()=>executorListener.unlisten(),()=>taskListener.unlisten(),()=>eventListener.unlisten()]}async function stopListening(){listenersActive=!1,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var listeners,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();listeners=new Set;stopFns=[]});import{randomUUID as randomUUID6}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=randomUUID6(),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});import{existsSync as existsSync25,mkdirSync as mkdirSync9,readFileSync as readFileSync12,writeFileSync as writeFileSync9}from"fs";import{homedir as homedir23}from"os";import{join as join34}from"path";function ensureAppHome(){if(!existsSync25(APP_HOME))mkdirSync9(APP_HOME,{recursive:!0})}function readRegistry(){if(ensureAppHome(),!existsSync25(REGISTRY_PATH))return[];try{return JSON.parse(readFileSync12(REGISTRY_PATH,"utf-8"))}catch{return[]}}function writeRegistry(workspaces){ensureAppHome(),writeFileSync9(REGISTRY_PATH,JSON.stringify(workspaces,null,2))}async function listWorkspaces(){return readRegistry()}async function initWorkspace(basePath,name,pgUrl){let resolvedName=name??basePath.split("/").pop()??"workspace",now=new Date().toISOString(),genieDir=join34(basePath,".genie");if(!existsSync25(genieDir))mkdirSync9(genieDir,{recursive:!0});let wsConfig={name:resolvedName,pgUrl:pgUrl??null,created:now};writeFileSync9(join34(genieDir,"workspace.json"),JSON.stringify(wsConfig,null,2));let registry=readRegistry(),existing=registry.findIndex((w)=>w.path===basePath),entry={path:basePath,name:resolvedName,pgUrl:pgUrl??null,created:now,lastOpened:now};if(existing>=0)registry[existing]=entry;else registry.push(entry);return writeRegistry(registry),entry}async function openWorkspace(path2){let registry=readRegistry(),idx=registry.findIndex((w)=>w.path===path2);if(idx<0)throw Error(`Workspace not found in registry: ${path2}`);let wsConfigPath=join34(path2,".genie","workspace.json");if(!existsSync25(wsConfigPath))throw Error(`Workspace config missing at ${wsConfigPath}`);return registry[idx].lastOpened=new Date().toISOString(),writeRegistry(registry),registry[idx]}async function removeWorkspace(path2){let registry=readRegistry(),filtered=registry.filter((w)=>w.path!==path2);if(filtered.length===registry.length)return!1;return writeRegistry(filtered),!0}var APP_HOME,REGISTRY_PATH;var init_workspace2=__esm(()=>{APP_HOME=join34(homedir23(),".genie-app"),REGISTRY_PATH=join34(APP_HOME,"workspaces.json")});async function list_agents(){return listAgents2()}async function show_agent(params){return showAgent2(params.id)}async function list_tasks(params){return listTasks2(params.boardId)}async function kanban_board(params){return kanbanBoard(params.boardId)}async function list_boards(){return listBoards()}async function move_task(params){return{ok:await moveTask2(params.taskId,params.columnName)}}async function list_teams(){return listTeams3()}async function dashboard_stats(){return dashboardStats()}async function stream_events(params){return streamEvents(params)}async function spawn_terminal(params){if(params.agentName){let session2=await spawnForAgent(params.agentName,{cwd:params.cwd,cols:params.cols,rows:params.rows});return{sessionId:session2.id,agentId:session2.agentId,executorId:session2.executorId}}return{sessionId:spawnBash(params.cwd).id,agentId:null,executorId:null}}function write_terminal(params){return{ok:writeTerminal(params.sessionId,params.data)}}function resize_terminal(params){return{ok:resizeTerminal(params.sessionId,params.cols,params.rows)}}async function kill_terminal(params){return{ok:await killTerminal(params.sessionId)}}async function list_workspaces(){return listWorkspaces()}async function open_workspace(params){return openWorkspace(params.path)}async function init_workspace3(params){return initWorkspace(params.path,params.name,params.pgUrl)}async function remove_workspace(params){return{ok:await removeWorkspace(params.path)}}var commands;var init_ipc=__esm(()=>{init_pg_bridge();init_pty();init_workspace2();commands={list_agents,show_agent,list_tasks,kanban_board,list_boards,move_task,list_teams,dashboard_stats,stream_events,spawn_terminal,write_terminal,resize_terminal,kill_terminal,list_workspaces,open_workspace,init_workspace:init_workspace3,remove_workspace}});var exports_src_backend={};async function start(){console.log("[genie-app] Starting backend sidecar..."),await getConnection(),console.log("[genie-app] PG connected"),await startListening(),console.log("[genie-app] PG LISTEN active (executor_state, task_stage, runtime_event)"),onPtyData((sessionId,data)=>{emitToFrontend("pty-data",{sessionId,data})}),onPtyExit((sessionId,code)=>{emitToFrontend("pty-exit",{sessionId,code})}),onBridgeEvent((event)=>{emitToFrontend(event.type,event.payload)}),listenForCommands(),console.log("[genie-app] Backend ready")}function emitToFrontend(type2,payload){let message=JSON.stringify({type:"event",event:type2,payload});process.stdout.write(`${message}
1470
1541
  `)}function listenForCommands(){let buffer2="";process.stdin.setEncoding("utf-8"),process.stdin.on("data",(chunk)=>{buffer2+=chunk;let lines=buffer2.split(`
1471
1542
  `);buffer2=lines.pop()??"";for(let line of lines){if(!line.trim())continue;handleCommand(line)}}),process.stdin.on("end",()=>{shutdown2()})}async function handleCommand(line){let id;try{let msg=JSON.parse(line);id=msg.id;let handler=commands[msg.command];if(!handler){respond(id,null,`Unknown command: ${msg.command}`);return}let result=await handler(msg.params);respond(id,result,null)}catch(err){let message=err instanceof Error?err.message:String(err);respond(id,null,message)}}function respond(id,result,error2){let message=JSON.stringify({type:"response",id,result,error:error2});process.stdout.write(`${message}
1472
1543
  `)}async function shutdown2(){if(shutdownRequested)return;shutdownRequested=!0,console.log("[genie-app] Shutting down..."),await killAll(),await stopListening(),console.log("[genie-app] Shutdown complete"),process.exit(0)}var shutdownRequested=!1;var init_src_backend=__esm(()=>{init_db();init_ipc();init_pg_bridge();init_pty();process.env.GENIE_APP="1";process.on("SIGTERM",()=>void shutdown2());process.on("SIGINT",()=>void shutdown2());start().catch((err)=>{console.error("[genie-app] Fatal error:",err),process.exit(1)})});var exports_board_service={};__export(exports_board_service,{updateColumn:()=>updateColumn,updateBoard:()=>updateBoard,reorderColumns:()=>reorderColumns,removeColumn:()=>removeColumn,reconcileBoard:()=>reconcileBoard,listBoards:()=>listBoards2,importBoard:()=>importBoard,getColumns:()=>getColumns,getBoard:()=>getBoard,exportBoard:()=>exportBoard,deleteBoard:()=>deleteBoard,createBoard:()=>createBoard,addColumn:()=>addColumn});function str3(v){return v!=null?String(v):null}function strOrDefault2(v,def){return v!=null?String(v):def}function parseJsonb(val,fallback){if(val==null)return fallback;if(typeof val==="string")try{return JSON.parse(val)}catch{return fallback}return val}function mapBoard(row){return{id:row.id,name:row.name,projectId:str3(row.project_id),description:str3(row.description),status:strOrDefault2(row.status,"active"),archivedAt:str3(row.archived_at),columns:parseJsonb(row.columns,[]),config:parseJsonb(row.config,{}),createdAt:strOrDefault2(row.created_at,""),updatedAt:strOrDefault2(row.updated_at,"")}}function generateColumnId(){return crypto.randomUUID()}function fillColumnDefaults(col,position){return{id:col.id??generateColumnId(),name:col.name??`column-${position}`,label:col.label??col.name??`Column ${position}`,gate:col.gate??"human",action:col.action??null,auto_advance:col.auto_advance??!1,transitions:col.transitions??[],roles:col.roles??["*"],color:col.color??"#94a3b8",parallel:col.parallel??!1,on_fail:col.on_fail??null,position}}async function createBoard(input){let sql=await getConnection(),columns=[];if(input.fromTemplate){let tmplRows=await sql`
@@ -1527,44 +1598,7 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
1527
1598
  UPDATE board_templates SET columns = ${sql.json(columns)}, updated_at = now()
1528
1599
  WHERE id = ${templateId}
1529
1600
  RETURNING *
1530
- `;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()});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())`
1531
- SELECT e.ended_at FROM executors e
1532
- JOIN agents a ON e.agent_id = a.id
1533
- WHERE a.custom_name = ${options.agent}
1534
- AND a.team = ${options.team}
1535
- AND e.ended_at IS NOT NULL
1536
- ORDER BY e.ended_at DESC
1537
- LIMIT 1
1538
- `;if(rows.length>0&&rows[0].ended_at){let endedAt=rows[0].ended_at;return endedAt instanceof Date?endedAt.toISOString():String(endedAt)}}return new Date(Date.now()-86400000).toISOString()}async function getTaskMessages(team,since){return(await(await getConnection())`
1539
- SELECT m.body, m.sender_type, m.sender_id, m.created_at,
1540
- t.id AS task_id, t.title AS task_title
1541
- FROM messages m
1542
- JOIN conversations c ON m.conversation_id = c.id
1543
- JOIN tasks t ON c.linked_entity_id = t.id
1544
- WHERE c.linked_entity = 'task'
1545
- AND t.team_name = ${team}
1546
- AND m.created_at > ${since}
1547
- ORDER BY m.created_at ASC
1548
- LIMIT 100
1549
- `).map((r)=>({taskId:r.task_id,taskTitle:r.task_title,senderType:r.sender_type,senderId:r.sender_id,body:r.body,createdAt:r.created_at instanceof Date?r.created_at.toISOString():String(r.created_at)}))}async function getTeamRoster(teamName){return(await(await getConnection())`
1550
- SELECT a.custom_name AS agent_id, a.role, e.state AS executor_state, e.started_at AS executor_started_at
1551
- FROM agents a
1552
- LEFT JOIN executors e ON a.current_executor_id = e.id
1553
- WHERE a.team = ${teamName}
1554
- ORDER BY a.custom_name
1555
- `).map((r)=>({agentId:r.agent_id,role:r.role??null,executorState:r.executor_state??null,executorStartedAt:r.executor_started_at?r.executor_started_at instanceof Date?r.executor_started_at.toISOString():String(r.executor_started_at):null}))}async function getPendingRequestMessages(team){return(await(await getConnection())`
1556
- SELECT m.id, m.request_type, m.sender_id, m.body, m.created_at
1557
- FROM messages m
1558
- JOIN conversations c ON m.conversation_id = c.id
1559
- JOIN tasks t ON c.linked_entity_id = t.id
1560
- WHERE c.linked_entity = 'task'
1561
- AND t.team_name = ${team}
1562
- AND m.request_type IS NOT NULL
1563
- AND m.request_status = 'pending'
1564
- ORDER BY m.created_at DESC
1565
- LIMIT 100
1566
- `).map((r)=>({id:r.id,requestType:r.request_type,senderId:r.sender_id,body:r.body,createdAt:r.created_at instanceof Date?r.created_at.toISOString():String(r.created_at)}))}async function generateBrief(options){let teamConfig=await getTeam(options.team);if(!teamConfig)throw Error(`Team not found: ${options.team}`);let since=await resolveSince(options),repoPath=options.repoPath??teamConfig.repo,agentName=options.agent??teamConfig.leader??null,agentIdentifiers=agentName?[agentName]:[],[unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster]=await Promise.all([agentIdentifiers.length>0?getUnread(repoPath,agentIdentifiers):Promise.resolve([]),getTaskMessages(options.team,since),listRuntimeEvents({team:options.team,since,limit:100}),getPendingRequestMessages(options.team),getTeamRoster(options.team)]);return{team:options.team,agent:agentName,since,unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster}}function truncate2(text,max){return text.length>max?`${text.slice(0,max)}...`:text}function formatUnreadSection(messages2){if(messages2.length===0)return[];let lines=[`## Unread Messages (${messages2.length})`];for(let msg of messages2)lines.push(`- **${msg.from}**: ${truncate2(msg.body,120)}`);return lines.push(""),lines}function formatTaskMessagesSection(messages2){if(messages2.length===0)return[];let lines=[`## Task Updates (${messages2.length})`],byTask=new Map;for(let msg of messages2){let existing=byTask.get(msg.taskId)??[];existing.push(msg),byTask.set(msg.taskId,existing)}for(let[taskId,msgs]of byTask){lines.push(`### ${taskId}: ${msgs[0].taskTitle}`);for(let msg of msgs)lines.push(`- [${msg.senderType}:${msg.senderId}] ${truncate2(msg.body,100)}`)}return lines.push(""),lines}function formatRequestsSection(requests){if(requests.length===0)return[];let lines=[`## Pending Requests (${requests.length})`];for(let req of requests)lines.push(`- [${req.requestType}] ${req.senderId}: ${truncate2(req.body,80)}`);return lines.push(""),lines}function formatRosterSection(roster){if(roster.length===0)return[];let lines=[`## Team Roster (${roster.length})`];for(let member of roster){let state=member.executorState??"offline",icon=STATE_ICONS[state]??"\u25CC";lines.push(`- ${icon} **${member.agentId}** (${member.role??"unassigned"}): ${state}`)}return lines.push(""),lines}function formatEventsSection(events){if(events.length===0)return[];let lines=[`## Recent Events (${events.length})`],tail=events.slice(-10);for(let evt of tail){let ts3=evt.timestamp.slice(11,16);lines.push(`- ${ts3} [${evt.kind}] ${evt.agent}: ${truncate2(evt.text,80)}`)}if(events.length>10)lines.push(` _(${events.length-10} more events)_`);return lines.push(""),lines}function formatBrief(brief){let lines=[`# BRIEF \u2014 ${brief.team}${brief.agent?` ${brief.agent}`:""}`,`Since: ${brief.since}`,"",...formatUnreadSection(brief.unreadMessages),...formatTaskMessagesSection(brief.taskMessages),...formatRequestsSection(brief.pendingRequests),...formatRosterSection(brief.teamRoster),...formatEventsSection(brief.recentEvents)];if(!(brief.unreadMessages.length+brief.taskMessages.length+brief.pendingRequests.length+brief.recentEvents.length>0))lines.push("_No activity since last session._","");return lines.join(`
1567
- `)}var STATE_ICONS;var init_brief=__esm(()=>{init_db();init_mailbox();init_runtime_events();init_team_manager();STATE_ICONS={working:"\u25CF",idle:"\u25CB",error:"\u2718"}});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,getInbox:()=>getInbox});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){let start2=Date.now();while(Date.now()-start2<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}async function findLiveWorkerFuzzy(recipientId){let matches=await resolveRecipient(recipientId);return matches.length===1?matches[0]:null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(!process.env.TMUX)return null;let templates=await listTemplates(),candidates=[worker?.role,worker?.id,recipientId].filter((v)=>Boolean(v)),uniqueCandidates=[...new Set(candidates)],workerTeam=worker?.team,template=templates.find((t)=>{if(workerTeam&&t.team!==workerTeam)return!1;return uniqueCandidates.some((q)=>t.id===q||t.role===q||`${t.team}:${t.role}`===q)});if(!template)return null;let resumeSessionId=template.provider==="claude"&&worker?.claudeSessionId?worker.claudeSessionId:void 0;try{if(await cleanupDeadWorkers(recipientId,workerTeam),worker)await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn)),result=await spawnWorkerFromTemplate2(template,resumeSessionId);if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await waitForWorkerReady(result.paneId),!await isPaneAlive(result.paneId))return await unregister(result.worker.id),null;return{worker:result.worker,respawned:!0}}catch{return null}}async function cleanupDeadWorkers(recipientId,team){let allWorkers=await list();for(let w of allWorkers){if(team&&w.team!==team)continue;if(!(w.role===recipientId||w.id===recipientId))continue;if(await isPaneAlive(w.paneId))continue;await unregister(w.id)}}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to;try{let message=await send(repoPath,from,to,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,to,message.id),{messageId:message.id,workerId:to,delivered:!0}}catch{return null}}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let liveMatches=await resolveRecipient(to);if(liveMatches.length===1)return deliverToWorker(repoPath,from,liveMatches[0],body);if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve5}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve5(to),worker=await get(to);if(!worker){let allWorkers=await list();worker=allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}if(dirResolved||worker){let alive=await ensureWorkerAlive(worker,to);if(alive)return deliverToWorker(repoPath,from,alive.worker,body)}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve5)=>setTimeout(resolve5,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_mailbox();init_orchestrator();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 randomUUID7}from"crypto";async function createAssignment(executorId,taskId,wishSlug,groupNumber){let sql=await getConnection(),id=randomUUID7(),now=new Date().toISOString(),rows=await sql`
1601
+ `;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()});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,getInbox:()=>getInbox});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){let start2=Date.now();while(Date.now()-start2<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}async function findLiveWorkerFuzzy(recipientId){let matches=await resolveRecipient(recipientId);return matches.length===1?matches[0]:null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(!process.env.TMUX)return null;let templates=await listTemplates(),candidates=[worker?.role,worker?.id,recipientId].filter((v)=>Boolean(v)),uniqueCandidates=[...new Set(candidates)],workerTeam=worker?.team,template=templates.find((t)=>{if(workerTeam&&t.team!==workerTeam)return!1;return uniqueCandidates.some((q)=>t.id===q||t.role===q||`${t.team}:${t.role}`===q)});if(!template)return null;let resumeSessionId=template.provider==="claude"&&worker?.claudeSessionId?worker.claudeSessionId:void 0;try{if(await cleanupDeadWorkers(recipientId,workerTeam),worker)await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn)),result=await spawnWorkerFromTemplate2(template,resumeSessionId);if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await waitForWorkerReady(result.paneId),!await isPaneAlive(result.paneId))return await unregister(result.worker.id),null;return{worker:result.worker,respawned:!0}}catch{return null}}async function cleanupDeadWorkers(recipientId,team){let allWorkers=await list();for(let w of allWorkers){if(team&&w.team!==team)continue;if(!(w.role===recipientId||w.id===recipientId))continue;if(await isPaneAlive(w.paneId))continue;await unregister(w.id)}}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to;try{let message=await send(repoPath,from,to,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,to,message.id),{messageId:message.id,workerId:to,delivered:!0}}catch{return null}}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let liveMatches=await resolveRecipient(to);if(liveMatches.length===1)return deliverToWorker(repoPath,from,liveMatches[0],body);if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve5}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve5(to),worker=await get(to);if(!worker){let allWorkers=await list();worker=allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}if(dirResolved||worker){let alive=await ensureWorkerAlive(worker,to);if(alive)return deliverToWorker(repoPath,from,alive.worker,body)}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve5)=>setTimeout(resolve5,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_mailbox();init_orchestrator();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 randomUUID7}from"crypto";async function createAssignment(executorId,taskId,wishSlug,groupNumber){let sql=await getConnection(),id=randomUUID7(),now=new Date().toISOString(),rows=await sql`
1568
1602
  INSERT INTO assignments (id, executor_id, task_id, wish_slug, group_number, started_at)
1569
1603
  VALUES (${id}, ${executorId}, ${taskId}, ${wishSlug??null}, ${groupNumber??null}, ${now})
1570
1604
  RETURNING *
@@ -1584,8 +1618,12 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
1584
1618
  SELECT * FROM assignments
1585
1619
  WHERE executor_id = ${executorId}
1586
1620
  ORDER BY started_at ASC
1587
- `).map(rowToAssignment)}async function getAssignment(id){let rows=await(await getConnection())`SELECT * FROM assignments WHERE id = ${id}`;return rows.length>0?rowToAssignment(rows[0]):null}var init_assignment_registry=__esm(()=>{init_db()});var exports_team={};__export(exports_team,{registerTeamNamespace:()=>registerTeamNamespace,handleTeamCreate:()=>handleTeamCreate});import{existsSync as existsSync31}from"fs";import{copyFile as copyFile2,cp,mkdir as mkdir5}from"fs/promises";import{join as join38,resolve as resolve5}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--tmux-session <name>","Tmux session to place team window in (default: derived from repo path)").option("--session <name>","Alias for --tmux-session (deprecated)").option("--no-spawn","Create team and copy wish without spawning the leader (useful for testing)").action(async(name,options)=>{try{let merged={...options,tmuxSession:options.tmuxSession??options.session};await handleTeamCreate(name,merged)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--all","Include archived teams").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("archive <name>").description("Archive a team (preserves all data, kills members)").action(async(name)=>{try{if(await archiveTeam(name))console.log(`Team "${name}" archived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("unarchive <name>").description("Restore an archived team").action(async(name)=>{try{if(await unarchiveTeam(name))console.log(`Team "${name}" unarchived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team (archives \u2014 preserves data). Use `genie team archive` directly.").action(async(name)=>{try{if(await disbandTeam(name))console.log("Note: disband now archives the team. Use `genie team archive` directly."),console.log(`Team "${name}" disbanded (archived).`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("cleanup").description("Kill tmux windows for done/archived teams").option("--dry-run","Show what would be cleaned without doing it").action(async(options)=>{try{await handleTeamCleanup(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve5(options.repo);if(options.wish){let wishPath=join38(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync31(wishPath)){let cwdWishDir=join38(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join38(cwdWishDir,"WISH.md");if(existsSync31(cwdWishPath)){let destDir=join38(resolvedRepo,".genie","wishes",options.wish);await mkdir5(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve5(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config),config.leader=slug,config.spawner=process.env.GENIE_AGENT_NAME||"cli",await updateTeamConfig(config.name,config);let sourceWishPath=join38(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync31(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join38(config.worktreePath,".genie","wishes",slug);await mkdir5(destWishDir,{recursive:!0});let destWishPath=join38(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let leaderName=config.leader||"team-lead",standardTeam=[leaderName,"engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!==leaderName).join(", "),spawner=config.spawner||"cli",kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2(leaderName,{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli",leaderName,kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${leaderName} failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2,includeArchived){let teams=await listTeams2(includeArchived);if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress",dimmed=status==="archived"?"\x1B[90m":"",reset2=status==="archived"?"\x1B[0m":"";console.log(` ${dimmed}${t.name} [${status}]${reset2}`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}async function findTeamWindow(sessionName,teamName){let tmuxLib=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(!await tmuxLib.findSessionByName(sessionName))return null;try{return(await tmuxLib.listWindows(sessionName)).find((w)=>w.name===teamName||w.name===teamName.replace(/\./g,"_"))??null}catch{return null}}async function cleanupTeamWindow(t,dryRun){if(!t.tmuxSessionName)return null;let match=await findTeamWindow(t.tmuxSessionName,t.name);if(!match)return null;if(dryRun)return` [dry-run] Would kill window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}" [${t.status}])`;if(!await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).killWindow(t.tmuxSessionName,match.name))return null;return` Killed window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}")`}async function handleTeamCleanup(options){let cleanable=(await listTeams2(!0)).filter((t)=>t.status==="done"||t.status==="archived");if(cleanable.length===0){console.log("No done/archived teams to clean up.");return}let cleaned=0;for(let t of cleanable){let msg=await cleanupTeamWindow(t,options.dryRun===!0);if(msg)console.log(msg),cleaned++}let verb=options.dryRun?"Would clean":"Cleaned";if(cleaned===0)console.log("No tmux windows found for done/archived teams.");else console.log(`
1588
- ${verb} ${cleaned} window${cleaned===1?"":"s"}.`)}var init_team=__esm(()=>{init_team_manager()});var exports_wish_sync={};__export(exports_wish_sync,{syncWishes:()=>syncWishes,parseWishStatus:()=>parseWishStatus,listWishes:()=>listWishes,getWish:()=>getWish});import{existsSync as existsSync32,readFileSync as readFileSync16,readdirSync as readdirSync7}from"fs";import{basename as basename6,join as join39}from"path";function parseWishStatus(content){let match=content.match(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)/i);if(match)return match[1].trim();return"DRAFT"}function scanRepoWishes(repoPath){let wishesDir=join39(repoPath,".genie","wishes");if(!existsSync32(wishesDir))return[];let results=[],namespace=basename6(repoPath);try{let entries=readdirSync7(wishesDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let wishPath=join39(wishesDir,entry.name,"WISH.md");if(!existsSync32(wishPath))continue;try{let content=readFileSync16(wishPath,"utf-8");results.push({slug:entry.name,repo:repoPath,namespace,status:parseWishStatus(content),filePath:wishPath})}catch{}}}catch{}return results}function discoverWishes(repoPath){if(repoPath)return scanRepoWishes(repoPath);if(!existsSync32(REPOS_BASE2))return[];let results=[];try{let entries=readdirSync7(REPOS_BASE2,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;results.push(...scanRepoWishes(join39(REPOS_BASE2,entry.name)))}}catch{}return results}async function syncWishes(repoPath){let wishes=discoverWishes(repoPath);if(wishes.length===0)return 0;let sql=await getConnection();for(let wish of wishes)await sql`
1621
+ `).map(rowToAssignment)}async function getAssignment(id){let rows=await(await getConnection())`SELECT * FROM assignments WHERE id = ${id}`;return rows.length>0?rowToAssignment(rows[0]):null}var init_assignment_registry=__esm(()=>{init_db()});var exports_team={};__export(exports_team,{registerTeamNamespace:()=>registerTeamNamespace,handleTeamCreate:()=>handleTeamCreate});import{existsSync as existsSync31}from"fs";import{copyFile as copyFile2,cp,mkdir as mkdir5}from"fs/promises";import{join as join40,resolve as resolve5}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--tmux-session <name>","Tmux session to place team window in (default: derived from repo path)").option("--session <name>","Alias for --tmux-session (deprecated)").option("--no-spawn","Create team and copy wish without spawning the leader (useful for testing)").addHelpText("after",`
1622
+ Examples:
1623
+ genie team create my-feature --repo . # Create team in current repo
1624
+ genie team create my-feature --repo . --wish my-feature-slug # Create team with a wish
1625
+ genie team create hotfix --repo . --branch main # Create from main branch`).action(async(name,options)=>{try{let merged={...options,tmuxSession:options.tmuxSession??options.session};await handleTeamCreate(name,merged)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--all","Include archived teams").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("archive <name>").description("Archive a team (preserves all data, kills members)").action(async(name)=>{try{if(await archiveTeam(name))console.log(`Team "${name}" archived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("unarchive <name>").description("Restore an archived team").action(async(name)=>{try{if(await unarchiveTeam(name))console.log(`Team "${name}" unarchived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team (archives \u2014 preserves data). Use `genie team archive` directly.").action(async(name)=>{try{if(await disbandTeam(name))console.log("Note: disband now archives the team. Use `genie team archive` directly."),console.log(`Team "${name}" disbanded (archived).`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("cleanup").description("Kill tmux windows for done/archived teams").option("--dry-run","Show what would be cleaned without doing it").action(async(options)=>{try{await handleTeamCleanup(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve5(options.repo);if(options.wish){let wishPath=join40(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync31(wishPath)){let cwdWishDir=join40(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join40(cwdWishDir,"WISH.md");if(existsSync31(cwdWishPath)){let destDir=join40(resolvedRepo,".genie","wishes",options.wish);await mkdir5(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve5(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config),config.leader=slug,config.spawner=process.env.GENIE_AGENT_NAME||"cli",await updateTeamConfig(config.name,config);let sourceWishPath=join40(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync31(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join40(config.worktreePath,".genie","wishes",slug);await mkdir5(destWishDir,{recursive:!0});let destWishPath=join40(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let leaderName=config.leader||"team-lead",standardTeam=[leaderName,"engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!==leaderName).join(", "),spawner=config.spawner||"cli",kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2(leaderName,{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli",leaderName,kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${leaderName} failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2,includeArchived){let teams=await listTeams2(includeArchived);if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress",dimmed=status==="archived"?"\x1B[90m":"",reset2=status==="archived"?"\x1B[0m":"";console.log(` ${dimmed}${t.name} [${status}]${reset2}`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}async function findTeamWindow(sessionName,teamName){let tmuxLib=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(!await tmuxLib.findSessionByName(sessionName))return null;try{return(await tmuxLib.listWindows(sessionName)).find((w)=>w.name===teamName||w.name===teamName.replace(/\./g,"_"))??null}catch{return null}}async function cleanupTeamWindow(t,dryRun){if(!t.tmuxSessionName)return null;let match=await findTeamWindow(t.tmuxSessionName,t.name);if(!match)return null;if(dryRun)return` [dry-run] Would kill window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}" [${t.status}])`;if(!await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).killWindow(t.tmuxSessionName,match.name))return null;return` Killed window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}")`}async function handleTeamCleanup(options){let cleanable=(await listTeams2(!0)).filter((t)=>t.status==="done"||t.status==="archived");if(cleanable.length===0){console.log("No done/archived teams to clean up.");return}let cleaned=0;for(let t of cleanable){let msg=await cleanupTeamWindow(t,options.dryRun===!0);if(msg)console.log(msg),cleaned++}let verb=options.dryRun?"Would clean":"Cleaned";if(cleaned===0)console.log("No tmux windows found for done/archived teams.");else console.log(`
1626
+ ${verb} ${cleaned} window${cleaned===1?"":"s"}.`)}var init_team=__esm(()=>{init_team_manager()});var exports_wish_sync={};__export(exports_wish_sync,{syncWishes:()=>syncWishes,parseWishStatus:()=>parseWishStatus,listWishes:()=>listWishes,getWish:()=>getWish});import{existsSync as existsSync32,readFileSync as readFileSync16,readdirSync as readdirSync7}from"fs";import{basename as basename6,join as join41}from"path";function parseWishStatus(content){let match=content.match(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)/i);if(match)return match[1].trim();return"DRAFT"}function scanRepoWishes(repoPath){let wishesDir=join41(repoPath,".genie","wishes");if(!existsSync32(wishesDir))return[];let results=[],namespace=basename6(repoPath);try{let entries=readdirSync7(wishesDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let wishPath=join41(wishesDir,entry.name,"WISH.md");if(!existsSync32(wishPath))continue;try{let content=readFileSync16(wishPath,"utf-8");results.push({slug:entry.name,repo:repoPath,namespace,status:parseWishStatus(content),filePath:wishPath})}catch{}}}catch{}return results}function discoverWishes(repoPath){if(repoPath)return scanRepoWishes(repoPath);if(!existsSync32(REPOS_BASE2))return[];let results=[];try{let entries=readdirSync7(REPOS_BASE2,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;results.push(...scanRepoWishes(join41(REPOS_BASE2,entry.name)))}}catch{}return results}async function syncWishes(repoPath){let wishes=discoverWishes(repoPath);if(wishes.length===0)return 0;let sql=await getConnection();for(let wish of wishes)await sql`
1589
1627
  INSERT INTO wishes (slug, repo, namespace, status, file_path)
1590
1628
  VALUES (${wish.slug}, ${wish.repo}, ${wish.namespace}, ${wish.status}, ${wish.filePath})
1591
1629
  ON CONFLICT (slug, repo) DO UPDATE SET
@@ -1593,8 +1631,8 @@ ${verb} ${cleaned} window${cleaned===1?"":"s"}.`)}var init_team=__esm(()=>{init_
1593
1631
  status = EXCLUDED.status,
1594
1632
  file_path = EXCLUDED.file_path,
1595
1633
  updated_at = now()
1596
- `;return wishes.length}async function listWishes(filters){let sql=await getConnection();if(filters?.repo&&filters?.status)return sql`SELECT * FROM wishes WHERE repo = ${filters.repo} AND status = ${filters.status} ORDER BY updated_at DESC`;if(filters?.repo)return sql`SELECT * FROM wishes WHERE repo = ${filters.repo} ORDER BY updated_at DESC`;if(filters?.status)return sql`SELECT * FROM wishes WHERE status = ${filters.status} ORDER BY updated_at DESC`;if(filters?.namespace)return sql`SELECT * FROM wishes WHERE namespace = ${filters.namespace} ORDER BY updated_at DESC`;return sql`SELECT * FROM wishes ORDER BY updated_at DESC`}async function getWish(slug,repo){let sql=await getConnection(),rows=repo?await sql`SELECT * FROM wishes WHERE slug = ${slug} AND repo = ${repo} LIMIT 1`:await sql`SELECT * FROM wishes WHERE slug = ${slug} ORDER BY updated_at DESC LIMIT 1`;return rows.length>0?rows[0]:null}var REPOS_BASE2="/home/genie/workspace/repos";var init_wish_sync=__esm(()=>{init_db()});var exports_context_enrichment={};__export(exports_context_enrichment,{enrichContext:()=>enrichContext});import{execSync as execSync10}from"child_process";import{existsSync as existsSync33}from"fs";import{homedir as homedir23}from"os";import{join as join40}from"path";function detectBrainPath(){let agentBrain=process.env.AGENT_BRAIN;if(agentBrain)try{let vaultsOutput=execSync10("qmd vaults 2>/dev/null",{encoding:"utf-8",timeout:5000});for(let line of vaultsOutput.split(`
1597
- `))if(line.includes(agentBrain)){let pathMatch=line.match(/\u2192\s*(.+)/);if(pathMatch)return pathMatch[1].trim()}}catch{}let candidates=[join40(process.cwd(),"brain"),join40(homedir23(),"brain")];for(let candidate of candidates)if(existsSync33(candidate))return candidate;return null}function enrichContext(options){let brainPath=options.brainPath??detectBrainPath();if(!brainPath)return"";try{execSync10("which rlmx",{encoding:"utf-8",timeout:5000})}catch{return""}let maxCost=options.maxCost??0.01,maxIterations=options.maxIterations??5,timeout=options.timeout??30000,rlmxQuery=`Given this task: "${options.query}"
1634
+ `;return wishes.length}async function listWishes(filters){let sql=await getConnection();if(filters?.repo&&filters?.status)return sql`SELECT * FROM wishes WHERE repo = ${filters.repo} AND status = ${filters.status} ORDER BY updated_at DESC`;if(filters?.repo)return sql`SELECT * FROM wishes WHERE repo = ${filters.repo} ORDER BY updated_at DESC`;if(filters?.status)return sql`SELECT * FROM wishes WHERE status = ${filters.status} ORDER BY updated_at DESC`;if(filters?.namespace)return sql`SELECT * FROM wishes WHERE namespace = ${filters.namespace} ORDER BY updated_at DESC`;return sql`SELECT * FROM wishes ORDER BY updated_at DESC`}async function getWish(slug,repo){let sql=await getConnection(),rows=repo?await sql`SELECT * FROM wishes WHERE slug = ${slug} AND repo = ${repo} LIMIT 1`:await sql`SELECT * FROM wishes WHERE slug = ${slug} ORDER BY updated_at DESC LIMIT 1`;return rows.length>0?rows[0]:null}var REPOS_BASE2="/home/genie/workspace/repos";var init_wish_sync=__esm(()=>{init_db()});var exports_context_enrichment={};__export(exports_context_enrichment,{enrichContext:()=>enrichContext});import{execSync as execSync10}from"child_process";import{existsSync as existsSync33}from"fs";import{homedir as homedir25}from"os";import{join as join42}from"path";function detectBrainPath(){let agentBrain=process.env.AGENT_BRAIN;if(agentBrain)try{let vaultsOutput=execSync10("qmd vaults 2>/dev/null",{encoding:"utf-8",timeout:5000});for(let line of vaultsOutput.split(`
1635
+ `))if(line.includes(agentBrain)){let pathMatch=line.match(/\u2192\s*(.+)/);if(pathMatch)return pathMatch[1].trim()}}catch{}let candidates=[join42(process.cwd(),"brain"),join42(homedir25(),"brain")];for(let candidate of candidates)if(existsSync33(candidate))return candidate;return null}function enrichContext(options){let brainPath=options.brainPath??detectBrainPath();if(!brainPath)return"";try{execSync10("which rlmx",{encoding:"utf-8",timeout:5000})}catch{return""}let maxCost=options.maxCost??0.01,maxIterations=options.maxIterations??5,timeout=options.timeout??30000,rlmxQuery=`Given this task: "${options.query}"
1598
1636
 
1599
1637
  Find the most relevant prior knowledge, decisions, and context from the brain vault.
1600
1638
  Return ONLY a bulleted list of relevant excerpts with their source file paths.
@@ -1608,9 +1646,7 @@ ${answer}
1608
1646
  AND table_type = 'BASE TABLE'
1609
1647
  AND table_name NOT LIKE '_genie_%'
1610
1648
  ORDER BY table_name
1611
- `).map((r)=>r.table_name)}async function filterAvailableTables(sql,requested){let existing=new Set(await getAvailableTables(sql)),available=[],skipped=[];for(let table of requested)if(existing.has(table))available.push(table);else skipped.push(table);return{available,skipped}}var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile8,readdir as readdir5}from"fs/promises";import{homedir as homedir24}from"os";import{join as join42}from"path";function getCodexDir(){return join42(homedir24(),".codex")}function getSessionsDir(){return join42(getCodexDir(),"sessions")}function getStateDbPath(){return join42(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir5(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result=await scanYear(join42(sessionsDir,year),cwd);if(result)return result}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result=await scanMonth(join42(yearDir,month),cwd);if(result)return result}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result=await scanDay(join42(monthDir,day),cwd);if(result)return result}return null}async function scanDay(dayDir,cwd){let files=(await readdir5(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join42(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile8(filePath,"utf-8"),nlIdx=content.indexOf(`
1612
- `),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry=JSON.parse(firstLine);if(entry.type==="session_meta"&&entry.payload?.cwd)return{cwd:entry.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile8(logPath,"utf-8")}catch{return[]}return content.split(`
1613
- `).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result=result.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;var exports_task_close_merged={};__export(exports_task_close_merged,{parseSinceDate:()=>parseSinceDate,matchPRsToSlugs:()=>matchPRsToSlugs,fetchMergedPRs:()=>fetchMergedPRs,extractWishSlug:()=>extractWishSlug,extractSlugFromBranch:()=>extractSlugFromBranch,extractSlugFromBody:()=>extractSlugFromBody,closeMergedTasks:()=>closeMergedTasks});import{execSync as execSync15}from"child_process";function extractSlugFromBody(body){if(!body)return null;let match=body.match(/(?:wish|slug):\s*(\S+)/i);return match?match[1]:null}function extractSlugFromBranch(branch){if(!branch)return null;let match=branch.match(/^(?:feat|fix|chore|docs|refactor|test|dream)\/(.+)$/);return match?match[1]:null}function extractWishSlug(pr){return extractSlugFromBody(pr.body)??extractSlugFromBranch(pr.headRefName)}function parseSinceDate(since){let match=since.match(/^(\d+)([hd])$/);if(!match)throw Error(`Invalid --since format: "${since}". Use e.g. "24h" or "7d".`);let amount=Number.parseInt(match[1],10),unit=match[2],now=new Date;if(unit==="h")now.setHours(now.getHours()-amount);else now.setDate(now.getDate()-amount);return now.toISOString()}function fetchMergedPRs(since,repo){let sinceDate=parseSinceDate(since),cmd=`gh pr list --state merged --json number,title,body,headRefName,mergedAt --limit 100 ${repo?`--repo ${repo}`:""}`.trim(),output;try{output=execSync15(cmd,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch(err){let message=err instanceof Error?err.message:String(err);throw Error(`Failed to fetch merged PRs: ${message}`)}return JSON.parse(output).filter((pr)=>new Date(pr.mergedAt)>=new Date(sinceDate))}function matchPRsToSlugs(prs){let matches=[];for(let pr of prs){let slug=extractWishSlug(pr);if(slug)matches.push({prNumber:pr.number,slug,mergedAt:pr.mergedAt})}return matches}async function closeMergedTasks(options={}){let{since="24h",dryRun=!1,repo,repoPath}=options,ts3=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),prs=fetchMergedPRs(since,repo),slugMatches=matchPRsToSlugs(prs),result={closed:0,alreadyShipped:0,prsScanned:prs.length,details:[]};if(slugMatches.length===0)return result;let actor={actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"},processedTaskIds=new Set;for(let{prNumber,slug}of slugMatches){let tasks=await findTasksByWishSlug(slug,repoPath);for(let task of tasks){if(processedTaskIds.has(task.id))continue;if(processedTaskIds.add(task.id),task.stage==="ship"){result.alreadyShipped++;continue}if(!dryRun)await ts3.moveTask(task.id,"ship",actor,void 0,task.repoPath),await ts3.commentOnTask(task.id,actor,`Auto-closed: PR #${prNumber} merged to dev`,task.repoPath);result.closed++,result.details.push({taskSeq:task.seq,taskTitle:task.title,prNumber,slug})}}return result}async function findTasksByWishSlug(slug,repoPath){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),repo=repoPath??getRepoPathSafe(),pattern=`%${slug}%`;return(await sql`
1649
+ `).map((r)=>r.table_name)}async function filterAvailableTables(sql,requested){let existing=new Set(await getAvailableTables(sql)),available=[],skipped=[];for(let table of requested)if(existing.has(table))available.push(table);else skipped.push(table);return{available,skipped}}var exports_task_close_merged={};__export(exports_task_close_merged,{parseSinceDate:()=>parseSinceDate,matchPRsToSlugs:()=>matchPRsToSlugs,fetchMergedPRs:()=>fetchMergedPRs,extractWishSlug:()=>extractWishSlug,extractSlugFromBranch:()=>extractSlugFromBranch,extractSlugFromBody:()=>extractSlugFromBody,closeMergedTasks:()=>closeMergedTasks});import{execSync as execSync15}from"child_process";function extractSlugFromBody(body){if(!body)return null;let match=body.match(/(?:wish|slug):\s*(\S+)/i);return match?match[1]:null}function extractSlugFromBranch(branch){if(!branch)return null;let match=branch.match(/^(?:feat|fix|chore|docs|refactor|test|dream)\/(.+)$/);return match?match[1]:null}function extractWishSlug(pr){return extractSlugFromBody(pr.body)??extractSlugFromBranch(pr.headRefName)}function parseSinceDate(since){let match=since.match(/^(\d+)([hd])$/);if(!match)throw Error(`Invalid --since format: "${since}". Use e.g. "24h" or "7d".`);let amount=Number.parseInt(match[1],10),unit=match[2],now=new Date;if(unit==="h")now.setHours(now.getHours()-amount);else now.setDate(now.getDate()-amount);return now.toISOString()}function fetchMergedPRs(since,repo){let sinceDate=parseSinceDate(since),cmd=`gh pr list --state merged --json number,title,body,headRefName,mergedAt --limit 100 ${repo?`--repo ${repo}`:""}`.trim(),output;try{output=execSync15(cmd,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch(err){let message=err instanceof Error?err.message:String(err);throw Error(`Failed to fetch merged PRs: ${message}`)}return JSON.parse(output).filter((pr)=>new Date(pr.mergedAt)>=new Date(sinceDate))}function matchPRsToSlugs(prs){let matches=[];for(let pr of prs){let slug=extractWishSlug(pr);if(slug)matches.push({prNumber:pr.number,slug,mergedAt:pr.mergedAt})}return matches}async function closeMergedTasks(options={}){let{since="24h",dryRun=!1,repo,repoPath}=options,ts3=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),prs=fetchMergedPRs(since,repo),slugMatches=matchPRsToSlugs(prs),result={closed:0,alreadyShipped:0,prsScanned:prs.length,details:[]};if(slugMatches.length===0)return result;let actor={actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"},processedTaskIds=new Set;for(let{prNumber,slug}of slugMatches){let tasks=await findTasksByWishSlug(slug,repoPath);for(let task of tasks){if(processedTaskIds.has(task.id))continue;if(processedTaskIds.add(task.id),task.stage==="ship"){result.alreadyShipped++;continue}if(!dryRun)await ts3.moveTask(task.id,"ship",actor,void 0,task.repoPath),await ts3.commentOnTask(task.id,actor,`Auto-closed: PR #${prNumber} merged to dev`,task.repoPath);result.closed++,result.details.push({taskSeq:task.seq,taskTitle:task.title,prNumber,slug})}}return result}async function findTasksByWishSlug(slug,repoPath){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db)),sql=await getConnection2(),repo=repoPath??getRepoPathSafe(),pattern=`%${slug}%`;return(await sql`
1614
1650
  SELECT id, seq, title, stage, repo_path
1615
1651
  FROM tasks
1616
1652
  WHERE repo_path = ${repo}
@@ -2180,21 +2216,60 @@ Next steps:`),console.log(" 1. Reload tmux: tmux source ~/.tmux.conf"),console.
2180
2216
  genie task status <slug>
2181
2217
  genie events list --since 5m`}];async function orchestrationGuard(payload){let command=payload.tool_input?.command;if(typeof command!=="string")return;for(let{test,message}of NUDGE_PATTERNS)if(test.test(command))return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",additionalContext:`[orchestration-guard] ${message}`}};return}var getAgent2=()=>process.env.GENIE_AGENT_NAME??"unknown",getTeam2=()=>process.env.GENIE_TEAM;async function emit(subject,event){try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),subject,event)}catch{}}async function emitToolCallEvent(payload){let{tool_name:toolName,tool_input:input}=payload;if(!toolName||!input)return;await emit(`genie.tool.${getAgent2()}.call`,{timestamp:new Date().toISOString(),kind:"tool_call",agent:getAgent2(),team:getTeam2(),text:summarizeToolCall(toolName,input),data:{toolCall:{name:toolName,input}},source:"hook"});return}async function emitMessageEvent(payload){let input=payload.tool_input;if(!input)return;let msgType=input.type;if(msgType&&msgType!=="message"&&msgType!=="broadcast")return;let to=input.to,content=input.content??input.message;if(!to||!content)return;let subject=msgType==="broadcast"?"genie.msg.broadcast":`genie.msg.${to}`;await emit(subject,{timestamp:new Date().toISOString(),kind:"message",agent:getAgent2(),team:getTeam2(),peer:to,direction:"out",text:content,source:"hook"});return}async function emitUserPromptEvent(payload){let prompt2=payload.prompt;if(!prompt2)return;await emit(`genie.user.${getAgent2()}.prompt`,{timestamp:new Date().toISOString(),kind:"user",agent:getAgent2(),team:getTeam2(),text:prompt2,source:"hook"});return}async function emitAssistantResponseEvent(payload){let lastMessage=payload.last_assistant_message;if(!lastMessage)return;await emit(`genie.agent.${getAgent2()}.response`,{timestamp:new Date().toISOString(),kind:"assistant",agent:getAgent2(),team:getTeam2(),text:lastMessage,source:"hook"});return}function summarizeToolCall(name,input){switch(name){case"Read":case"Edit":case"Write":return`${name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
2182
2218
  `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;default:return name}}init_types3();var handlers=[{name:"branch-guard",event:"PreToolUse",matcher:/^Bash$/,priority:1,fn:branchGuard},{name:"orchestration-guard",event:"PreToolUse",matcher:/^Bash$/,priority:2,fn:orchestrationGuard},{name:"identity-inject",event:"PreToolUse",matcher:/^SendMessage$/,priority:10,fn:identityInject},{name:"auto-spawn",event:"PreToolUse",matcher:/^SendMessage$/,priority:20,fn:autoSpawn},{name:"runtime-emit-tool",event:"PreToolUse",matcher:/.*/,priority:30,fn:emitToolCallEvent},{name:"runtime-emit-msg",event:"PostToolUse",matcher:/^SendMessage$/,priority:30,fn:emitMessageEvent},{name:"runtime-emit-user-prompt",event:"UserPromptSubmit",priority:30,fn:emitUserPromptEvent},{name:"runtime-emit-assistant-response",event:"Stop",priority:30,fn:emitAssistantResponseEvent}];function resolveHandlers(event,toolName){return handlers.filter((h)=>{if(h.event!==event)return!1;if(h.matcher&&toolName&&!h.matcher.test(toolName))return!1;if(h.matcher&&!toolName)return!1;return!0}).sort((a,b2)=>a.priority-b2.priority)}async function runHandler(handler,payload,currentInput){let handlerPayload={...payload};if(currentInput)handlerPayload.tool_input=currentInput;try{return await handler.fn(handlerPayload)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`[genie-hook] Handler "${handler.name}" threw: ${msg}`);return}}function buildDenyResponse(handler,reason,hookEventName){if(hookEventName==="PreToolUse")return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:reason??`Denied by handler: ${handler.name}`}};return{decision:"block",reason:reason??`Denied by handler: ${handler.name}`}}function buildBlockingResponse(hookEventName,contextMessages,currentInput,originalInput){let response={},hasContext=contextMessages.length>0,hasInputChange=currentInput&&originalInput&&JSON.stringify(currentInput)!==JSON.stringify(originalInput);if(hasInputChange)response.updatedInput=currentInput;if(hookEventName==="PreToolUse"&&(hasContext||hasInputChange)){let output={hookEventName:"PreToolUse"};if(hasContext)output.additionalContext=contextMessages.join(`
2183
- `);if(hasInputChange)output.permissionDecision="allow",output.updatedInput=currentInput;response.hookSpecificOutput=output}return response}async function executeBlockingChain(matched,payload){let currentInput=payload.tool_input?{...payload.tool_input}:void 0,contextMessages=[],hookEventName=payload.hook_event_name;for(let handler of matched){let result=await runHandler(handler,payload,currentInput);if(!result)continue;if(result.decision==="deny")return buildDenyResponse(handler,result.reason,hookEventName);if(result.hookSpecificOutput?.additionalContext)contextMessages.push(result.hookSpecificOutput.additionalContext);let inputUpdate=result.hookSpecificOutput?.updatedInput??result.updatedInput;if(inputUpdate)currentInput={...currentInput,...inputUpdate}}return buildBlockingResponse(hookEventName,contextMessages,currentInput,payload.tool_input)}async function executeNonBlockingHandlers(matched,payload){await Promise.allSettled(matched.map((h)=>h.fn(payload).catch((err)=>{let msg=err instanceof Error?err.message:String(err);console.error(`[genie-hook] Handler "${h.name}" threw: ${msg}`)})))}async function dispatch(stdin){let payload;try{payload=JSON.parse(stdin)}catch{return console.error("[genie-hook] Invalid JSON on stdin"),""}let event=payload.hook_event_name;if(!event)return console.error("[genie-hook] Missing hook_event_name in payload"),"";let toolName=payload.tool_name,matched=resolveHandlers(event,toolName);if(matched.length===0)return"";if(isBlockingEvent(event)){let result=await executeBlockingChain(matched,payload);if(Object.keys(result).length>0)return JSON.stringify(result);return""}return await executeNonBlockingHandlers(matched,payload),""}async function readStdin(){let chunks=[];for await(let chunk of Bun.stdin.stream())chunks.push(Buffer.from(chunk));return Buffer.concat(chunks).toString("utf-8")}async function dispatchAction(){let stdin=await readStdin();if(!stdin.trim())process.exit(0);let result=await dispatch(stdin);if(result)process.stdout.write(result)}function registerHookNamespace(program2){program2.command("hook").description("Hook middleware for Claude Code integration").command("dispatch").description("Dispatch a CC hook event (reads JSON from stdin, writes decision to stdout)").action(dispatchAction)}init_audit();init_db();init_otel_receiver();init_agents();async function handleTuiMode(){let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2()}async function findTauriBinary(){let{existsSync:existsSync26}=await import("fs"),{join:join33,dirname:dirname6}=await import("path"),{execSync:execSync8}=await import("child_process"),appName="genie-desktop",rootDir=join33(dirname6(new URL(import.meta.url).pathname),"..",".."),localBin=[join33(rootDir,"packages","genie-app","src-tauri","target","release","genie-desktop"),join33(rootDir,"packages","genie-app","src-tauri","target","debug","genie-desktop"),join33(rootDir,"dist","app","genie-desktop"),"/usr/local/bin/genie-desktop"].find((p)=>existsSync26(p));if(localBin)return localBin;try{return execSync8("which genie-desktop",{stdio:"ignore"}),"genie-desktop"}catch{return}}function registerAppCommand(program2){program2.command("app").description("Launch Genie desktop app (backend sidecar + views)").option("--backend-only","Start only the backend sidecar (IPC on stdin/stdout)").option("--tui","Fall back to terminal UI mode").option("--dev","Development mode").action(async(options)=>{if(options.tui){await handleTuiMode();return}if(options.backendOnly){await Promise.resolve().then(() => (init_src_backend(),exports_src_backend));return}let tauriBin=await findTauriBinary();if(tauriBin){console.log("\x1B[35m\u25C6 Genie App\x1B[0m Launching desktop...");let{execFileSync}=await import("child_process");try{execFileSync(tauriBin,[],{stdio:"inherit"})}catch{}return}console.log("\x1B[35m\u25C6 Genie App\x1B[0m Starting backend sidecar..."),console.log("\x1B[2mDesktop binary not found \u2014 running in sidecar mode.\x1B[0m"),console.log("\x1B[2mPG bridge + PTY manager + IPC on stdin/stdout\x1B[0m"),console.log(`\x1B[2mUse --tui for terminal UI, or pipe to a frontend shell.\x1B[0m
2184
- `),await Promise.resolve().then(() => (init_src_backend(),exports_src_backend))})}init_audit();function padRight(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function truncate(str3,len){return str3.length<=len?str3:`${str3.slice(0,len-1)}\u2026`}function formatDate(iso){if(!iso)return"-";return new Date(iso).toLocaleDateString("en-US",{month:"short",day:"numeric"})}function formatRelativeTimestamp(ts3){let d=new Date(ts3),diffMs=Date.now()-d.getTime();if(diffMs<60000)return`${Math.floor(diffMs/1000)}s ago`;if(diffMs<3600000)return`${Math.floor(diffMs/60000)}m ago`;if(diffMs<86400000)return`${Math.floor(diffMs/3600000)}h ago`;return d.toISOString().replace("T"," ").slice(0,19)}function formatTimestamp(iso,opts){if(!iso)return opts?.fallback??"-";let d=iso instanceof Date?iso:new Date(iso),fmt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return d.toLocaleString("en-US",fmt)}function formatTime(iso,opts){try{let date=new Date(iso),fmt={hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return date.toLocaleTimeString("en-US",fmt)}catch{return opts?.fallback??"??:??"}}function printEventsTable(rows){if(rows.length===0){console.log("No audit events found.");return}let sorted=[...rows].reverse(),headers=["Time","Type","Entity","Event","Actor","Details"],data=sorted.map((r)=>[formatRelativeTimestamp(r.created_at),r.entity_type,r.entity_id,r.event_type,r.actor??"-",summarizeDetails(r.details)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))}),header=headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data){let line=row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | ");console.log(line)}console.log(`
2219
+ `);if(hasInputChange)output.permissionDecision="allow",output.updatedInput=currentInput;response.hookSpecificOutput=output}return response}async function executeBlockingChain(matched,payload){let currentInput=payload.tool_input?{...payload.tool_input}:void 0,contextMessages=[],hookEventName=payload.hook_event_name;for(let handler of matched){let result=await runHandler(handler,payload,currentInput);if(!result)continue;if(result.decision==="deny")return buildDenyResponse(handler,result.reason,hookEventName);if(result.hookSpecificOutput?.additionalContext)contextMessages.push(result.hookSpecificOutput.additionalContext);let inputUpdate=result.hookSpecificOutput?.updatedInput??result.updatedInput;if(inputUpdate)currentInput={...currentInput,...inputUpdate}}return buildBlockingResponse(hookEventName,contextMessages,currentInput,payload.tool_input)}async function executeNonBlockingHandlers(matched,payload){await Promise.allSettled(matched.map((h)=>h.fn(payload).catch((err)=>{let msg=err instanceof Error?err.message:String(err);console.error(`[genie-hook] Handler "${h.name}" threw: ${msg}`)})))}async function dispatch(stdin){let payload;try{payload=JSON.parse(stdin)}catch{return console.error("[genie-hook] Invalid JSON on stdin"),""}let event=payload.hook_event_name;if(!event)return console.error("[genie-hook] Missing hook_event_name in payload"),"";let toolName=payload.tool_name,matched=resolveHandlers(event,toolName);if(matched.length===0)return"";if(isBlockingEvent(event)){let result=await executeBlockingChain(matched,payload);if(Object.keys(result).length>0)return JSON.stringify(result);return""}return await executeNonBlockingHandlers(matched,payload),""}async function readStdin(){let chunks=[];for await(let chunk of Bun.stdin.stream())chunks.push(Buffer.from(chunk));return Buffer.concat(chunks).toString("utf-8")}async function dispatchAction(){let stdin=await readStdin();if(!stdin.trim())process.exit(0);let result=await dispatch(stdin);if(result)process.stdout.write(result)}function registerHookNamespace(program2){program2.command("hook").description("Hook middleware for Claude Code integration").command("dispatch").description("Dispatch a CC hook event (reads JSON from stdin, writes decision to stdout)").action(dispatchAction)}init_audit();init_db();init_otel_receiver();init_target_resolver();init_tmux();init_orchestrator();async function resolveOrcTarget(target){let resolved=await resolveTarget(target);return{paneId:resolved.paneId,session:resolved.session||target,label:formatResolvedLabel(resolved,target)}}async function sendTextChoice(paneId,text){await executeTmux2(`send-keys -t '${paneId}' End`),await sleep(100),await executeTmux2(`send-keys -t '${paneId}' Enter`),await sleep(100),await executeTmux2(`send-keys -t '${paneId}' ${shellEscape(text)}`),await sleep(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}function findCurrentOption(output){let lines=stripAnsi(output).split(`
2220
+ `);for(let line of lines){let match=line.match(/^\s*\u276F\s*(\d+)\./);if(match)return Number.parseInt(match[1],10)}return 1}async function navigateToOption(paneId,targetOption,currentOption){let diff=targetOption-currentOption,key=diff>0?"Down":"Up";for(let i2=0;i2<Math.abs(diff);i2++)await executeTmux2(`send-keys -t '${paneId}' ${key}`),await sleep(50);await sleep(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}async function answerQuestion(target,choice){try{let{paneId,label}=await resolveOrcTarget(target),output=await capturePaneContent(paneId,50),state=detectState(output);if(state.type!=="question"){console.log(`No question pending (state: ${state.type})`);return}if(choice.startsWith("text:")){let text=choice.slice(5);await sendTextChoice(paneId,text),console.log(`Sent feedback: "${text.substring(0,50)}${text.length>50?"...":""}"`)}else if(/^\d+$/.test(choice)){let targetOption=Number.parseInt(choice,10);await navigateToOption(paneId,targetOption,findCurrentOption(output)),console.log(`Selected option ${targetOption} for ${label}`)}else await executeTmux2(`send-keys -t '${paneId}' '${choice}'`),console.log(`Sent '${choice}' to ${label}`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}function shellEscape(str2){return`"${str2.replace(/"/g,"\\\"").replace(/\$/g,"\\$")}"`}function sleep(ms){return new Promise((resolve4)=>setTimeout(resolve4,ms))}function registerAgentAnswer(parent){parent.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{try{await answerQuestion(name,choice)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}var _brief;async function getBrief(){if(!_brief)_brief=await Promise.resolve().then(() => (init_brief(),exports_brief));return _brief}async function handleBrief(options){let team=options.team??process.env.GENIE_TEAM;if(!team)console.error("Error: --team is required (or set GENIE_TEAM)"),process.exit(1);let agent=options.agent??process.env.GENIE_AGENT_NAME,briefService=await getBrief(),brief=await briefService.generateBrief({team,agent,since:options.since,repoPath:process.cwd()});console.log(briefService.formatBrief(brief))}function registerAgentBrief(parent){parent.command("brief").description("Show startup brief \u2014 aggregated context since last session").option("--team <name>","Team name (default: GENIE_TEAM)").option("--agent <name>","Agent name (default: GENIE_AGENT_NAME)").option("--since <iso>","Start timestamp (default: last executor end)").action(async(options)=>{try{await handleBrief(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_cache();init_agent_directory();init_agent_sync();init_builtin_agents();init_genie_config2();async function showEntry(name,json2){let resolved=await resolve3(name);if(!resolved)console.error(`Agent "${name}" not found in directory or built-ins.`),process.exit(1);if(json2){console.log(JSON.stringify({...resolved.entry,builtin:resolved.builtin},null,2));return}if(resolved.builtin)console.log(`
2221
+ (built-in agent)`);if(console.log(""),console.log(` Name: ${resolved.entry.name}`),console.log(` Dir: ${contractPath(resolved.entry.dir)}`),resolved.entry.repo)console.log(` Repo: ${contractPath(resolved.entry.repo)}`);if(console.log(` Prompt mode: ${resolved.entry.promptMode}`),resolved.entry.model)console.log(` Model: ${resolved.entry.model}`);if(resolved.entry.roles?.length)console.log(` Roles: ${resolved.entry.roles.join(", ")}`);console.log(` Registered: ${resolved.entry.registeredAt}`),console.log("")}function printRegisteredAgentsTable(entries){let termW=process.stdout.columns||120,repoValues=entries.map((e)=>e.repo?contractPath(e.repo):contractPath(e.dir)),maxRepoLen=Math.max(4,...repoValues.map((v)=>v.length)),fixedW=42,repoW=Math.min(maxRepoLen+2,Math.max(30,termW-42-20));console.log(""),console.log("REGISTERED AGENTS"),console.log("-".repeat(Math.max(90,42+repoW+20))),console.log(` ${"NAME".padEnd(22)}${"SCOPE".padEnd(10)}${"REPO".padEnd(repoW)}${"MODEL".padEnd(8)}ROLES`);for(let i2=0;i2<entries.length;i2++){let entry=entries[i2],repo=repoValues[i2],roles=entry.roles?.join(", ")||"-";console.log(` ${entry.name.padEnd(22)}${entry.scope.padEnd(10)}${repo.padEnd(repoW)}${(entry.model||"-").padEnd(8)}${roles}`)}console.log("")}function printBuiltinAgentsTable(){console.log("BUILT-IN AGENTS"),console.log("-".repeat(80)),console.log(` ${"NAME".padEnd(22)}${"TYPE".padEnd(10)}${"MODEL".padEnd(8)}DESCRIPTION`);for(let agent of ALL_BUILTINS)console.log(` ${agent.name.padEnd(22)}${agent.category.padEnd(10)}${(agent.model||"-").padEnd(8)}${agent.description}`);console.log("")}function normalizeRoles(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}async function listEntries(json2,includeBuiltins,includeArchived){await migrateAgentDirectory().catch(()=>{});let entries;try{entries=(await listItemsFromStore("agent")).filter((item)=>{if(includeArchived)return!0;return!(item.manifest??{}).archived}).map((item)=>{let manifest=item.manifest??{};return{name:item.name,dir:item.install_path??"",repo:manifest.repo??"",promptMode:manifest.promptMode??"append",model:manifest.model,roles:normalizeRoles(manifest.roles),registeredAt:item.installed_at,scope:manifest.archived?"archived":"global"}})}catch{entries=await ls()}if(json2){let result=entries.map((e)=>({...e,builtin:!1}));if(includeBuiltins)for(let b2 of ALL_BUILTINS)result.push({name:b2.name,description:b2.description,model:b2.model,category:b2.category,scope:"built-in",builtin:!0});console.log(JSON.stringify(result,null,2));return}if(entries.length===0&&!includeBuiltins){console.log(`
2222
+ No agents registered. Add one with: genie agent register <name> --dir <path>`),console.log(`Use --builtins to also see built-in roles and council members.
2223
+ `);return}if(entries.length>0)printRegisteredAgentsTable(entries);if(includeBuiltins)printBuiltinAgentsTable()}function registerAgentDirectory(parent){parent.command("directory [name]").alias("dir").description("List all agents or show single entry details from directory").option("--json","Output as JSON").option("--builtins","Include built-in roles and council members").option("--all","Include archived agents").action(async(name,options)=>{try{if(name==="sync"){let resolved=await resolve3("sync");if(resolved&&!resolved.builtin)await showEntry("sync",options.json);else await handleSync()}else if(name)await showEntry(name,options.json);else await listEntries(options.json,options.builtins,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleSync(){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` first."),process.exit(1);console.log(`Syncing agents from ${ws.root}/agents/...`);let result=await syncAgentDirectory(ws.root);printSyncResult(result)}init_msg();var _taskService2;async function getTaskService2(){if(!_taskService2)_taskService2=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService2}function printConversation(conv,lastMsg){let 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 handleInbox2(agent,options){let ts3=await getTaskService2(),resolvedAgent=agent??await detectSenderIdentity(),actor={actorType:"local",actorId: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){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null;printConversation(conv,lastMsg)}}function registerAgentInbox(parent){let inbox2=parent.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox2(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
2224
+ `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result},warn:(msg)=>console.log(msg)}),shutdown2=()=>{console.log(`
2225
+ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})})}init_agents();function registerAgentKill(parent){parent.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentList(parent){parent.command("list").alias("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_registry();init_mailbox();init_runtime_events();init_db();function rowToMessage2(row){return{id:row.id,sender:row.sender,body:row.body,timestamp:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)}}async function readMessages(repoPath,teamName,since){let sql=await getConnection();if(since)return(await sql`
2226
+ SELECT * FROM team_chat
2227
+ WHERE team = ${teamName} AND repo_path = ${repoPath} AND created_at >= ${since}
2228
+ ORDER BY created_at ASC
2229
+ `).map(rowToMessage2);return(await sql`
2230
+ SELECT * FROM team_chat
2231
+ WHERE team = ${teamName} AND repo_path = ${repoPath}
2232
+ ORDER BY created_at ASC
2233
+ `).map(rowToMessage2)}function mailboxActorKeys(agent){let keys=[agent.id];if(agent.role&&agent.role!==agent.id)keys.push(agent.role);if(agent.customName&&!keys.includes(agent.customName))keys.push(agent.customName);return keys}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys),team?readMessages(repoPath,team):Promise.resolve([])]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys)]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startPgFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startPgFollow(agents,repoPath,teamName,filter,onEvent)}async function startPgFollow(agents,repoPath,team,filter,onEvent){let kindsFilter=filter?.kinds?new Set(filter.kinds):null,agentIds=new Set(agents.map((agent)=>agent.id)),seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,matchesScope=(event)=>{if(team==="all")return agentIds.has(event.agent);if(team&&event.team===team)return!0;return agentIds.has(event.agent)},handleRuntimeEvent=(event)=>{if(!event.timestamp||!event.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;if(!matchesScope(event))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},handle=await followRuntimeEvents({repoPath,agentIds:[...agentIds],team:team&&team!=="all"?team:void 0,kinds:filter?.kinds,scopeMode:team&&team!=="all"?"any":"all"},handleRuntimeEvent,{pollIntervalMs:500});return{mode:"pg",stop:()=>handle.stop()}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
2234
+ `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;case"shell":case"exec_command":return`$ ${(Array.isArray(input.command)?input.command.join(" "):String(input.command??"")).split(`
2235
+ `)[0]}`;case"web_search":return`Search: ${input.query??""}`;default:return`${tc.name}`}}function formatEventBlock(event){let time=formatTime(event.timestamp,{seconds:!0,fallback:"??:??:??"}),icon=kindIcon(event.kind),color=kindColor(event.kind),agent=event.agent;if(event.direction==="in")agent=`${event.peer} \u2192 ${event.agent}`;else if(event.direction==="out")agent=`${event.agent} \u2192 ${event.peer}`;let header=`${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}`;if(event.kind==="tool_call"){let summary=summarizeToolCall2(event);return`${header} ${DIM}${summary}${RESET}`}if(event.kind==="tool_result"){let line=event.text.split(`
2236
+ `)[0].slice(0,120);return`${header} ${DIM}${line}${RESET}`}let text=event.text.trim();if(text.length<80&&!text.includes(`
2237
+ `))return`${header}
2238
+ ${text}`;let indented=text.split(`
2239
+ `).map((l)=>` ${l}`).join(`
2240
+ `);return`${header}
2241
+ ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push(""),lines.push(`${BOLD}Log: ${label}${RESET} (${events.length} events)`),lines.push(""),events.length===0)return lines.push(" No events found."),lines.push(""),lines.join(`
2242
+ `);let lastKind=null;for(let event of events){if(lastKind!==null&&!(lastKind==="tool_call"&&event.kind==="tool_call"))lines.push("");lines.push(formatEventBlock(event)),lastKind=event.kind}return lines.push(""),lines.join(`
2243
+ `)}async function findAgent(identifier,teamName){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;let all=await list(),teamMatch=(teamName?all.filter((a)=>a.team===teamName):[]).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()));if(teamMatch)return teamMatch;return all.find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
2244
+ `)}function outputJson(events){process.stdout.write(`${JSON.stringify(events,null,2)}
2245
+ `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie agent list\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
2246
+ `);else{if(lastFollowKind!==null&&!(lastFollowKind==="tool_call"&&event.kind==="tool_call"))process.stdout.write(`
2247
+ `);process.stdout.write(`${formatEventBlock(event)}
2248
+ `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);label=`team:${options.team}${agents.length>0?` (${agents.length} agents)`:""}`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie agent list\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}function registerAgentLog(parent){parent.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").option("--raw","Show raw pane capture (was `genie read`)").option("--transcript","Show compressed transcript (was `genie history`)").option("--full","Show full conversation (with --transcript)").option("--search <query>","Search across sessions (was `genie sessions search`)").option("-n, --lines <number>","Number of lines to read (with --raw)").action(async(agent,options)=>{try{if(options.raw){await handleRawMode(agent,options);return}if(options.transcript){await handleTranscriptMode(agent,options);return}if(options.search){await handleSearchMode(options.search,options);return}await logCommand(agent,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleRawMode(agent,options){if(!agent)console.error("Error: agent name required for --raw mode."),console.error("Usage: genie agent log <name> --raw"),process.exit(1);let{readSessionLogs:readSessionLogs3}=await Promise.resolve().then(() => (init_read(),exports_read));await readSessionLogs3(agent,{lines:options.lines,follow:options.follow,json:options.json})}async function handleTranscriptMode(agent,options){if(!agent)console.error("Error: agent name required for --transcript mode."),console.error("Usage: genie agent log <name> --transcript"),process.exit(1);let{historyCommand:historyCommand2}=await Promise.resolve().then(() => (init_history(),exports_history));await historyCommand2(agent,{full:options.full,last:options.last,type:options.type,json:options.json,ndjson:options.ndjson,after:options.since})}async function handleSearchMode(query,options){let{isAvailable:isAvailable2,getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!await isAvailable2())console.error("Database not available for session search."),process.exit(1);let sql=await getConnection2(),limit=options.last??20,rows=await sql`
2249
+ SELECT c.session_id, c.turn_index, c.role, c.content, c.timestamp,
2250
+ s.agent_id, s.team, s.status
2251
+ FROM session_content c
2252
+ JOIN sessions s ON c.session_id = s.id
2253
+ WHERE c.content ILIKE ${`%${query}%`}
2254
+ ORDER BY c.timestamp DESC
2255
+ LIMIT ${limit}
2256
+ `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log(`No results for "${query}".`);return}console.log(""),console.log(`Search: "${query}" (${rows.length} results)`),console.log("\u2500".repeat(60));for(let r of rows){let preview=String(r.content).replace(/\n/g," ").slice(0,100),agent=r.agent_id??"(unknown)",time=r.timestamp?new Date(r.timestamp).toLocaleTimeString():"??:??";console.log(` [${time}] ${agent} (${r.role}): ${preview}`)}console.log("")}init_agent_directory();init_genie_config2();import{resolve as resolvePath}from"path";init_audit();init_genie_config2();async function resolveOmniApiUrl(){let envUrl=process.env.OMNI_API_URL;if(envUrl)return envUrl;return(await loadGenieConfig()).omni?.apiUrl??null}async function resolveOmniApiKey(){let envKey=process.env.OMNI_API_KEY;if(envKey)return envKey;return(await loadGenieConfig()).omni?.apiKey}async function registerAgentInOmni(agentName,options){let apiUrl=await resolveOmniApiUrl();if(!apiUrl)return null;let apiKey=await resolveOmniApiKey(),body={name:agentName,provider:"claude",model:options?.model,agentType:"assistant",capabilities:options?.roles??[],metadata:{source:"genie",sessionIsolation:{perPerson:!0,perChannel:!0},registeredAt:new Date().toISOString()}},traceId=generateTraceId();try{let headers={"Content-Type":"application/json","X-Trace-Id":traceId};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;let response=await fetch(`${apiUrl}/api/v2/agents`,{method:"POST",headers,body:JSON.stringify(body),signal:AbortSignal.timeout(1e4)});if(!response.ok){let text=await response.text().catch(()=>"");return console.warn(`Warning: Omni registration failed (HTTP ${response.status}): ${text}`),recordAuditEvent("omni",agentName,"registration_error",getActor(),{traceId,status:response.status,error:text.slice(0,200)}).catch(()=>{}),null}let result=await response.json();return recordAuditEvent("omni",agentName,"registration_success",getActor(),{traceId,omniAgentId:result.data.id}).catch(()=>{}),result.data.id}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);return console.warn(`Warning: Omni registration failed: ${message}`),recordAuditEvent("omni",agentName,"registration_error",getActor(),{traceId,error:message.slice(0,200)}).catch(()=>{}),null}}async function findOmniAgent(agentName){let apiUrl=await resolveOmniApiUrl();if(!apiUrl)return null;let apiKey=await resolveOmniApiKey(),traceId=generateTraceId();try{let headers={"X-Trace-Id":traceId};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;let response=await fetch(`${apiUrl}/api/v2/agents?name=${encodeURIComponent(agentName)}`,{method:"GET",headers,signal:AbortSignal.timeout(1e4)});if(!response.ok)return null;return(await response.json()).data?.find((a)=>a.name===agentName&&a.isActive)?.id??null}catch{return null}}function validatePromptMode(mode){if(mode!=="system"&&mode!=="append")throw Error(`Invalid prompt mode "${mode}". Must be "append" or "system".`);return mode}function normalizeRoles2(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}function printEntry(entry){if(console.log(` Name: ${entry.name}`),console.log(` Dir: ${contractPath(entry.dir)}`),entry.repo)console.log(` Repo: ${contractPath(entry.repo)}`);if(console.log(` Prompt mode: ${entry.promptMode}`),entry.model)console.log(` Model: ${entry.model}`);if(entry.roles?.length)console.log(` Roles: ${entry.roles.join(", ")}`);console.log(` Registered: ${entry.registeredAt}`)}async function handleOmniRegistration(name,options){let omniUrl=await resolveOmniApiUrl();if(!omniUrl)return;console.log(`
2257
+ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);if(existingId){console.log(` Agent already exists in Omni: ${existingId}`),await edit(name,{omniAgentId:existingId},{global:options.global}),console.log(" Linked existing Omni agent to directory entry.");return}let omniAgentId=await registerAgentInOmni(name,{model:options.model,roles:options.roles});if(omniAgentId)await edit(name,{omniAgentId},{global:options.global}),console.log(` Omni agent created: ${omniAgentId}`),console.log(" Session isolation: per-person + per-channel")}async function handleAgentRegister(name,options){let promptMode=validatePromptMode(options.promptMode),roles=normalizeRoles2(options.roles),entry=await add({name,dir:resolvePath(options.dir),repo:options.repo?resolvePath(options.repo):void 0,promptMode,model:options.model,roles},{global:options.global}),scope=options.global?"global":"project";if(console.log(`Agent "${entry.name}" registered (${scope}).`),printEntry(entry),!options.skipOmni)await handleOmniRegistration(name,{...options,roles})}function registerAgentRegister(parent){parent.command("register <name>").description("Register an agent locally and auto-register in Omni when configured").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").option("--skip-omni","Skip Omni auto-registration").action(async(name,options)=>{try{await handleAgentRegister(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentResume(parent){parent.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_msg();function registerAgentSend(parent){parent.command("send <body>").description("Send a direct message to an agent (hierarchy-enforced)").option("--to <agent>","Recipient agent name (default: team-lead)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--broadcast","Send to all direct reports").action(async(body,options)=>{try{let from=options.from??await detectSenderIdentity(options.team);if(options.broadcast){await handleBroadcast(from,body,options.team);return}await handleDirectMessage(from,options.to,body,options.team)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}async function isTeamLeader(agentName,teamName){if(agentName==="team-lead")return!0;try{let config=await(await Promise.resolve().then(() => (init_team_manager(),exports_team_manager))).getTeam(teamName);return agentName===config?.leader}catch{return!1}}async function checkHierarchy(from,to){if(from==="cli")return{allowed:!0};if(from===to)return{allowed:!0};try{let agents=await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).listAgents({}),sender=agents.find((a)=>a.customName===from||a.role===from||a.id===from),recipient=agents.find((a)=>a.customName===to||a.role===to||a.id===to);if(!sender||!recipient)return{allowed:!0};if(recipient.reportsTo===from||recipient.reportsTo===sender.id)return{allowed:!0};if(sender.reportsTo===to||sender.reportsTo===recipient.id)return{allowed:!0};if(sender.reportsTo&&sender.reportsTo===recipient.reportsTo)return{allowed:!0};if(sender.team&&sender.team===recipient.team&&await isTeamLeader(from,sender.team))return{allowed:!0};let manager=sender.reportsTo??"your manager";return{allowed:!1,reason:`Cannot reach "${to}". Escalate to ${manager}.`}}catch{return{allowed:!0}}}async function handleDirectMessage(from,to,body,team){let{allowed,reason}=await checkHierarchy(from,to);if(!allowed)console.error(`Error: ${reason}`),process.exit(1);let{checkSendScope:checkSendScope2}=await Promise.resolve().then(() => (init_msg(),exports_msg)),repoPath=process.cwd(),scopeError=await checkSendScope2(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox)),senderActor={actorType:"local",actorId:from},recipientActor={actorType:"local",actorId:to},conv=await taskService.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await taskService.addMember(conv.id,senderActor),await taskService.addMember(conv.id,recipientActor),await mailbox.send(repoPath,from,to,body);let msg=await taskService.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{}try{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=team??process.env.GENIE_TEAM;if(currentTeam){let nativeName=await nativeTeams.resolveNativeMemberName(currentTeam,to);if(nativeName)await nativeTeams.writeNativeInbox(currentTeam,nativeName,nativeMsg)}}catch{}console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}async function handleBroadcast(from,body,team){let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),repoPath=process.cwd(),teamName=team??process.env.GENIE_TEAM;if(!teamName)console.error("Error: Could not detect team. Use --team <name>."),process.exit(1);let senderActor={actorType:"local",actorId:from},conv=await taskService.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await taskService.addMember(conv.id,senderActor);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function printAgentFields(agent){if(console.log(""),console.log(`AGENT: ${agent.customName??agent.role??agent.id}`),console.log("\u2500".repeat(60)),console.log(` ${padRight("ID:",20)} ${agent.id}`),agent.role)console.log(` ${padRight("Role:",20)} ${agent.role}`);if(agent.customName)console.log(` ${padRight("Name:",20)} ${agent.customName}`);if(agent.team)console.log(` ${padRight("Team:",20)} ${agent.team}`);console.log(` ${padRight("Started:",20)} ${agent.startedAt}`)}function printExecutorFields(executor){if(console.log(""),console.log("Current Executor:"),console.log("\u2500".repeat(60)),console.log(` ${padRight("Executor ID:",20)} ${executor.id}`),console.log(` ${padRight("Provider:",20)} ${executor.provider}`),console.log(` ${padRight("Transport:",20)} ${executor.transport}`),console.log(` ${padRight("State:",20)} ${executor.state}`),executor.pid)console.log(` ${padRight("PID:",20)} ${executor.pid}`);if(executor.tmuxSession)console.log(` ${padRight("Tmux Session:",20)} ${executor.tmuxSession}`);if(executor.tmuxPaneId)console.log(` ${padRight("Tmux Pane:",20)} ${executor.tmuxPaneId}`);if(executor.worktree)console.log(` ${padRight("Worktree:",20)} ${executor.worktree}`);if(console.log(` ${padRight("Started:",20)} ${executor.startedAt}`),executor.endedAt)console.log(` ${padRight("Ended:",20)} ${executor.endedAt}`)}async function showAgent(name,json2){let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agent=(await registry.listAgents({team:process.env.GENIE_TEAM})).find((a)=>a.customName===name||a.role===name||a.id===name);if(!agent)console.error(`Agent "${name}" not found.`),process.exit(1);if(json2){let executor=agent.currentExecutorId?await executorRegistry.getExecutor(agent.currentExecutorId):null;console.log(JSON.stringify({agent,executor},null,2));return}if(printAgentFields(agent),agent.currentExecutorId){let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(executor)printExecutorFields(executor)}else console.log(`
2258
+ No active executor.`);console.log("")}function registerAgentShow(parent){parent.command("show <name>").description("Show agent identity and current executor detail").option("--json","Output as JSON").action(async(name,options)=>{try{await showAgent(name,options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentSpawn(parent){parent.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",process.env.GENIE_TEAM??"genie").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("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentStop(parent){parent.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}function registerAgentCommands(program2){let agent=program2.command("agent").description("Agent lifecycle management");registerAgentSpawn(agent),registerAgentStop(agent),registerAgentResume(agent),registerAgentKill(agent),registerAgentList(agent),registerAgentShow(agent),registerAgentAnswer(agent),registerAgentRegister(agent),registerAgentDirectory(agent),registerAgentInbox(agent),registerAgentBrief(agent),registerAgentLog(agent),registerAgentSend(agent),agent.on("command:*",(operands)=>{let cmd=operands[0],available=agent.commands.map((c)=>c.name()).join(", ");agent.error(`Unknown agent command '${cmd}'. Available: ${available}`)})}init_agents();async function handleTuiMode(){let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2()}async function findTauriBinary(){let{existsSync:existsSync26}=await import("fs"),{join:join35,dirname:dirname6}=await import("path"),{execSync:execSync8}=await import("child_process"),appName="genie-desktop",rootDir=join35(dirname6(new URL(import.meta.url).pathname),"..",".."),localBin=[join35(rootDir,"packages","genie-app","src-tauri","target","release","genie-desktop"),join35(rootDir,"packages","genie-app","src-tauri","target","debug","genie-desktop"),join35(rootDir,"dist","app","genie-desktop"),"/usr/local/bin/genie-desktop"].find((p)=>existsSync26(p));if(localBin)return localBin;try{return execSync8("which genie-desktop",{stdio:"ignore"}),"genie-desktop"}catch{return}}function registerAppCommand(program2){program2.command("app").description("Launch Genie desktop app (backend sidecar + views)").option("--backend-only","Start only the backend sidecar (IPC on stdin/stdout)").option("--tui","Fall back to terminal UI mode").option("--dev","Development mode").action(async(options)=>{if(options.tui){await handleTuiMode();return}if(options.backendOnly){await Promise.resolve().then(() => (init_src_backend(),exports_src_backend));return}let tauriBin=await findTauriBinary();if(tauriBin){console.log("\x1B[35m\u25C6 Genie App\x1B[0m Launching desktop...");let{execFileSync}=await import("child_process");try{execFileSync(tauriBin,[],{stdio:"inherit"})}catch{}return}console.log("\x1B[35m\u25C6 Genie App\x1B[0m Starting backend sidecar..."),console.log("\x1B[2mDesktop binary not found \u2014 running in sidecar mode.\x1B[0m"),console.log("\x1B[2mPG bridge + PTY manager + IPC on stdin/stdout\x1B[0m"),console.log(`\x1B[2mUse --tui for terminal UI, or pipe to a frontend shell.\x1B[0m
2259
+ `),await Promise.resolve().then(() => (init_src_backend(),exports_src_backend))})}init_audit();function printEventsTable(rows){if(rows.length===0){console.log("No audit events found.");return}let sorted=[...rows].reverse(),headers=["Time","Type","Entity","Event","Actor","Details"],data=sorted.map((r)=>[formatRelativeTimestamp(r.created_at),r.entity_type,r.entity_id,r.event_type,r.actor??"-",summarizeDetails(r.details)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))}),header=headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data){let line=row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | ");console.log(line)}console.log(`
2185
2260
  (${rows.length} event${rows.length===1?"":"s"})`)}function summarizeDetails(details){if(!details)return"";if(typeof details==="string")try{return summarizeDetails(JSON.parse(details))}catch{return details.slice(0,40)}if(Object.keys(details).length===0)return"";let keys=Object.keys(details);if(keys.length===1){let val=details[keys[0]];if(typeof val==="string")return val.slice(0,40);return JSON.stringify(val).slice(0,40)}if(details.error)return`error: ${String(details.error).slice(0,35)}`;if(details.duration_ms)return`${details.duration_ms}ms`;return JSON.stringify(details).slice(0,40)}function printErrorsTable(patterns2){if(patterns2.length===0){console.log("No error patterns found.");return}let headers=["Count","Event","Command","Error","Last Seen"],data=patterns2.map((p)=>[String(p.count),p.event_type,p.entity_id,p.error_message.slice(0,50),formatRelativeTimestamp(p.last_seen)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(50,Math.max(h.length,...colVals.map((v)=>v.length)))}),header=headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data){let line=row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | ");console.log(line)}console.log(`
2186
2261
  (${patterns2.length} pattern${patterns2.length===1?"":"s"})`)}async function eventsListCommand(options){try{let queryOpts={type:options.type,entity:options.entity,since:options.since??"1h",errorsOnly:options.errorsOnly,limit:options.limit?Number.parseInt(options.limit,10):50},rows=await queryAuditEvents(queryOpts);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying events: ${msg}`),process.exit(1)}}async function eventsErrorsCommand(options){try{let patterns2=await queryErrorPatterns(options.since);if(options.json)console.log(JSON.stringify(patterns2,null,2));else printErrorsTable(patterns2)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying error patterns: ${msg}`),process.exit(1)}}function resolveCostsGroupBy(options){if(options.byWish)return"wish";if(options.byModel)return"model";return"agent"}function printCostsTable(rows,groupBy){if(rows.length===0){console.log("No cost data found.");return}let headers=[groupBy==="agent"?"Agent":groupBy==="wish"?"Wish":"Model","Total Cost","Requests","Avg Cost"],data=rows.map((r)=>[r.group_key,`$${r.total_cost.toFixed(4)}`,String(r.request_count),`$${r.avg_cost.toFixed(4)}`]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCost=rows.reduce((sum,r)=>sum+r.total_cost,0),totalReqs=rows.reduce((sum,r)=>sum+r.request_count,0);console.log(`
2187
2262
  Total: $${totalCost.toFixed(4)} across ${totalReqs} requests`)}async function eventsCostsCommand(options){try{let since=options.today?"24h":options.since??"24h",groupBy=resolveCostsGroupBy(options),rows=await queryCostBreakdown(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printCostsTable(rows,groupBy),console.log(`
2188
2263
  \u26A0 OTel costs only include genie-spawned sessions. For full server costs: npx ccusage monthly`)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying costs: ${msg}`),process.exit(1)}}function printToolsTable(rows,groupBy){if(rows.length===0){console.log("No tool usage data found.");return}let headers=[groupBy==="tool"?"Tool":"Agent","Calls","Success","Errors","Avg Duration"],data=rows.map((r)=>[r.group_key,String(r.total_calls),String(r.success_count),String(r.error_count),r.avg_duration_ms!=null?`${r.avg_duration_ms.toFixed(0)}ms`:"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCalls=rows.reduce((sum,r)=>sum+r.total_calls,0);console.log(`
2189
- (${totalCalls} total tool calls)`)}async function eventsToolsCommand(options){try{let since=options.since??"24h",groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}async function eventsTimelineCommand(entityId,options){try{let rows=await queryTimeline(entityId);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else printSummary(summary)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync3}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result=spawnSync3("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result.stdout)process.stdout.write(result.stdout);if(result.status!==0)process.exit(result.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG");events.command("list",{isDefault:!0}).description("List recent audit events").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").action(async(options)=>{await eventsListCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Full event timeline for a task, agent, wish, or traceId").option("--json","Output as JSON").action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)})}import{execSync as execSync8}from"child_process";import{existsSync as existsSync26,mkdirSync as mkdirSync10,readFileSync as readFileSync13,writeFileSync as writeFileSync10}from"fs";import{dirname as dirname6,join as join33}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService;async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate(b2.name,22),24)} ${padRight(truncate(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
2264
+ (${totalCalls} total tool calls)`)}async function eventsToolsCommand(options){try{let since=options.since??"24h",groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}async function eventsTimelineCommand(entityId,options){try{let rows=await queryTimeline(entityId);if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else printSummary(summary)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync3}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result=spawnSync3("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result.stdout)process.stdout.write(result.stdout);if(result.status!==0)process.exit(result.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG");events.command("list",{isDefault:!0}).description("List recent audit events").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").action(async(options)=>{await eventsListCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Full event timeline for a task, agent, wish, or traceId").option("--json","Output as JSON").action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)})}import{execSync as execSync8}from"child_process";import{existsSync as existsSync26,mkdirSync as mkdirSync10,readFileSync as readFileSync13,writeFileSync as writeFileSync10}from"fs";import{dirname as dirname6,join as join35}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService3()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate2(b2.name,22),24)} ${padRight(truncate2(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
2190
2265
  ${boards.length} board${boards.length===1?"":"s"}`)}function printColumnPipeline(columns,header){console.log(`
2191
- ${header}`),console.log("\u2500".repeat(60));let sorted=[...columns].sort((a,b2)=>a.position-b2.position);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],arrow=i2<sorted.length-1?" \u2192":"",gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action}${arrow}`)}console.log("")}function printTemplateTable(templates){console.log(` ${padRight("NAME",24)} ${padRight("COLUMNS",10)} ${padRight("BUILTIN",10)} DESCRIPTION`),console.log(` ${"\u2500".repeat(80)}`);for(let t of templates){let colCount=String(t.columns.length),builtin=t.isBuiltin?"yes":"no",desc=t.description?truncate(t.description,30):"-";console.log(` ${padRight(truncate(t.name,22),24)} ${padRight(colCount,10)} ${padRight(builtin,10)} ${desc}`)}console.log(`
2192
- ${templates.length} template${templates.length===1?"":"s"}`)}async function handleBoardCreate(name,options){let bs=await getBoardService(),tmpl=await getTemplateService(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let columns;if(options.from){let template=await tmpl.getTemplate(options.from);if(!template)throw Error(`Template not found: ${options.from}`);columns=template.columns}else if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({name:colName.trim(),label:colName.trim(),gate:"human",position:i2}));let board=await bs.createBoard({name,projectId,description:options.description,columns});console.log(`Created board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleBoardList(options){let bs=await getBoardService(),ts3=await getTaskService(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let boards=await bs.listBoards(projectId,options.all);if(options.json){console.log(JSON.stringify(boards,null,2));return}let projects=await ts3.listProjects(),projectMap=new Map;for(let p of projects)projectMap.set(p.id,p.name);printBoardTable(boards,projectMap)}async function handleBoardShow(name,options){let ts3=await getTaskService(),board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board,null,2));return}let projectName="-";if(board.projectId){let proj=(await ts3.listProjects()).find((p)=>p.id===board.projectId);if(proj)projectName=proj.name}if(console.log(`
2266
+ ${header}`),console.log("\u2500".repeat(60));let sorted=[...columns].sort((a,b2)=>a.position-b2.position);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],arrow=i2<sorted.length-1?" \u2192":"",gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action}${arrow}`)}console.log("")}function printTemplateTable(templates){console.log(` ${padRight("NAME",24)} ${padRight("COLUMNS",10)} ${padRight("BUILTIN",10)} DESCRIPTION`),console.log(` ${"\u2500".repeat(80)}`);for(let t of templates){let colCount=String(t.columns.length),builtin=t.isBuiltin?"yes":"no",desc=t.description?truncate2(t.description,30):"-";console.log(` ${padRight(truncate2(t.name,22),24)} ${padRight(colCount,10)} ${padRight(builtin,10)} ${desc}`)}console.log(`
2267
+ ${templates.length} template${templates.length===1?"":"s"}`)}async function handleBoardCreate(name,options){let bs=await getBoardService(),tmpl=await getTemplateService(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let columns;if(options.from){let template=await tmpl.getTemplate(options.from);if(!template)throw Error(`Template not found: ${options.from}`);columns=template.columns}else if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({name:colName.trim(),label:colName.trim(),gate:"human",position:i2}));let board=await bs.createBoard({name,projectId,description:options.description,columns});console.log(`Created board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleBoardList(options){let bs=await getBoardService(),ts3=await getTaskService3(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let boards=await bs.listBoards(projectId,options.all);if(options.json){console.log(JSON.stringify(boards,null,2));return}let projects=await ts3.listProjects(),projectMap=new Map;for(let p of projects)projectMap.set(p.id,p.name);printBoardTable(boards,projectMap)}async function handleBoardShow(name,options){let ts3=await getTaskService3(),board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board,null,2));return}let projectName="-";if(board.projectId){let proj=(await ts3.listProjects()).find((p)=>p.id===board.projectId);if(proj)projectName=proj.name}if(console.log(`
2193
2268
  Board: ${board.name} (${board.id})`),board.description)console.log(`Description: ${board.description}`);console.log(`Project: ${projectName}`),console.log(`Columns: ${board.columns.length}`),console.log("\u2500".repeat(60));let tasks=await ts3.listTasks({boardId:board.id,allProjects:!0}),countByColumn=new Map;for(let t of tasks){let colId=t.stage;countByColumn.set(colId,(countByColumn.get(colId)??0)+1)}let sorted=[...board.columns].sort((a,b2)=>a.position-b2.position);console.log(`
2194
- Columns:`);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],count=countByColumn.get(c.name)??countByColumn.get(c.id)??0,gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action} \u2014 ${count} task${count===1?"":"s"}`)}console.log("")}function buildColumnUpdates(options){let updates={};if(options.gate)updates.gate=options.gate;if(options.action)updates.action=options.action;if(options.color)updates.color=options.color;if(options.rename)updates.name=options.rename,updates.label=options.rename;return updates}async function handleBoardEdit(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project);if(options.column){let col=board.columns.find((c)=>c.name===options.column||c.label===options.column);if(!col)throw Error(`Column not found: ${options.column}`);let updates=buildColumnUpdates(options);if(!await bs.updateColumn(board.id,col.id,updates))throw Error(`Failed to update column: ${options.column}`);console.log(`Updated column "${options.column}" on board "${board.name}".`);return}let boardUpdates={};if(options.name)boardUpdates.name=options.name;if(options.description)boardUpdates.description=options.description;if(Object.keys(boardUpdates).length===0)console.error("Error: No updates specified. Use --column, --name, or --description."),process.exit(1);let updated=await bs.updateBoard(board.id,boardUpdates);if(!updated)throw Error(`Failed to update board: ${name}`);console.log(`Updated board "${updated.name}" (${updated.id}).`)}async function handleBoardDelete(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project);if(!options.force)console.log(`Deleting board "${board.name}" (${board.id})...`);if(!await bs.deleteBoard(board.id))throw Error(`Failed to delete board: ${name}`);console.log(`Deleted board "${board.name}" (${board.id}).`)}async function handleBoardColumns(name,options){let board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board.columns,null,2));return}printColumnPipeline(board.columns,`Board: ${board.name} (${board.columns.length} columns)`)}async function handleBoardUse(name,options){let board=await resolveBoard(name,options.project),repoRoot=execSync8("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),genieDir=join33(repoRoot,".genie"),configPath2=join33(genieDir,"config.json");if(!existsSync26(genieDir))mkdirSync10(genieDir,{recursive:!0});let config={};if(existsSync26(configPath2))try{config=JSON.parse(readFileSync13(configPath2,"utf-8"))}catch{}config.activeBoard=board.id,writeFileSync10(configPath2,`${JSON.stringify(config,null,2)}
2269
+ Columns:`);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],count=countByColumn.get(c.name)??countByColumn.get(c.id)??0,gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action} \u2014 ${count} task${count===1?"":"s"}`)}console.log("")}function buildColumnUpdates(options){let updates={};if(options.gate)updates.gate=options.gate;if(options.action)updates.action=options.action;if(options.color)updates.color=options.color;if(options.rename)updates.name=options.rename,updates.label=options.rename;return updates}async function handleBoardEdit(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project);if(options.column){let col=board.columns.find((c)=>c.name===options.column||c.label===options.column);if(!col)throw Error(`Column not found: ${options.column}`);let updates=buildColumnUpdates(options);if(!await bs.updateColumn(board.id,col.id,updates))throw Error(`Failed to update column: ${options.column}`);console.log(`Updated column "${options.column}" on board "${board.name}".`);return}let boardUpdates={};if(options.name)boardUpdates.name=options.name;if(options.description)boardUpdates.description=options.description;if(Object.keys(boardUpdates).length===0)console.error("Error: No updates specified. Use --column, --name, or --description."),process.exit(1);let updated=await bs.updateBoard(board.id,boardUpdates);if(!updated)throw Error(`Failed to update board: ${name}`);console.log(`Updated board "${updated.name}" (${updated.id}).`)}async function handleBoardDelete(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project);if(!options.force)console.log(`Deleting board "${board.name}" (${board.id})...`);if(!await bs.deleteBoard(board.id))throw Error(`Failed to delete board: ${name}`);console.log(`Deleted board "${board.name}" (${board.id}).`)}async function handleBoardColumns(name,options){let board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board.columns,null,2));return}printColumnPipeline(board.columns,`Board: ${board.name} (${board.columns.length} columns)`)}async function handleBoardUse(name,options){let board=await resolveBoard(name,options.project),repoRoot=execSync8("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),genieDir=join35(repoRoot,".genie"),configPath2=join35(genieDir,"config.json");if(!existsSync26(genieDir))mkdirSync10(genieDir,{recursive:!0});let config={};if(existsSync26(configPath2))try{config=JSON.parse(readFileSync13(configPath2,"utf-8"))}catch{}config.activeBoard=board.id,writeFileSync10(configPath2,`${JSON.stringify(config,null,2)}
2195
2270
  `),console.log(`Active board set to "${board.name}" (${board.id})`)}async function handleBoardExport(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project),exported=await bs.exportBoard(board.id),json2=JSON.stringify(exported,null,2);if(options.output){let dir=dirname6(options.output);if(!existsSync26(dir))mkdirSync10(dir,{recursive:!0});writeFileSync10(options.output,`${json2}
2196
2271
  `),console.log(`Exported board "${board.name}" to ${options.output}`)}else console.log(json2)}async function handleBoardReconcile(name,options){let{reconcileBoard:reconcileBoard2}=await Promise.resolve().then(() => (init_board_service(),exports_board_service)),board=await resolveBoard(name,options.project),result=await reconcileBoard2(board.id);if(options.json){console.log(JSON.stringify(result,null,2));return}if(result.fixed===0&&result.orphaned===0){console.log(`Board "${board.name}": all tasks have valid column_ids.`);return}if(console.log(`Board "${board.name}" reconciliation:`),console.log(` Fixed: ${result.fixed} task${result.fixed===1?"":"s"}`),result.orphaned>0){let count=result.orphaned;console.log(` Still orphaned: ${count} task${count===1?"":"s"} (stage doesn't match any column)`)}}async function handleBoardImport(options){let bs=await getBoardService(),projectId=await resolveProjectId(options.project),raw=readFileSync13(options.json,"utf-8"),data=JSON.parse(raw),board=await bs.importBoard(data,projectId);console.log(`Imported board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleTemplateList(options){let templates=await(await getTemplateService()).listTemplates();if(options.json){console.log(JSON.stringify(templates,null,2));return}printTemplateTable(templates)}async function handleTemplateShow(name,options){let template=await(await getTemplateService()).getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(options.json){console.log(JSON.stringify(template,null,2));return}if(console.log(`
2197
- Template: ${template.name} (${template.id})`),template.description)console.log(`Description: ${template.description}`);if(template.icon)console.log(`Icon: ${template.icon}`);console.log(`Built-in: ${template.isBuiltin?"yes":"no"}`),printColumnPipeline(template.columns,`Pipeline (${template.columns.length} columns)`)}async function handleTemplateCreate(name,options){let tmpl=await getTemplateService();if(options.fromBoard){let board=await(await getBoardService()).getBoard(options.fromBoard);if(!board)throw Error(`Board not found: ${options.fromBoard}`);let template2=await tmpl.snapshotFromBoard(board.id,name);console.log(`Created template "${template2.name}" (${template2.id}) from board "${board.name}" with ${template2.columns.length} columns`);return}let columns;if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({id:crypto.randomUUID(),name:colName.trim(),label:colName.trim(),gate:"human",action:null,auto_advance:!1,transitions:[],roles:["*"],color:"#94a3b8",parallel:!1,on_fail:null,position:i2}));let template=await tmpl.createTemplate({name,description:options.description,columns});console.log(`Created template "${template.name}" (${template.id}) with ${template.columns.length} columns`)}async function handleTemplateEdit(name,options){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!options.column)console.error("Error: --column is required for template edit."),process.exit(1);let updates={};if(options.gate)updates.gate=options.gate;if(options.action)updates.action=options.action;if(options.color)updates.color=options.color;if(options.rename)updates.name=options.rename,updates.label=options.rename;if(!await tmpl.updateTemplateColumn(template.id,options.column,updates))throw Error(`Failed to update template: ${name}`);console.log(`Updated column "${options.column}" on template "${template.name}".`)}async function handleTemplateRename(oldName,newName){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(oldName);if(!template)throw Error(`Template not found: ${oldName}`);let updated=await tmpl.renameTemplate(template.id,newName);if(!updated)throw Error(`Failed to rename template: ${oldName}`);console.log(`Renamed template "${oldName}" to "${updated.name}".`)}async function handleTemplateDelete(name){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!await tmpl.deleteTemplate(template.id))throw Error(`Failed to delete template: ${name}`);console.log(`Deleted template "${template.name}" (${template.id}).`)}function registerBoardCommands(program2){let board=program2.command("board").description("Board and pipeline management");board.command("create <name>").description("Create a new board").option("--project <project>","Project name").option("--from <template>","Create from template name").option("--columns <columns>","Comma-separated column names").option("--description <text>","Board description").action(async(name,options)=>{try{await handleBoardCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("list").description("List all boards").option("--project <project>","Filter by project").option("--all","Include archived boards").option("--json","Output as JSON").action(async(options)=>{try{await handleBoardList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("show <name...>").description("Show board detail").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardShow(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("edit <name...>").description("Edit board or column properties").option("--project <project>","Disambiguate by project").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--color <color>","New color hex").option("--rename <new>","Rename the column").option("--name <new>","Rename the board itself").option("--description <text>","Update description").action(async(nameParts,options)=>{try{await handleBoardEdit(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("delete <name...>").description("Delete a board").option("--project <project>","Disambiguate by project").option("--force","Skip confirmation").action(async(nameParts,options)=>{try{await handleBoardDelete(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("columns <name...>").description("Show board column pipeline").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardColumns(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("use <name...>").description("Set active board for current repo").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{await handleBoardUse(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("export <name...>").description("Export board as JSON").option("--project <project>","Disambiguate by project").option("--output <file>","Write to file instead of stdout").option("--json","Output as JSON (default, accepted for consistency)").action(async(nameParts,options)=>{try{await handleBoardExport(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("reconcile <name...>").description("Fix orphaned column_ids by matching task stage to board columns").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardReconcile(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("archive <name...>").description("Archive a board and its unfinished tasks").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{let ts3=await getTaskService(),board2=await resolveBoard(nameParts.join(" "),options.project);await ts3.archiveBoard(board2.id),console.log(`Archived board "${board2.name}" and its unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("import").description("Import board from JSON file").requiredOption("--json <file>","JSON file to import").requiredOption("--project <project>","Target project").action(async(options)=>{try{await handleBoardImport(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let template=board.command("template").description("Board template management");template.command("list").description("List all board templates").option("--json","Output as JSON").action(async(options)=>{try{await handleTemplateList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("show <name>").description("Show template detail with pipeline view").option("--json","Output as JSON").action(async(name,options)=>{try{await handleTemplateShow(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("create <name>").description("Create a board template").option("--from-board <board>","Create from existing board").option("--columns <columns>","Comma-separated column names").option("--description <text>","Template description").action(async(name,options)=>{try{await handleTemplateCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("edit <name>").description("Edit a template column").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--rename <new>","Rename the column").option("--color <color>","New color hex").action(async(name,options)=>{try{await handleTemplateEdit(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("rename <old> <new>").description("Rename a template").action(async(oldName,newName)=>{try{await handleTemplateRename(oldName,newName)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("delete <name>").description("Delete a template").action(async(name)=>{try{await handleTemplateDelete(name)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _brief;async function getBrief(){if(!_brief)_brief=await Promise.resolve().then(() => (init_brief(),exports_brief));return _brief}async function handleBrief(options){let team=options.team??process.env.GENIE_TEAM;if(!team)console.error("Error: --team is required (or set GENIE_TEAM)"),process.exit(1);let agent=options.agent??process.env.GENIE_AGENT_NAME,briefService=await getBrief(),brief=await briefService.generateBrief({team,agent,since:options.since,repoPath:process.cwd()});console.log(briefService.formatBrief(brief))}function registerBriefCommands(program2){program2.command("brief").description("Show startup brief \u2014 aggregated context since last session").option("--team <name>","Team name (default: GENIE_TEAM)").option("--agent <name>","Agent name (default: GENIE_AGENT_NAME)").option("--since <iso>","Start timestamp (default: last executor end)").action(async(options)=>{try{await handleBrief(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}import{existsSync as existsSync27,mkdirSync as mkdirSync11,readFileSync as readFileSync14,unlinkSync as unlinkSync6,writeFileSync as writeFileSync11}from"fs";import{homedir as homedir22}from"os";import{join as join34}from"path";function genieHome2(){return process.env.GENIE_HOME??join34(homedir22(),".genie")}function pidFilePath(){return join34(genieHome2(),"scheduler.pid")}function logFilePath(){return join34(genieHome2(),"logs","scheduler.log")}function systemdDir(){return join34(homedir22(),".config","systemd","user")}function systemdUnitPath(){return join34(systemdDir(),"genie-scheduler.service")}function readPid(){let path2=pidFilePath();if(!existsSync27(path2))return null;let raw=readFileSync14(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writePid(pid){let dir=genieHome2();mkdirSync11(dir,{recursive:!0}),writeFileSync11(pidFilePath(),String(pid),"utf-8")}function removePid(){let path2=pidFilePath();if(existsSync27(path2))try{unlinkSync6(path2)}catch{}}function isProcessAlive2(pid){try{return process.kill(pid,0),!0}catch{return!1}}function generateSystemdUnit(){let genieBin=process.argv[1]??"genie";return`[Unit]
2272
+ Template: ${template.name} (${template.id})`),template.description)console.log(`Description: ${template.description}`);if(template.icon)console.log(`Icon: ${template.icon}`);console.log(`Built-in: ${template.isBuiltin?"yes":"no"}`),printColumnPipeline(template.columns,`Pipeline (${template.columns.length} columns)`)}async function handleTemplateCreate(name,options){let tmpl=await getTemplateService();if(options.fromBoard){let board=await(await getBoardService()).getBoard(options.fromBoard);if(!board)throw Error(`Board not found: ${options.fromBoard}`);let template2=await tmpl.snapshotFromBoard(board.id,name);console.log(`Created template "${template2.name}" (${template2.id}) from board "${board.name}" with ${template2.columns.length} columns`);return}let columns;if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({id:crypto.randomUUID(),name:colName.trim(),label:colName.trim(),gate:"human",action:null,auto_advance:!1,transitions:[],roles:["*"],color:"#94a3b8",parallel:!1,on_fail:null,position:i2}));let template=await tmpl.createTemplate({name,description:options.description,columns});console.log(`Created template "${template.name}" (${template.id}) with ${template.columns.length} columns`)}async function handleTemplateEdit(name,options){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!options.column)console.error("Error: --column is required for template edit."),process.exit(1);let updates={};if(options.gate)updates.gate=options.gate;if(options.action)updates.action=options.action;if(options.color)updates.color=options.color;if(options.rename)updates.name=options.rename,updates.label=options.rename;if(!await tmpl.updateTemplateColumn(template.id,options.column,updates))throw Error(`Failed to update template: ${name}`);console.log(`Updated column "${options.column}" on template "${template.name}".`)}async function handleTemplateRename(oldName,newName){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(oldName);if(!template)throw Error(`Template not found: ${oldName}`);let updated=await tmpl.renameTemplate(template.id,newName);if(!updated)throw Error(`Failed to rename template: ${oldName}`);console.log(`Renamed template "${oldName}" to "${updated.name}".`)}async function handleTemplateDelete(name){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!await tmpl.deleteTemplate(template.id))throw Error(`Failed to delete template: ${name}`);console.log(`Deleted template "${template.name}" (${template.id}).`)}function registerBoardCommands(program2){let board=program2.command("board").description("Board and pipeline management");board.command("create <name>").description("Create a new board").option("--project <project>","Project name").option("--from <template>","Create from template name").option("--columns <columns>","Comma-separated column names").option("--description <text>","Board description").action(async(name,options)=>{try{await handleBoardCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("list").description("List all boards").option("--project <project>","Filter by project").option("--all","Include archived boards").option("--json","Output as JSON").action(async(options)=>{try{await handleBoardList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("show <name...>").description("Show board detail").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardShow(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("edit <name...>").description("Edit board or column properties").option("--project <project>","Disambiguate by project").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--color <color>","New color hex").option("--rename <new>","Rename the column").option("--name <new>","Rename the board itself").option("--description <text>","Update description").action(async(nameParts,options)=>{try{await handleBoardEdit(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("delete <name...>").description("Delete a board").option("--project <project>","Disambiguate by project").option("--force","Skip confirmation").action(async(nameParts,options)=>{try{await handleBoardDelete(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("columns <name...>").description("Show board column pipeline").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardColumns(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("use <name...>").description("Set active board for current repo").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{await handleBoardUse(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("export <name...>").description("Export board as JSON").option("--project <project>","Disambiguate by project").option("--output <file>","Write to file instead of stdout").option("--json","Output as JSON (default, accepted for consistency)").action(async(nameParts,options)=>{try{await handleBoardExport(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("reconcile <name...>").description("Fix orphaned column_ids by matching task stage to board columns").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardReconcile(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("archive <name...>").description("Archive a board and its unfinished tasks").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{let ts3=await getTaskService3(),board2=await resolveBoard(nameParts.join(" "),options.project);await ts3.archiveBoard(board2.id),console.log(`Archived board "${board2.name}" and its unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("import").description("Import board from JSON file").requiredOption("--json <file>","JSON file to import").requiredOption("--project <project>","Target project").action(async(options)=>{try{await handleBoardImport(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let template=board.command("template").description("Board template management");template.command("list").description("List all board templates").option("--json","Output as JSON").action(async(options)=>{try{await handleTemplateList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("show <name>").description("Show template detail with pipeline view").option("--json","Output as JSON").action(async(name,options)=>{try{await handleTemplateShow(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("create <name>").description("Create a board template").option("--from-board <board>","Create from existing board").option("--columns <columns>","Comma-separated column names").option("--description <text>","Template description").action(async(name,options)=>{try{await handleTemplateCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("edit <name>").description("Edit a template column").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--rename <new>","Rename the column").option("--color <color>","New color hex").action(async(name,options)=>{try{await handleTemplateEdit(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("rename <old> <new>").description("Rename a template").action(async(oldName,newName)=>{try{await handleTemplateRename(oldName,newName)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("delete <name>").description("Delete a template").action(async(name)=>{try{await handleTemplateDelete(name)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _brief2;async function getBrief2(){if(!_brief2)_brief2=await Promise.resolve().then(() => (init_brief(),exports_brief));return _brief2}async function handleBrief2(options){let team=options.team??process.env.GENIE_TEAM;if(!team)console.error("Error: --team is required (or set GENIE_TEAM)"),process.exit(1);let agent=options.agent??process.env.GENIE_AGENT_NAME,briefService=await getBrief2(),brief=await briefService.generateBrief({team,agent,since:options.since,repoPath:process.cwd()});console.log(briefService.formatBrief(brief))}function registerBriefCommands(program2){program2.command("brief").description("Show startup brief \u2014 aggregated context since last session").option("--team <name>","Team name (default: GENIE_TEAM)").option("--agent <name>","Agent name (default: GENIE_AGENT_NAME)").option("--since <iso>","Start timestamp (default: last executor end)").action(async(options)=>{try{await handleBrief2(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}import{existsSync as existsSync27,mkdirSync as mkdirSync11,readFileSync as readFileSync14,unlinkSync as unlinkSync6,writeFileSync as writeFileSync11}from"fs";import{homedir as homedir24}from"os";import{join as join36}from"path";function genieHome2(){return process.env.GENIE_HOME??join36(homedir24(),".genie")}function pidFilePath(){return join36(genieHome2(),"scheduler.pid")}function logFilePath(){return join36(genieHome2(),"logs","scheduler.log")}function systemdDir(){return join36(homedir24(),".config","systemd","user")}function systemdUnitPath(){return join36(systemdDir(),"genie-scheduler.service")}function readPid(){let path2=pidFilePath();if(!existsSync27(path2))return null;let raw=readFileSync14(path2,"utf-8").trim(),pid=Number.parseInt(raw,10);if(Number.isNaN(pid)||pid<=0)return null;return pid}function writePid(pid){let dir=genieHome2();mkdirSync11(dir,{recursive:!0}),writeFileSync11(pidFilePath(),String(pid),"utf-8")}function removePid(){let path2=pidFilePath();if(existsSync27(path2))try{unlinkSync6(path2)}catch{}}function isProcessAlive2(pid){try{return process.kill(pid,0),!0}catch{return!1}}function generateSystemdUnit(){let genieBin=process.argv[1]??"genie";return`[Unit]
2198
2273
  Description=Genie Scheduler Daemon
2199
2274
  Documentation=https://github.com/automagik/genie
2200
2275
  After=network.target
@@ -2205,7 +2280,7 @@ ExecStart=${process.execPath??"bun"} ${genieBin} daemon start --foreground
2205
2280
  Restart=on-failure
2206
2281
  RestartSec=5
2207
2282
  Environment=GENIE_HOME=${genieHome2()}
2208
- WorkingDirectory=${homedir22()}
2283
+ WorkingDirectory=${homedir24()}
2209
2284
 
2210
2285
  # Logging handled by the daemon itself (structured JSON)
2211
2286
  StandardOutput=journal
@@ -2234,7 +2309,7 @@ Genie Scheduler Daemon`),console.log("\u2500".repeat(50)),console.log(` Status:
2234
2309
  (showing last ${lines} of ${allLines.length} entries)`)}async function tailFollow(filePath,initialLines){let{watch:watch2}=await import("fs");tailStatic(filePath,initialLines),console.log(`
2235
2310
  --- following (Ctrl+C to exit) ---
2236
2311
  `);let lastSize=existsSync27(filePath)?readFileSync14(filePath).length:0,watcher2=watch2(filePath,()=>{try{let content=readFileSync14(filePath,"utf-8");if(content.length>lastSize){let newLines=content.slice(lastSize).trim().split(`
2237
- `).filter(Boolean);for(let line of newLines)printLogLine(line);lastSize=content.length}}catch{}});process.on("SIGINT",()=>{watcher2.close(),process.exit(0)}),await new Promise(()=>{})}function printLogLine(raw){try{let entry=JSON.parse(raw),ts3=entry.timestamp?new Date(entry.timestamp).toLocaleTimeString("en-US",{hour12:!1}):"??:??:??",level=(entry.level??"info").toUpperCase().padEnd(5),event=entry.event??"unknown",extras=Object.entries(entry).filter(([k])=>!["timestamp","level","event"].includes(k)).map(([k,v])=>`${k}=${typeof v==="object"?JSON.stringify(v):v}`).join(" ");console.log(`${ts3} ${level} ${event}${extras?` ${extras}`:""}`)}catch{console.log(raw)}}function formatUptime(ms){let seconds=Math.floor(ms/1000),minutes=Math.floor(seconds/60),hours=Math.floor(minutes/60),days=Math.floor(hours/24);if(days>0)return`${days}d ${hours%24}h ${minutes%60}m`;if(hours>0)return`${hours}h ${minutes%60}m`;if(minutes>0)return`${minutes}m ${seconds%60}s`;return`${seconds}s`}function registerDaemonCommands(program2){let daemon=program2.command("daemon").description("Manage scheduler daemon lifecycle");daemon.command("install").description("Generate systemd service unit and enable it").action(async()=>{await daemonInstallCommand()}),daemon.command("start").description("Start the scheduler daemon").option("--foreground","Run in foreground (for systemd ExecStart)").action(async(options)=>{await daemonStartCommand(options)}),daemon.command("stop").description("Stop the scheduler daemon gracefully").action(async()=>{await daemonStopCommand()}),daemon.command("status").description("Show daemon state, PID, uptime, and trigger stats").action(async()=>{await daemonStatusCommand()}),daemon.command("logs").description("Tail structured JSON scheduler log").option("--follow, -f","Follow log output").option("--lines <n>","Number of lines to show (default: 20)",Number.parseInt).action(async(options)=>{await daemonLogsCommand(options)})}import{createInterface as createInterface3}from"readline";init_db();init_wish_state();import{spawnSync as spawnSync3}from"child_process";import{existsSync as existsSync28,mkdirSync as mkdirSync12,readFileSync as readFileSync15,renameSync as renameSync3,statSync,writeFileSync as writeFileSync12}from"fs";import{join as join35}from"path";import{gunzipSync,gzipSync}from"zlib";var DB_NAME2="genie",DB_USER="postgres",DB_HOST="127.0.0.1",SNAPSHOT_FILE="snapshot.sql.gz";function getSnapshotPath(cwd){let repoRoot=resolveRepoPath(cwd);return join35(repoRoot,".genie",SNAPSHOT_FILE)}function pgEnv(port){return{...process.env,PGHOST:DB_HOST,PGPORT:String(port),PGUSER:DB_USER,PGPASSWORD:DB_USER,PGDATABASE:DB_NAME2}}function backup(cwd){let port=getActivePort(),snapshotPath=getSnapshotPath(cwd),genieDir=join35(resolveRepoPath(cwd),".genie"),tmpPath=`${snapshotPath}.tmp`;mkdirSync12(genieDir,{recursive:!0});let result=spawnSync3("pg_dump",["--no-owner","--no-acl"],{env:pgEnv(port),stdio:["pipe","pipe","pipe"],timeout:120000,maxBuffer:1073741824});if(result.status!==0){let stderr=result.stderr?.toString().trim()||"unknown error";throw Error(`pg_dump failed (exit ${result.status}): ${stderr}`)}let compressed=gzipSync(result.stdout);writeFileSync12(tmpPath,compressed),renameSync3(tmpPath,snapshotPath);let compressedBytes=statSync(snapshotPath).size,uncompressedBytes=0;try{let sizeResult=spawnSync3("psql",["-t","-A","-c",`SELECT pg_database_size('${DB_NAME2}')`],{env:pgEnv(port),encoding:"utf-8",timeout:1e4});if(sizeResult.status===0)uncompressedBytes=Number.parseInt(sizeResult.stdout.trim(),10)||0}catch{}return{path:snapshotPath,compressedBytes,uncompressedBytes}}function restore(snapshotFile,cwd){let port=getActivePort(),filePath=snapshotFile??getSnapshotPath(cwd);if(!existsSync28(filePath))throw Error(`Snapshot not found: ${filePath}`);let env=pgEnv(port),adminEnv={...env,PGDATABASE:"postgres"};spawnSync3("psql",["-c",`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME2}' AND pid <> pg_backend_pid()`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});let dropResult=spawnSync3("psql",["-c",`DROP DATABASE IF EXISTS ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(dropResult.status!==0)throw Error(`Failed to drop database: ${dropResult.stderr?.toString().trim()}`);let createResult=spawnSync3("psql",["-c",`CREATE DATABASE ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(createResult.status!==0)throw Error(`Failed to create database: ${createResult.stderr?.toString().trim()}`);let compressed=readFileSync15(filePath),sql=gunzipSync(compressed),restoreResult=spawnSync3("psql",[],{env:{...env,PGDATABASE:DB_NAME2},input:sql,stdio:["pipe","pipe","pipe"],timeout:300000,maxBuffer:1073741824});if(restoreResult.status!==0)throw Error(`psql restore failed (exit ${restoreResult.status}): ${restoreResult.stderr?.toString().trim()}`)}init_db_migrations();init_db();function printTable(rows){if(rows.length===0){console.log("(0 rows)");return}let columns=Object.keys(rows[0]),widths=columns.map((col)=>{let values2=rows.map((r)=>String(r[col]??"NULL"));return Math.max(col.length,...values2.map((v)=>v.length))}),header=columns.map((col,i2)=>padRight(col,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of rows){let line=columns.map((col,i2)=>padRight(String(row[col]??"NULL"),widths[i2])).join(" | ");console.log(line)}console.log(`(${rows.length} row${rows.length===1?"":"s"})`)}async function dbStatusCommand(){let port=getActivePort(),dataDir=getDataDir();if(console.log(`
2312
+ `).filter(Boolean);for(let line of newLines)printLogLine(line);lastSize=content.length}}catch{}});process.on("SIGINT",()=>{watcher2.close(),process.exit(0)}),await new Promise(()=>{})}function printLogLine(raw){try{let entry=JSON.parse(raw),ts3=entry.timestamp?new Date(entry.timestamp).toLocaleTimeString("en-US",{hour12:!1}):"??:??:??",level=(entry.level??"info").toUpperCase().padEnd(5),event=entry.event??"unknown",extras=Object.entries(entry).filter(([k])=>!["timestamp","level","event"].includes(k)).map(([k,v])=>`${k}=${typeof v==="object"?JSON.stringify(v):v}`).join(" ");console.log(`${ts3} ${level} ${event}${extras?` ${extras}`:""}`)}catch{console.log(raw)}}function formatUptime(ms){let seconds=Math.floor(ms/1000),minutes=Math.floor(seconds/60),hours=Math.floor(minutes/60),days=Math.floor(hours/24);if(days>0)return`${days}d ${hours%24}h ${minutes%60}m`;if(hours>0)return`${hours}h ${minutes%60}m`;if(minutes>0)return`${minutes}m ${seconds%60}s`;return`${seconds}s`}function registerDaemonCommands(program2){let daemon=program2.command("daemon").description("Manage scheduler daemon lifecycle");daemon.command("install").description("Generate systemd service unit and enable it").action(async()=>{await daemonInstallCommand()}),daemon.command("start").description("Start the scheduler daemon").option("--foreground","Run in foreground (for systemd ExecStart)").action(async(options)=>{await daemonStartCommand(options)}),daemon.command("stop").description("Stop the scheduler daemon gracefully").action(async()=>{await daemonStopCommand()}),daemon.command("status").description("Show daemon state, PID, uptime, and trigger stats").action(async()=>{await daemonStatusCommand()}),daemon.command("logs").description("Tail structured JSON scheduler log").option("--follow, -f","Follow log output").option("--lines <n>","Number of lines to show (default: 20)",Number.parseInt).action(async(options)=>{await daemonLogsCommand(options)})}import{createInterface as createInterface3}from"readline";init_db();init_wish_state();import{spawnSync as spawnSync3}from"child_process";import{existsSync as existsSync28,mkdirSync as mkdirSync12,readFileSync as readFileSync15,renameSync as renameSync3,statSync,writeFileSync as writeFileSync12}from"fs";import{join as join37}from"path";import{gunzipSync,gzipSync}from"zlib";var DB_NAME2="genie",DB_USER="postgres",DB_HOST="127.0.0.1",SNAPSHOT_FILE="snapshot.sql.gz";function getSnapshotPath(cwd){let repoRoot=resolveRepoPath(cwd);return join37(repoRoot,".genie",SNAPSHOT_FILE)}function pgEnv(port){return{...process.env,PGHOST:DB_HOST,PGPORT:String(port),PGUSER:DB_USER,PGPASSWORD:DB_USER,PGDATABASE:DB_NAME2}}function backup(cwd){let port=getActivePort(),snapshotPath=getSnapshotPath(cwd),genieDir=join37(resolveRepoPath(cwd),".genie"),tmpPath=`${snapshotPath}.tmp`;mkdirSync12(genieDir,{recursive:!0});let result=spawnSync3("pg_dump",["--no-owner","--no-acl"],{env:pgEnv(port),stdio:["pipe","pipe","pipe"],timeout:120000,maxBuffer:1073741824});if(result.status!==0){let stderr=result.stderr?.toString().trim()||"unknown error";throw Error(`pg_dump failed (exit ${result.status}): ${stderr}`)}let compressed=gzipSync(result.stdout);writeFileSync12(tmpPath,compressed),renameSync3(tmpPath,snapshotPath);let compressedBytes=statSync(snapshotPath).size,uncompressedBytes=0;try{let sizeResult=spawnSync3("psql",["-t","-A","-c",`SELECT pg_database_size('${DB_NAME2}')`],{env:pgEnv(port),encoding:"utf-8",timeout:1e4});if(sizeResult.status===0)uncompressedBytes=Number.parseInt(sizeResult.stdout.trim(),10)||0}catch{}return{path:snapshotPath,compressedBytes,uncompressedBytes}}function restore(snapshotFile,cwd){let port=getActivePort(),filePath=snapshotFile??getSnapshotPath(cwd);if(!existsSync28(filePath))throw Error(`Snapshot not found: ${filePath}`);let env=pgEnv(port),adminEnv={...env,PGDATABASE:"postgres"};spawnSync3("psql",["-c",`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${DB_NAME2}' AND pid <> pg_backend_pid()`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});let dropResult=spawnSync3("psql",["-c",`DROP DATABASE IF EXISTS ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(dropResult.status!==0)throw Error(`Failed to drop database: ${dropResult.stderr?.toString().trim()}`);let createResult=spawnSync3("psql",["-c",`CREATE DATABASE ${DB_NAME2}`],{env:adminEnv,stdio:["pipe","pipe","pipe"],timeout:1e4});if(createResult.status!==0)throw Error(`Failed to create database: ${createResult.stderr?.toString().trim()}`);let compressed=readFileSync15(filePath),sql=gunzipSync(compressed),restoreResult=spawnSync3("psql",[],{env:{...env,PGDATABASE:DB_NAME2},input:sql,stdio:["pipe","pipe","pipe"],timeout:300000,maxBuffer:1073741824});if(restoreResult.status!==0)throw Error(`psql restore failed (exit ${restoreResult.status}): ${restoreResult.stderr?.toString().trim()}`)}init_db_migrations();init_db();function printTable(rows){if(rows.length===0){console.log("(0 rows)");return}let columns=Object.keys(rows[0]),widths=columns.map((col)=>{let values2=rows.map((r)=>String(r[col]??"NULL"));return Math.max(col.length,...values2.map((v)=>v.length))}),header=columns.map((col,i2)=>padRight(col,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of rows){let line=columns.map((col,i2)=>padRight(String(row[col]??"NULL"),widths[i2])).join(" | ");console.log(line)}console.log(`(${rows.length} row${rows.length===1?"":"s"})`)}async function dbStatusCommand(){let port=getActivePort(),dataDir=getDataDir();if(console.log(`
2238
2313
  Genie Database Status`),console.log("\u2500".repeat(50)),console.log(` Port: ${port}`),console.log(" Host: 127.0.0.1"),console.log(` Data dir: ${dataDir}`),!await isAvailable()){console.log(" Status: stopped"),console.log(`
2239
2314
  pgserve is not running. It will auto-start on first use.`),console.log("");return}console.log(" Status: running");try{let sql=await getConnection(),sizeResult=await sql`SELECT pg_size_pretty(pg_database_size(current_database())) AS size`;console.log(` DB size: ${sizeResult[0].size}`);let migrations=await getMigrationStatus(sql);console.log(`
2240
2315
  Migrations: ${migrations.applied.length} applied, ${migrations.pending.length} pending`);let tables=await sql`
@@ -2244,22 +2319,21 @@ Genie Database Status`),console.log("\u2500".repeat(50)),console.log(` Port:
2244
2319
  `;if(tables.length>0){console.log(`
2245
2320
  Table Row Counts:`);let maxNameLen=Math.max(...tables.map((t)=>t.tablename.length),5);for(let table of tables){let count=(await sql.unsafe(`SELECT count(*) AS cnt FROM "${table.tablename}"`))[0].cnt;console.log(` ${padRight(table.tablename,maxNameLen)} ${count}`)}}await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`
2246
2321
  Error querying database: ${message}`)}console.log("")}async function dbMigrateCommand(){try{let sql=await getConnection(),status=await getMigrationStatus(sql);if(status.pending.length===0){console.log("All migrations are up to date."),console.log(` Applied: ${status.applied.length} migration${status.applied.length===1?"":"s"}`),await shutdown();return}console.log(`Running ${status.pending.length} pending migration${status.pending.length===1?"":"s"}...`);let results=await runMigrations(sql);for(let r of results)console.log(` Applied: ${r.name} (${r.applied_at})`);console.log(`
2247
- Done. ${results.length} migration${results.length===1?"":"s"} applied.`),await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Error running migrations: ${message}`),process.exit(1)}}async function dbQueryCommand(query){try{let result=await(await getConnection()).unsafe(query);if(result.length===0&&result.count!==void 0)console.log(`Query OK, ${result.count} row${result.count===1?"":"s"} affected.`);else printTable(result);await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Query error: ${message}`),process.exit(1)}}function formatBytes(bytes){if(bytes===0)return"0B";let units=["B","KB","MB","GB"],i2=Math.min(Math.floor(Math.log(bytes)/Math.log(1024)),units.length-1),value=bytes/1024**i2;return`${value<10?value.toFixed(1):Math.round(value)}${units[i2]}`}async function dbBackupCommand(){if(!await isAvailable())console.error("Database is not running. Start it with: genie db status"),process.exit(1);try{let result=backup(),compressed=formatBytes(result.compressedBytes),uncompressed=result.uncompressedBytes>0?`, ${formatBytes(result.uncompressedBytes)} uncompressed`:"";console.log(`\u2713 Backup created: ${result.path} (${compressed}${uncompressed})`),await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Backup failed: ${message}`),process.exit(1)}}function confirm(prompt2){let rl=createInterface3({input:process.stdin,output:process.stdout});return new Promise((resolve5)=>{rl.question(prompt2,(answer)=>{rl.close(),resolve5(/^y(es)?$/i.test(answer.trim()))})})}async function dbRestoreCommand(file,options){let snapshotPath=file??getSnapshotPath();if(!await isAvailable())console.error("Database is not running. Start it with: genie db status"),process.exit(1);if(!options.yes){if(!await confirm("This will replace all data in the genie database. Continue? [y/N] ")){console.log("Restore cancelled.");return}}try{await shutdown(),restore(file),console.log(`\u2713 Database restored from: ${snapshotPath}`)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Restore failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_agent_cache();init_agent_directory();init_agent_sync();init_audit();init_builtin_agents();init_genie_config2();import{resolve as resolvePath}from"path";init_audit();init_genie_config2();async function resolveOmniApiUrl(){let envUrl=process.env.OMNI_API_URL;if(envUrl)return envUrl;return(await loadGenieConfig()).omni?.apiUrl??null}async function resolveOmniApiKey(){let envKey=process.env.OMNI_API_KEY;if(envKey)return envKey;return(await loadGenieConfig()).omni?.apiKey}async function registerAgentInOmni(agentName,options){let apiUrl=await resolveOmniApiUrl();if(!apiUrl)return null;let apiKey=await resolveOmniApiKey(),body={name:agentName,provider:"claude",model:options?.model,agentType:"assistant",capabilities:options?.roles??[],metadata:{source:"genie",sessionIsolation:{perPerson:!0,perChannel:!0},registeredAt:new Date().toISOString()}},traceId=generateTraceId();try{let headers={"Content-Type":"application/json","X-Trace-Id":traceId};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;let response=await fetch(`${apiUrl}/api/v2/agents`,{method:"POST",headers,body:JSON.stringify(body),signal:AbortSignal.timeout(1e4)});if(!response.ok){let text=await response.text().catch(()=>"");return console.warn(`Warning: Omni registration failed (HTTP ${response.status}): ${text}`),recordAuditEvent("omni",agentName,"registration_error",getActor(),{traceId,status:response.status,error:text.slice(0,200)}).catch(()=>{}),null}let result=await response.json();return recordAuditEvent("omni",agentName,"registration_success",getActor(),{traceId,omniAgentId:result.data.id}).catch(()=>{}),result.data.id}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);return console.warn(`Warning: Omni registration failed: ${message}`),recordAuditEvent("omni",agentName,"registration_error",getActor(),{traceId,error:message.slice(0,200)}).catch(()=>{}),null}}async function findOmniAgent(agentName){let apiUrl=await resolveOmniApiUrl();if(!apiUrl)return null;let apiKey=await resolveOmniApiKey(),traceId=generateTraceId();try{let headers={"X-Trace-Id":traceId};if(apiKey)headers.Authorization=`Bearer ${apiKey}`;let response=await fetch(`${apiUrl}/api/v2/agents?name=${encodeURIComponent(agentName)}`,{method:"GET",headers,signal:AbortSignal.timeout(1e4)});if(!response.ok)return null;return(await response.json()).data?.find((a)=>a.name===agentName&&a.isActive)?.id??null}catch{return null}}function registerDirNamespace(program2){let dir=program2.command("dir").description("Agent directory management");dir.command("add <name>").description("Register an agent in the directory").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").action(async(name,options)=>{try{await handleDirAdd(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("rm <name>").description("Remove an agent from the directory").option("--global","Remove from global directory instead of project").action(async(name,options)=>{try{let removed=await rm3(name,{global:options.global});if(await removeItemFromStore(name).catch(()=>{}),await regenerateAgentCache(),recordAuditEvent("item",name,"item_removed",getActor(),{type:"agent",source:"dir_rm"}).catch(()=>{}),removed){let scope=options.global?"global":"project";console.log(`Agent "${name}" removed from ${scope} directory.`)}else console.error(`Agent "${name}" not found in directory.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("ls [name]").description("List all agents or show single entry details").option("--json","Output as JSON").option("--builtins","Include built-in roles and council members").option("--all","Include archived agents").action(async(name,options)=>{try{if(name)await showEntry(name,options.json);else await listEntries(options.json,options.builtins,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("edit <name>").description("Update an agent directory entry").option("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo").option("--prompt-mode <mode>","Prompt mode: append or system").option("--model <model>","Default model").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Edit in global directory instead of project").action(async(name,options)=>{try{await handleEdit(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("sync").description("Sync agents from workspace agents/ directory").action(async()=>{try{await handleDirSync()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleDirAdd(name,options){let promptMode=validatePromptMode(options.promptMode),resolvedDir=resolvePath(options.dir),entry=await add({name,dir:resolvedDir,repo:options.repo?resolvePath(options.repo):void 0,promptMode,model:options.model,roles:normalizeRoles(options.roles)},{global:options.global});try{await registerItemInStore({name,itemType:"agent",installPath:resolvedDir,manifest:{promptMode,model:options.model,roles:normalizeRoles(options.roles),repo:options.repo}})}catch{}await regenerateAgentCache(),recordAuditEvent("item",name,"item_registered",getActor(),{type:"agent",source:"dir_add"}).catch(()=>{});let scope=options.global?"global":"project";console.log(`Agent "${entry.name}" registered (${scope}).`),printEntry(entry)}async function handleEdit(name,options){let updates={};if(options.dir)updates.dir=resolvePath(options.dir);if(options.repo)updates.repo=resolvePath(options.repo);if(options.promptMode)updates.promptMode=validatePromptMode(options.promptMode);if(options.model)updates.model=options.model;if(options.roles)updates.roles=normalizeRoles(options.roles);if(Object.keys(updates).length===0)console.error("No fields to update. Provide at least one of: --dir, --repo, --prompt-mode, --model, --roles"),process.exit(1);let entry=await edit(name,updates,{global:options.global});try{await updateItemInStore(name,{installPath:updates.dir,manifest:{promptMode:updates.promptMode,model:updates.model,roles:updates.roles,repo:updates.repo}})}catch{}await regenerateAgentCache(),recordAuditEvent("item",name,"item_updated",getActor(),{type:"agent",source:"dir_edit"}).catch(()=>{});let scope=options.global?"global":"project";console.log(`Agent "${name}" updated (${scope}).`),printEntry(entry)}async function handleDirSync(){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` first."),process.exit(1);console.log(`Syncing agents from ${ws.root}/agents/...`);let result=await syncAgentDirectory(ws.root);printSyncResult(result)}function validatePromptMode(mode){if(mode!=="system"&&mode!=="append")throw Error(`Invalid prompt mode "${mode}". Must be "append" or "system".`);return mode}function printEntry(entry){if(console.log(` Name: ${entry.name}`),console.log(` Dir: ${contractPath(entry.dir)}`),entry.repo)console.log(` Repo: ${contractPath(entry.repo)}`);if(console.log(` Prompt mode: ${entry.promptMode}`),entry.model)console.log(` Model: ${entry.model}`);if(entry.roles?.length)console.log(` Roles: ${entry.roles.join(", ")}`);console.log(` Registered: ${entry.registeredAt}`)}async function showEntry(name,json2){let resolved=await resolve3(name);if(!resolved)console.error(`Agent "${name}" not found in directory or built-ins.`),process.exit(1);if(json2){console.log(JSON.stringify({...resolved.entry,builtin:resolved.builtin},null,2));return}if(resolved.builtin)console.log(`
2248
- (built-in ${resolved.entry.registeredAt==="(built-in)"?"agent":"agent"})`);console.log(""),printEntry(resolved.entry),console.log("")}async function listEntries(json2,includeBuiltins,includeArchived){await migrateAgentDirectory().catch(()=>{});let entries;try{entries=(await listItemsFromStore("agent")).filter((item)=>{if(includeArchived)return!0;return!(item.manifest??{}).archived}).map((item)=>{let manifest=item.manifest??{};return{name:item.name,dir:item.install_path??"",repo:manifest.repo??"",promptMode:manifest.promptMode??"append",model:manifest.model,roles:normalizeRoles(manifest.roles),registeredAt:item.installed_at,scope:manifest.archived?"archived":"global"}})}catch{entries=await ls()}if(json2){listEntriesJson(entries,includeBuiltins);return}if(entries.length===0&&!includeBuiltins){console.log(`
2322
+ Done. ${results.length} migration${results.length===1?"":"s"} applied.`),await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Error running migrations: ${message}`),process.exit(1)}}async function dbQueryCommand(query){try{let result=await(await getConnection()).unsafe(query);if(result.length===0&&result.count!==void 0)console.log(`Query OK, ${result.count} row${result.count===1?"":"s"} affected.`);else printTable(result);await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Query error: ${message}`),process.exit(1)}}function formatBytes(bytes){if(bytes===0)return"0B";let units=["B","KB","MB","GB"],i2=Math.min(Math.floor(Math.log(bytes)/Math.log(1024)),units.length-1),value=bytes/1024**i2;return`${value<10?value.toFixed(1):Math.round(value)}${units[i2]}`}async function dbBackupCommand(){if(!await isAvailable())console.error("Database is not running. Start it with: genie db status"),process.exit(1);try{let result=backup(),compressed=formatBytes(result.compressedBytes),uncompressed=result.uncompressedBytes>0?`, ${formatBytes(result.uncompressedBytes)} uncompressed`:"";console.log(`\u2713 Backup created: ${result.path} (${compressed}${uncompressed})`),await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Backup failed: ${message}`),process.exit(1)}}function confirm(prompt2){let rl=createInterface3({input:process.stdin,output:process.stdout});return new Promise((resolve5)=>{rl.question(prompt2,(answer)=>{rl.close(),resolve5(/^y(es)?$/i.test(answer.trim()))})})}async function dbRestoreCommand(file,options){let snapshotPath=file??getSnapshotPath();if(!await isAvailable())console.error("Database is not running. Start it with: genie db status"),process.exit(1);if(!options.yes){if(!await confirm("This will replace all data in the genie database. Continue? [y/N] ")){console.log("Restore cancelled.");return}}try{await shutdown(),restore(file),console.log(`\u2713 Database restored from: ${snapshotPath}`)}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Restore failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_agent_cache();init_agent_directory();init_agent_sync();init_audit();init_builtin_agents();init_genie_config2();import{resolve as resolvePath2}from"path";function registerDirNamespace(program2){let dir=program2.command("dir").description("Agent directory management");dir.command("add <name>").description("Register an agent in the directory").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").action(async(name,options)=>{try{await handleDirAdd(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("rm <name>").description("Remove an agent from the directory").option("--global","Remove from global directory instead of project").action(async(name,options)=>{try{let removed=await rm3(name,{global:options.global});if(await removeItemFromStore(name).catch(()=>{}),await regenerateAgentCache(),recordAuditEvent("item",name,"item_removed",getActor(),{type:"agent",source:"dir_rm"}).catch(()=>{}),removed){let scope=options.global?"global":"project";console.log(`Agent "${name}" removed from ${scope} directory.`)}else console.error(`Agent "${name}" not found in directory.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("ls [name]").description("List all agents or show single entry details").option("--json","Output as JSON").option("--builtins","Include built-in roles and council members").option("--all","Include archived agents").action(async(name,options)=>{try{if(name)await showEntry2(name,options.json);else await listEntries2(options.json,options.builtins,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("edit <name>").description("Update an agent directory entry").option("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo").option("--prompt-mode <mode>","Prompt mode: append or system").option("--model <model>","Default model").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Edit in global directory instead of project").action(async(name,options)=>{try{await handleEdit(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),dir.command("sync").description("Sync agents from workspace agents/ directory").action(async()=>{try{await handleDirSync()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleDirAdd(name,options){let promptMode=validatePromptMode2(options.promptMode),resolvedDir=resolvePath2(options.dir),entry=await add({name,dir:resolvedDir,repo:options.repo?resolvePath2(options.repo):void 0,promptMode,model:options.model,roles:normalizeRoles3(options.roles)},{global:options.global});try{await registerItemInStore({name,itemType:"agent",installPath:resolvedDir,manifest:{promptMode,model:options.model,roles:normalizeRoles3(options.roles),repo:options.repo}})}catch{}await regenerateAgentCache(),recordAuditEvent("item",name,"item_registered",getActor(),{type:"agent",source:"dir_add"}).catch(()=>{});let scope=options.global?"global":"project";console.log(`Agent "${entry.name}" registered (${scope}).`),printEntry2(entry)}async function handleEdit(name,options){let updates={};if(options.dir)updates.dir=resolvePath2(options.dir);if(options.repo)updates.repo=resolvePath2(options.repo);if(options.promptMode)updates.promptMode=validatePromptMode2(options.promptMode);if(options.model)updates.model=options.model;if(options.roles)updates.roles=normalizeRoles3(options.roles);if(Object.keys(updates).length===0)console.error("No fields to update. Provide at least one of: --dir, --repo, --prompt-mode, --model, --roles"),process.exit(1);let entry=await edit(name,updates,{global:options.global});try{await updateItemInStore(name,{installPath:updates.dir,manifest:{promptMode:updates.promptMode,model:updates.model,roles:updates.roles,repo:updates.repo}})}catch{}await regenerateAgentCache(),recordAuditEvent("item",name,"item_updated",getActor(),{type:"agent",source:"dir_edit"}).catch(()=>{});let scope=options.global?"global":"project";console.log(`Agent "${name}" updated (${scope}).`),printEntry2(entry)}async function handleDirSync(){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` first."),process.exit(1);console.log(`Syncing agents from ${ws.root}/agents/...`);let result=await syncAgentDirectory(ws.root);printSyncResult(result)}function validatePromptMode2(mode){if(mode!=="system"&&mode!=="append")throw Error(`Invalid prompt mode "${mode}". Must be "append" or "system".`);return mode}function printEntry2(entry){if(console.log(` Name: ${entry.name}`),console.log(` Dir: ${contractPath(entry.dir)}`),entry.repo)console.log(` Repo: ${contractPath(entry.repo)}`);if(console.log(` Prompt mode: ${entry.promptMode}`),entry.model)console.log(` Model: ${entry.model}`);if(entry.roles?.length)console.log(` Roles: ${entry.roles.join(", ")}`);console.log(` Registered: ${entry.registeredAt}`)}async function showEntry2(name,json2){let resolved=await resolve3(name);if(!resolved)console.error(`Agent "${name}" not found in directory or built-ins.`),process.exit(1);if(json2){console.log(JSON.stringify({...resolved.entry,builtin:resolved.builtin},null,2));return}if(resolved.builtin)console.log(`
2323
+ (built-in ${resolved.entry.registeredAt==="(built-in)"?"agent":"agent"})`);console.log(""),printEntry2(resolved.entry),console.log("")}async function listEntries2(json2,includeBuiltins,includeArchived){await migrateAgentDirectory().catch(()=>{});let entries;try{entries=(await listItemsFromStore("agent")).filter((item)=>{if(includeArchived)return!0;return!(item.manifest??{}).archived}).map((item)=>{let manifest=item.manifest??{};return{name:item.name,dir:item.install_path??"",repo:manifest.repo??"",promptMode:manifest.promptMode??"append",model:manifest.model,roles:normalizeRoles3(manifest.roles),registeredAt:item.installed_at,scope:manifest.archived?"archived":"global"}})}catch{entries=await ls()}if(json2){listEntriesJson(entries,includeBuiltins);return}if(entries.length===0&&!includeBuiltins){console.log(`
2249
2324
  No agents registered. Add one with: genie dir add <name> --dir <path>`),console.log(`Use --builtins to also see built-in roles and council members.
2250
- `);return}if(entries.length>0)printRegisteredTable(entries);if(includeBuiltins)printBuiltinsTable()}function listEntriesJson(entries,includeBuiltins){let result=entries.map((e)=>({...e,builtin:!1}));if(includeBuiltins)for(let b2 of ALL_BUILTINS)result.push({name:b2.name,description:b2.description,model:b2.model,category:b2.category,scope:"built-in",builtin:!0});console.log(JSON.stringify(result,null,2))}function normalizeRoles(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}function printRegisteredTable(entries){let repoValues=[],roleValues=[];for(let entry of entries)repoValues.push(entry.repo?contractPath(entry.repo):contractPath(entry.dir)),roleValues.push(entry.roles?.join(", ")||"-");let termW=process.stdout.columns||120,fixedW=42,maxRepoLen=Math.max(4,...repoValues.map((v)=>v.length)),repoW=Math.min(maxRepoLen+2,Math.max(30,termW-fixedW-20)),totalW=fixedW+repoW+20;console.log(""),console.log("REGISTERED AGENTS"),console.log("-".repeat(Math.max(90,totalW))),console.log(` ${"NAME".padEnd(22)}${"SCOPE".padEnd(10)}${"REPO".padEnd(repoW)}${"MODEL".padEnd(8)}ROLES`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(repoW-2)} ${"-".repeat(6)} ${"-".repeat(20)}`);for(let i2=0;i2<entries.length;i2++){let entry=entries[i2],repo=repoValues[i2],roles=roleValues[i2];console.log(` ${entry.name.padEnd(22)}${entry.scope.padEnd(10)}${repo.padEnd(repoW)}${(entry.model||"-").padEnd(8)}${roles}`)}console.log("")}function printBuiltinsTable(){console.log("BUILT-IN AGENTS"),console.log("-".repeat(80)),console.log(` ${"NAME".padEnd(22)}${"TYPE".padEnd(10)}${"MODEL".padEnd(8)}DESCRIPTION`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(30)}`);for(let agent of ALL_BUILTINS)console.log(` ${agent.name.padEnd(22)}${agent.category.padEnd(10)}${(agent.model||"-").padEnd(8)}${agent.description}`);console.log("")}async function handleOmniRegistration(name,options){let omniUrl=await resolveOmniApiUrl();if(!omniUrl)return;console.log(`
2251
- Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);if(existingId){console.log(` Agent already exists in Omni: ${existingId}`),await edit(name,{omniAgentId:existingId},{global:options.global}),console.log(" Linked existing Omni agent to directory entry.");return}let omniAgentId=await registerAgentInOmni(name,{model:options.model,roles:options.roles});if(omniAgentId)await edit(name,{omniAgentId},{global:options.global}),console.log(` Omni agent created: ${omniAgentId}`),console.log(" Session isolation: per-person + per-channel")}async function handleAgentRegister(name,options){let promptMode=validatePromptMode(options.promptMode),roles=normalizeRoles(options.roles),entry=await add({name,dir:resolvePath(options.dir),repo:options.repo?resolvePath(options.repo):void 0,promptMode,model:options.model,roles},{global:options.global}),scope=options.global?"global":"project";if(console.log(`Agent "${entry.name}" registered (${scope}).`),printEntry(entry),!options.skipOmni)await handleOmniRegistration(name,{...options,roles})}function registerAgentNamespace(program2){program2.command("agent").description("Agent lifecycle management").command("register <name>").description("Register an agent locally and auto-register in Omni when configured").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").option("--skip-omni","Skip Omni auto-registration").action(async(name,options)=>{try{await handleAgentRegister(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_protocol_router();import{execSync as execSync11}from"child_process";import{existsSync as existsSync34}from"fs";import{mkdir as mkdir6,readFile as readFile7,writeFile as writeFile3}from"fs/promises";import{tmpdir}from"os";import{join as join41}from"path";init_tmux();import{existsSync as existsSync29}from"fs";import{join as join36}from"path";var REPOS_BASE="/home/genie/workspace/repos";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=join36(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync29(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=join36(REPOS_BASE,parsed.namespace);if(!existsSync29(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join36(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync29(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}}init_wish_state();init_agents();import{execSync as execSync9}from"child_process";import{existsSync as existsSync30}from"fs";import{readFile as readFile6}from"fs/promises";import{dirname as dirname7,join as join37}from"path";init_wish_state();async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path2){if(process.platform!=="darwin")return path2;if(!path2.startsWith("/private/"))return path2;let logicalPath=path2.slice(8);return existsSync30(logicalPath)?logicalPath:path2}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join37(base,".genie","wishes",slug,"WISH.md");if(existsSync30(cwdPath))return cwdPath;try{let commonDir=execSync9("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname7(commonDir));if(repoRoot!==base){let repoPath=join37(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync30(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile6(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync9("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync9("git add -A",{encoding:"utf-8"}),execSync9(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync9("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync9("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync9("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync9(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync9(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let config=await(await Promise.resolve().then(() => (init_team_manager(),exports_team_manager))).getTeam(teamName);return{leader:config?.leader||"team-lead",spawner:config?.spawner}}catch{return{leader:"team-lead"}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result&&typeof result==="object"&&"delivered"in result&&!result.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result.completedAt)console.log(` Completed at: ${formatTimestamp(result.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile6(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
2325
+ `);return}if(entries.length>0)printRegisteredTable(entries);if(includeBuiltins)printBuiltinsTable()}function listEntriesJson(entries,includeBuiltins){let result=entries.map((e)=>({...e,builtin:!1}));if(includeBuiltins)for(let b2 of ALL_BUILTINS)result.push({name:b2.name,description:b2.description,model:b2.model,category:b2.category,scope:"built-in",builtin:!0});console.log(JSON.stringify(result,null,2))}function normalizeRoles3(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}function printRegisteredTable(entries){let repoValues=[],roleValues=[];for(let entry of entries)repoValues.push(entry.repo?contractPath(entry.repo):contractPath(entry.dir)),roleValues.push(entry.roles?.join(", ")||"-");let termW=process.stdout.columns||120,fixedW=42,maxRepoLen=Math.max(4,...repoValues.map((v)=>v.length)),repoW=Math.min(maxRepoLen+2,Math.max(30,termW-fixedW-20)),totalW=fixedW+repoW+20;console.log(""),console.log("REGISTERED AGENTS"),console.log("-".repeat(Math.max(90,totalW))),console.log(` ${"NAME".padEnd(22)}${"SCOPE".padEnd(10)}${"REPO".padEnd(repoW)}${"MODEL".padEnd(8)}ROLES`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(repoW-2)} ${"-".repeat(6)} ${"-".repeat(20)}`);for(let i2=0;i2<entries.length;i2++){let entry=entries[i2],repo=repoValues[i2],roles=roleValues[i2];console.log(` ${entry.name.padEnd(22)}${entry.scope.padEnd(10)}${repo.padEnd(repoW)}${(entry.model||"-").padEnd(8)}${roles}`)}console.log("")}function printBuiltinsTable(){console.log("BUILT-IN AGENTS"),console.log("-".repeat(80)),console.log(` ${"NAME".padEnd(22)}${"TYPE".padEnd(10)}${"MODEL".padEnd(8)}DESCRIPTION`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(30)}`);for(let agent of ALL_BUILTINS)console.log(` ${agent.name.padEnd(22)}${agent.category.padEnd(10)}${(agent.model||"-").padEnd(8)}${agent.description}`);console.log("")}init_protocol_router();import{execSync as execSync11}from"child_process";import{existsSync as existsSync34}from"fs";import{mkdir as mkdir6,readFile as readFile9,writeFile as writeFile3}from"fs/promises";import{tmpdir}from"os";import{join as join43}from"path";init_tmux();import{existsSync as existsSync29}from"fs";import{join as join38}from"path";var REPOS_BASE="/home/genie/workspace/repos";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=join38(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync29(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=join38(REPOS_BASE,parsed.namespace);if(!existsSync29(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join38(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync29(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}}init_wish_state();init_agents();init_wish_state();import{execSync as execSync9}from"child_process";import{existsSync as existsSync30}from"fs";import{readFile as readFile8}from"fs/promises";import{dirname as dirname7,join as join39}from"path";async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path2){if(process.platform!=="darwin")return path2;if(!path2.startsWith("/private/"))return path2;let logicalPath=path2.slice(8);return existsSync30(logicalPath)?logicalPath:path2}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join39(base,".genie","wishes",slug,"WISH.md");if(existsSync30(cwdPath))return cwdPath;try{let commonDir=execSync9("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname7(commonDir));if(repoRoot!==base){let repoPath=join39(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync30(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile8(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync9("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync9("git add -A",{encoding:"utf-8"}),execSync9(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync9("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync9("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync9("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync9(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync9(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let config=await(await Promise.resolve().then(() => (init_team_manager(),exports_team_manager))).getTeam(teamName);return{leader:config?.leader||"team-lead",spawner:config?.spawner}}catch{return{leader:"team-lead"}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result&&typeof result==="object"&&"delivered"in result&&!result.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result.completedAt)console.log(` Completed at: ${formatTimestamp(result.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
2252
2326
  Active Executors:`),console.log("\u2500".repeat(60));for(let line of executorInfoLines)console.log(line)}}catch{}}async function statusCommand(slug){try{let state=await getState(slug)??await autoInitWishState(slug);console.log(`
2253
- Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").description("Reset an in-progress group back to ready (format: <slug>#<group>)").action(async(ref)=>{try{let{slug,group}=parseRef(ref),result=await resetGroup(slug,group);if(console.log(`\uD83D\uDD04 Group "${group}" reset to ready in wish "${slug}"`),result.status==="ready")console.log(" Status: ready (assignee cleared)")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}})}async function writeContextFile(content){let dir=join41(tmpdir(),"genie-dispatch");await mkdir6(dir,{recursive:!0});let ts3=Date.now().toString(36),rand=Math.random().toString(36).slice(2,8),filePath=join41(dir,`ctx-${ts3}-${rand}.md`);return await writeFile3(filePath,content),filePath}function extractGroup(content,groupName){let pattern=new RegExp(`^### Group ${escapeRegExp(groupName)}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start2=match.index,nextBoundary=content.slice(start2).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start2+1+nextBoundary:content.length;return content.slice(start2,end).trim()}function extractWishContext(content){let execGroupsIdx=content.indexOf("## Execution Groups");if(execGroupsIdx!==-1)return content.slice(0,execGroupsIdx).trim();return content.slice(0,2000).trim()}function escapeRegExp(str5){return str5.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildContextPrompt(opts){let parts=[`# Dispatch Context (${opts.command})`,"",`**Source file:** \`${opts.filePath}\``,"(Read the full document at the path above for complete context)",""];if(opts.wishContext)parts.push("## Wish Context","",opts.wishContext,"");if(parts.push("## Assigned Section","",opts.sectionContent,""),opts.enrichedContext)parts.push(opts.enrichedContext);if(opts.skill)parts.push("## Initial Command","",`Run \`/${opts.skill}\` to begin.`,"");return parts.join(`
2327
+ Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").description("Reset an in-progress group back to ready (format: <slug>#<group>)").action(async(ref)=>{try{let{slug,group}=parseRef(ref),result=await resetGroup(slug,group);if(console.log(`\uD83D\uDD04 Group "${group}" reset to ready in wish "${slug}"`),result.status==="ready")console.log(" Status: ready (assignee cleared)")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}})}async function writeContextFile(content){let dir=join43(tmpdir(),"genie-dispatch");await mkdir6(dir,{recursive:!0});let ts3=Date.now().toString(36),rand=Math.random().toString(36).slice(2,8),filePath=join43(dir,`ctx-${ts3}-${rand}.md`);return await writeFile3(filePath,content),filePath}function extractGroup(content,groupName){let pattern=new RegExp(`^### Group ${escapeRegExp(groupName)}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start2=match.index,nextBoundary=content.slice(start2).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start2+1+nextBoundary:content.length;return content.slice(start2,end).trim()}function extractWishContext(content){let execGroupsIdx=content.indexOf("## Execution Groups");if(execGroupsIdx!==-1)return content.slice(0,execGroupsIdx).trim();return content.slice(0,2000).trim()}function escapeRegExp(str5){return str5.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildContextPrompt(opts){let parts=[`# Dispatch Context (${opts.command})`,"",`**Source file:** \`${opts.filePath}\``,"(Read the full document at the path above for complete context)",""];if(opts.wishContext)parts.push("## Wish Context","",opts.wishContext,"");if(parts.push("## Assigned Section","",opts.sectionContent,""),opts.enrichedContext)parts.push(opts.enrichedContext);if(opts.skill)parts.push("## Initial Command","",`Run \`/${opts.skill}\` to begin.`,"");return parts.join(`
2254
2328
  `)}function getGitDiff(){try{let diff=execSync11("git diff HEAD",{encoding:"utf-8",maxBuffer:1048576}),staged=execSync11("git diff --cached",{encoding:"utf-8",maxBuffer:1048576}),combined=[diff,staged].filter(Boolean).join(`
2255
2329
  `);if(combined.length>50000)return`${combined.slice(0,50000)}
2256
2330
 
2257
- ... (diff truncated at 50KB)`;return combined}catch{return""}}function parseWishGroups(content){let groups=[],groupPattern=/^### Group ([A-Za-z0-9]+):/gim,match=groupPattern.exec(content);while(match!==null){let name=match[1],start2=match.index,rest=content.slice(start2+match[0].length),nextGroupIdx=rest.search(/^### Group [A-Za-z0-9]+:/m),depsMatch=(nextGroupIdx!==-1?rest.slice(0,nextGroupIdx):rest).match(/\*\*depends-on:\*\*\s*(.+)/i),dependsOn=[];if(depsMatch){let depsNormalized=depsMatch[1].trim().replace(/\s*\([^)]*\)/g,"").trim();if(depsNormalized.toLowerCase()!=="none")dependsOn=depsNormalized.split(",").map((d)=>d.trim().replace(/^groups?\s*/i,"").trim()).filter(Boolean)}groups.push({name,dependsOn}),match=groupPattern.exec(content)}return groups}function parseExecutionStrategy(content){let strategyMatch=content.match(/^## Execution Strategy\s*$/m);if(!strategyMatch||strategyMatch.index===void 0)return buildFallbackWaves(content);let strategyStart=strategyMatch.index+strategyMatch[0].length,nextSectionMatch=content.slice(strategyStart).match(/^## /m),strategyEnd=nextSectionMatch?.index!==void 0?strategyStart+nextSectionMatch.index:content.length,strategyContent=content.slice(strategyStart,strategyEnd),waves=[],wavePattern=/^### (Wave \d+[^\n]*)/gm,waveMatch=wavePattern.exec(strategyContent);while(waveMatch!==null){let waveName=waveMatch[1].trim(),waveStart=waveMatch.index+waveMatch[0].length,nextWaveIdx=strategyContent.slice(waveStart).search(/^### /m),waveEnd=nextWaveIdx!==-1?waveStart+nextWaveIdx:strategyContent.length,waveContent=strategyContent.slice(waveStart,waveEnd),waveGroups=[],tableRowPattern=/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*[^|]*\s*\|$/gm,rowMatch=tableRowPattern.exec(waveContent);while(rowMatch!==null){let groupVal=rowMatch[1].trim(),agentVal=rowMatch[2].trim();if(groupVal!=="Group"&&!groupVal.startsWith("-"))waveGroups.push({group:groupVal,agent:agentVal});rowMatch=tableRowPattern.exec(waveContent)}if(waveGroups.length>0)waves.push({name:waveName,groups:waveGroups});waveMatch=wavePattern.exec(strategyContent)}if(waves.length===0)return buildFallbackWaves(content);return waves}function buildFallbackWaves(content){let groups=parseWishGroups(content);if(groups.length===0)return[];return[{name:"Wave 1 (sequential fallback)",groups:groups.map((g)=>({group:g.name,agent:"engineer"}))}]}async function resolveLeaderTarget(){let teamName=process.env.GENIE_TEAM;if(!teamName)return"team-lead";try{return(await(await Promise.resolve().then(() => (init_team_manager(),exports_team_manager))).getTeam(teamName))?.leader||"team-lead"}catch{return"team-lead"}}function detectWorkMode(ref,agent){if(!agent){if(ref.includes("#"))throw Error("Manual dispatch requires an agent: genie work <slug>#<group> <agent>");return{mode:"auto",slug:ref}}if(ref.includes("#"))return{mode:"manual",ref,agent};if(agent.includes("#"))return{mode:"manual",ref:agent,agent:ref};throw Error('Invalid: ref must contain "#" \u2014 use "genie work <slug>" or "genie work <agent> <slug>#<group>"')}async function autoOrchestrateCommand(slug){let wishPath,actualSlug=slug;if(parseWishRef(slug).namespace){let resolved=await resolveWish(slug);wishPath=resolved.wishPath,actualSlug=resolved.slug;let{handleTeamCreate:handleTeamCreate2}=await Promise.resolve().then(() => (init_team(),exports_team));await handleTeamCreate2(actualSlug,{repo:resolved.repo,branch:"dev",wish:actualSlug,tmuxSession:resolved.session});return}if(wishPath=join41(process.cwd(),".genie","wishes",slug,"WISH.md"),!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);Promise.resolve().then(() => (init_wish_sync(),exports_wish_sync)).then((ws)=>ws.syncWishes(process.cwd())).catch(()=>{});let content=await readFile7(wishPath,"utf-8"),groups=parseWishGroups(content),waves=parseExecutionStrategy(content);if(waves.length===0)console.error("\u274C No execution groups found in wish"),process.exit(1);let state=await getOrCreateState(slug,groups),nextWave=waves.find((wave)=>wave.groups.some((g)=>{let gs=state?.groups[g.group];return!gs||gs.status==="ready"}));if(!nextWave){console.log(`\u2705 All waves already dispatched for wish "${slug}"`);return}console.log(`\uD83D\uDE80 Dispatching ${nextWave.name} for wish "${slug}" \u2014 ${nextWave.groups.length} group(s)`),await Promise.all(nextWave.groups.map(({group,agent})=>{let ref=`${slug}#${group}`;return workDispatchCommand(agent,ref)}));let groupList=nextWave.groups.map((g)=>g.group).join(", ");console.log(`
2258
- \u2705 Agents dispatched for ${nextWave.name} (groups: ${groupList})`),console.log(` Monitor: genie status ${slug}`),console.log(" Logs: genie read <agent>")}async function brainstormCommand(agentName,slug){let draftPath=join41(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync34(draftPath))console.error(`\u274C Draft not found: ${draftPath}`),console.error(` Create it first: mkdir -p .genie/brainstorms/${slug} && touch .genie/brainstorms/${slug}/DRAFT.md`),process.exit(1);let content=await readFile7(draftPath,"utf-8"),context=buildContextPrompt({filePath:draftPath,sectionContent:content,command:"brainstorm",skill:"brainstorm"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching brainstorm to ${agentName} for "${slug}"`),console.log(` Draft: ${draftPath}`);let brainstormPrompt=`Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:brainstormPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",agentName,brainstormPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function wishCommand(agentName,slug){let designPath=join41(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync34(designPath))console.error(`\u274C Design not found: ${designPath}`),console.error(` Run brainstorm first: genie brainstorm <agent> ${slug}`),process.exit(1);let content=await readFile7(designPath,"utf-8"),context=buildContextPrompt({filePath:designPath,sectionContent:content,command:"wish",skill:"wish"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching wish to ${agentName} for "${slug}"`),console.log(` Design: ${designPath}`);let wishPrompt=`Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:wishPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",agentName,wishPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function workDispatchCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join41(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile7(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection){console.error(`\u274C Group "${group}" not found in ${wishPath}`),console.error(" Available groups:");let groups2=content.match(/^### Group [A-Za-z0-9]+:.*$/gm);if(groups2)for(let g of groups2)console.error(` ${g}`);process.exit(1)}let groups=parseWishGroups(content);await getOrCreateState(slug,groups);try{await startGroup(slug,group,agentName),console.log(`\u2705 Group "${group}" set to in_progress (assigned to ${agentName})`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}let wishContext=extractWishContext(content),enrichedContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedContext=enrichContext2({query:`${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:groupSection,wishContext,command:`work ${ref}`,skill:"work",enrichedContext}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDD27 Dispatching work to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`);let effectiveRole=`${agentName}-${group}`,leaderTarget=await resolveLeaderTarget(),workPrompt=`Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.
2331
+ ... (diff truncated at 50KB)`;return combined}catch{return""}}function parseWishGroups(content){let groups=[],groupPattern=/^### Group ([A-Za-z0-9]+):/gim,match=groupPattern.exec(content);while(match!==null){let name=match[1],start2=match.index,rest=content.slice(start2+match[0].length),nextGroupIdx=rest.search(/^### Group [A-Za-z0-9]+:/m),depsMatch=(nextGroupIdx!==-1?rest.slice(0,nextGroupIdx):rest).match(/\*\*depends-on:\*\*\s*(.+)/i),dependsOn=[];if(depsMatch){let depsNormalized=depsMatch[1].trim().replace(/\s*\([^)]*\)/g,"").trim();if(depsNormalized.toLowerCase()!=="none")dependsOn=depsNormalized.split(",").map((d)=>d.trim().replace(/^groups?\s*/i,"").trim()).filter(Boolean)}groups.push({name,dependsOn}),match=groupPattern.exec(content)}return groups}function parseExecutionStrategy(content){let strategyMatch=content.match(/^## Execution Strategy\s*$/m);if(!strategyMatch||strategyMatch.index===void 0)return buildFallbackWaves(content);let strategyStart=strategyMatch.index+strategyMatch[0].length,nextSectionMatch=content.slice(strategyStart).match(/^## /m),strategyEnd=nextSectionMatch?.index!==void 0?strategyStart+nextSectionMatch.index:content.length,strategyContent=content.slice(strategyStart,strategyEnd),waves=[],wavePattern=/^### (Wave \d+[^\n]*)/gm,waveMatch=wavePattern.exec(strategyContent);while(waveMatch!==null){let waveName=waveMatch[1].trim(),waveStart=waveMatch.index+waveMatch[0].length,nextWaveIdx=strategyContent.slice(waveStart).search(/^### /m),waveEnd=nextWaveIdx!==-1?waveStart+nextWaveIdx:strategyContent.length,waveContent=strategyContent.slice(waveStart,waveEnd),waveGroups=[],tableRowPattern=/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*[^|]*\s*\|$/gm,rowMatch=tableRowPattern.exec(waveContent);while(rowMatch!==null){let groupVal=rowMatch[1].trim(),agentVal=rowMatch[2].trim();if(groupVal!=="Group"&&!groupVal.startsWith("-"))waveGroups.push({group:groupVal,agent:agentVal});rowMatch=tableRowPattern.exec(waveContent)}if(waveGroups.length>0)waves.push({name:waveName,groups:waveGroups});waveMatch=wavePattern.exec(strategyContent)}if(waves.length===0)return buildFallbackWaves(content);return waves}function buildFallbackWaves(content){let groups=parseWishGroups(content);if(groups.length===0)return[];return[{name:"Wave 1 (sequential fallback)",groups:groups.map((g)=>({group:g.name,agent:"engineer"}))}]}async function resolveLeaderTarget(){let teamName=process.env.GENIE_TEAM;if(!teamName)return"team-lead";try{return(await(await Promise.resolve().then(() => (init_team_manager(),exports_team_manager))).getTeam(teamName))?.leader||"team-lead"}catch{return"team-lead"}}function detectWorkMode(ref,agent){if(!agent){if(ref.includes("#"))throw Error("Manual dispatch requires an agent: genie work <slug>#<group> <agent>");return{mode:"auto",slug:ref}}if(ref.includes("#"))return{mode:"manual",ref,agent};if(agent.includes("#"))return{mode:"manual",ref:agent,agent:ref};throw Error('Invalid: ref must contain "#" \u2014 use "genie work <slug>" or "genie work <agent> <slug>#<group>"')}async function autoOrchestrateCommand(slug){let wishPath,actualSlug=slug;if(parseWishRef(slug).namespace){let resolved=await resolveWish(slug);wishPath=resolved.wishPath,actualSlug=resolved.slug;let{handleTeamCreate:handleTeamCreate2}=await Promise.resolve().then(() => (init_team(),exports_team));await handleTeamCreate2(actualSlug,{repo:resolved.repo,branch:"dev",wish:actualSlug,tmuxSession:resolved.session});return}if(wishPath=join43(process.cwd(),".genie","wishes",slug,"WISH.md"),!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);Promise.resolve().then(() => (init_wish_sync(),exports_wish_sync)).then((ws)=>ws.syncWishes(process.cwd())).catch(()=>{});let content=await readFile9(wishPath,"utf-8"),groups=parseWishGroups(content),waves=parseExecutionStrategy(content);if(waves.length===0)console.error("\u274C No execution groups found in wish"),process.exit(1);let state=await getOrCreateState(slug,groups),nextWave=waves.find((wave)=>wave.groups.some((g)=>{let gs=state?.groups[g.group];return!gs||gs.status==="ready"}));if(!nextWave){console.log(`\u2705 All waves already dispatched for wish "${slug}"`);return}console.log(`\uD83D\uDE80 Dispatching ${nextWave.name} for wish "${slug}" \u2014 ${nextWave.groups.length} group(s)`),await Promise.all(nextWave.groups.map(({group,agent})=>{let ref=`${slug}#${group}`;return workDispatchCommand(agent,ref)}));let groupList=nextWave.groups.map((g)=>g.group).join(", ");console.log(`
2332
+ \u2705 Agents dispatched for ${nextWave.name} (groups: ${groupList})`),console.log(` Monitor: genie status ${slug}`),console.log(" Logs: genie read <agent>")}async function brainstormCommand(agentName,slug){let draftPath=join43(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync34(draftPath))console.error(`\u274C Draft not found: ${draftPath}`),console.error(` Create it first: mkdir -p .genie/brainstorms/${slug} && touch .genie/brainstorms/${slug}/DRAFT.md`),process.exit(1);let content=await readFile9(draftPath,"utf-8"),context=buildContextPrompt({filePath:draftPath,sectionContent:content,command:"brainstorm",skill:"brainstorm"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching brainstorm to ${agentName} for "${slug}"`),console.log(` Draft: ${draftPath}`);let brainstormPrompt=`Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:brainstormPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",agentName,brainstormPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function wishCommand(agentName,slug){let designPath=join43(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync34(designPath))console.error(`\u274C Design not found: ${designPath}`),console.error(` Run brainstorm first: genie brainstorm <agent> ${slug}`),process.exit(1);let content=await readFile9(designPath,"utf-8"),context=buildContextPrompt({filePath:designPath,sectionContent:content,command:"wish",skill:"wish"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching wish to ${agentName} for "${slug}"`),console.log(` Design: ${designPath}`);let wishPrompt=`Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:wishPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",agentName,wishPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function workDispatchCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join43(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection){console.error(`\u274C Group "${group}" not found in ${wishPath}`),console.error(" Available groups:");let groups2=content.match(/^### Group [A-Za-z0-9]+:.*$/gm);if(groups2)for(let g of groups2)console.error(` ${g}`);process.exit(1)}let groups=parseWishGroups(content);await getOrCreateState(slug,groups);try{await startGroup(slug,group,agentName),console.log(`\u2705 Group "${group}" set to in_progress (assigned to ${agentName})`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}let wishContext=extractWishContext(content),enrichedContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedContext=enrichContext2({query:`${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:groupSection,wishContext,command:`work ${ref}`,skill:"work",enrichedContext}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDD27 Dispatching work to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`);let effectiveRole=`${agentName}-${group}`,leaderTarget=await resolveLeaderTarget(),workPrompt=`Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.
2259
2333
 
2260
2334
  When done:
2261
2335
  1. Run: genie done ${slug}#${group}
2262
- 2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",effectiveRole,workPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join41(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile7(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
2336
+ 2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",effectiveRole,workPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join43(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync34(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
2263
2337
  ${diff}
2264
2338
  \`\`\``:"(no uncommitted changes found \u2014 review committed changes)"].join(`
2265
2339
  `),enrichedReviewContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedReviewContext=enrichContext2({query:`review ${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:reviewContent,wishContext,command:`review ${ref}`,skill:"review",enrichedContext:enrichedReviewContext}),contextFile=await writeContextFile(context);if(console.log(`\uD83D\uDD0D Dispatching review to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`),diff)console.log(` Diff: ${diff.split(`
@@ -2267,16 +2341,9 @@ ${diff}
2267
2341
 
2268
2342
  When done, report your verdict:
2269
2343
  Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:reviewPrompt});let repoPath=process.cwd(),result=await sendMessage2(repoPath,"cli",agentName,reviewPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_export_format();import{existsSync as existsSync35,mkdirSync as mkdirSync13,writeFileSync as writeFileSync13}from"fs";import{dirname as dirname8}from"path";async function getSql(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getVersion(){let{VERSION:VERSION2}=await Promise.resolve().then(() => (init_version(),exports_version));return VERSION2}async function getActorName(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}function outputDocument(doc,options){let json2=options.pretty?JSON.stringify(doc,null,2):JSON.stringify(doc);if(options.output){let dir=dirname8(options.output);if(!existsSync35(dir))mkdirSync13(dir,{recursive:!0});writeFileSync13(options.output,`${json2}
2270
- `);let tables=Object.keys(doc.data),rows=Object.values(doc.data).reduce((sum,arr)=>sum+arr.length,0);if(console.log(`Exported ${tables.length} tables (${rows} rows) to ${options.output}`),doc.skippedTables.length>0)console.log(`Skipped tables (not found): ${doc.skippedTables.join(", ")}`)}else console.log(json2)}function autoOutputName(){let d=new Date;return`genie-backup-${`${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}`}.json`}async function exportGroup(sql,group,filter){let tables=GROUP_TABLES[group],{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(filter)data[table]=[...await sql.unsafe(`SELECT * FROM ${table} WHERE ${filter.column} = $1`,[filter.value])];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}async function exportBoards(sql,name){let tables=GROUP_TABLES.boards,{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(name&&table==="boards")data[table]=[...await sql`SELECT * FROM boards WHERE name = ${name}`];else if(table==="task_types")data[table]=[...await sql`SELECT * FROM task_types WHERE is_builtin = false`];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}var TASK_JOIN_ALIASES={task_tags:"tt",task_actors:"ta",task_dependencies:"td",task_stage_log:"tsl"};async function resolveProjectId2(sql,projectName){let projects=await sql`SELECT id FROM projects WHERE name = ${projectName}`;if(projects.length===0)throw Error(`Project not found: ${projectName}`);return projects[0].id}function stripEphemeralFields(rows){return rows.map((r)=>{let{checkout_run_id,execution_locked_at,session_id,pane_id,...rest}=r;return rest})}async function exportTaskTable(sql,table,projectId){let alias=TASK_JOIN_ALIASES[table];if(table==="tasks"){let rows=projectId?[...await sql.unsafe("SELECT * FROM tasks WHERE project_id = $1",[projectId])]:[...await sql`SELECT * FROM tasks`];return stripEphemeralFields(rows)}if(alias&&projectId)return[...await sql.unsafe(`SELECT ${alias}.* FROM ${table} ${alias} JOIN tasks t ON ${alias}.task_id = t.id WHERE t.project_id = $1`,[projectId])];return[...await sql.unsafe(`SELECT * FROM ${table}`)]}async function exportTasks(sql,projectName){let tables=GROUP_TABLES.tasks,{available,skipped}=await detectTables(sql,tables),data={},projectId=projectName?await resolveProjectId2(sql,projectName):null;for(let table of available)data[table]=await exportTaskTable(sql,table,projectId);return{data,skipped}}async function exportSchedules(sql,name){let{available,skipped}=await detectTables(sql,["schedules"]),data={};if(available.includes("schedules"))if(name)data.schedules=[...await sql`SELECT * FROM schedules WHERE name = ${name}`];else data.schedules=[...await sql`SELECT * FROM schedules`];return{data,skipped}}async function exportTags(sql){let{available,skipped}=await detectTables(sql,["tags"]),data={};if(available.includes("tags"))data.tags=[...await sql`SELECT * FROM tags WHERE name NOT LIKE 'test-%'`];return{data,skipped}}async function exportAll(sql){let allSkipped=[],allData={};for(let group of ALL_GROUPS){let result;switch(group){case"boards":result=await exportBoards(sql);break;case"tasks":result=await exportTasks(sql);break;case"tags":result=await exportTags(sql);break;case"schedules":result=await exportSchedules(sql);break;default:result=await exportGroup(sql,group);break}Object.assign(allData,result.data),allSkipped.push(...result.skipped)}return{data:allData,skipped:allSkipped}}async function runExport(groups,type2,exportFn,options){let sql=await getSql(),[version,actor]=await Promise.all([getVersion(),getActorName()]),doc=createExportDocument(type2,groups,version,actor),{data,skipped}=await exportFn(sql);doc.data=data,doc.skippedTables=skipped,outputDocument(doc,options)}function registerExportCommands(program2){let exp=program2.command("export").description("Export genie data as JSON").option("--output <file>","Write to file instead of stdout").option("-o <file>","Alias for --output").option("--pretty","Pretty-print JSON").action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts=(cmd)=>cmd.option("--output <file>","Write to file instead of stdout").option("--pretty","Pretty-print JSON");sharedOpts(exp.command("all").description("Full backup (all present tables)")).action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("boards [name]").description("Export boards, templates, and task types")).action(async(name,options)=>{try{await runExport(["boards"],"partial",(sql)=>exportBoards(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tasks").description("Export tasks with deps, actors, and stage log").option("--project <name>","Filter by project name")).action(async(options)=>{try{await runExport(["tasks"],"partial",(sql)=>exportTasks(sql,options.project),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tags").description("Export tags")).action(async(options)=>{try{await runExport(["tags"],"partial",(sql)=>exportTags(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("projects").description("Export projects")).action(async(options)=>{try{await runExport(["projects"],"partial",(sql)=>exportGroup(sql,"projects"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("schedules [name]").description("Export schedules with run_spec")).action(async(name,options)=>{try{await runExport(["schedules"],"partial",(sql)=>exportSchedules(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("agents").description("Export agents, templates, and checkpoints")).action(async(options)=>{try{await runExport(["agents"],"partial",(sql)=>exportGroup(sql,"agents"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("apps").description("Export app store (graceful skip if missing)")).action(async(options)=>{try{await runExport(["apps"],"partial",(sql)=>exportGroup(sql,"apps"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("comms").description("Export conversations, messages, mailbox")).action(async(options)=>{try{await runExport(["comms"],"partial",(sql)=>exportGroup(sql,"comms"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("config").description("Export OS config (graceful skip if missing)")).action(async(options)=>{try{await runExport(["config"],"partial",(sql)=>exportGroup(sql,"config"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_registry();function truncate3(str5,maxLen){if(str5.length<=maxLen)return str5;return`${str5.slice(0,maxLen-3)}...`}function formatPath(path2){let shortened=path2.replace(/^\/home\/\w+\/workspace\//,"~/").replace(/^\/home\/\w+\//,"~/");return truncate3(shortened,50)}function flushPendingReads(ctx){if(ctx.pendingReads.length===0)return;ctx.events.push({timestamp:ctx.lastReadTime,type:"read",summary:ctx.pendingReads.length===1?`Read: ${formatPath(ctx.pendingReads[0])}`:`Read ${ctx.pendingReads.length} files`,details:ctx.pendingReads.length>1?ctx.pendingReads.map(formatPath):void 0}),ctx.pendingReads.length=0}function flushPendingEdits(ctx){if(ctx.pendingEdits.length===0)return;ctx.events.push({timestamp:ctx.lastEditTime,type:"edit",summary:ctx.pendingEdits.length===1?`Edit: ${formatPath(ctx.pendingEdits[0])}`:`Edit ${ctx.pendingEdits.length} files`,details:ctx.pendingEdits.length>1?ctx.pendingEdits.map(formatPath):void 0}),ctx.pendingEdits.length=0}function flushAll(ctx){flushPendingReads(ctx),flushPendingEdits(ctx)}function processToolCallEntry(entry,ctx){if(!entry.toolCall)return;let{name,input}=entry.toolCall,inputRecord=input,normalizedName=name==="shell"||name==="exec_command"?"Bash":name;switch(normalizedName){case"Read":if(ctx.lastReadTime=entry.timestamp,inputRecord.file_path)ctx.pendingReads.push(String(inputRecord.file_path));break;case"Edit":if(flushPendingReads(ctx),ctx.lastEditTime=entry.timestamp,inputRecord.file_path)ctx.pendingEdits.push(String(inputRecord.file_path));break;case"Write":flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"write",summary:`Write: ${formatPath(String(inputRecord.file_path||"unknown"))}`});break;case"Bash":{flushAll(ctx);let cmd=String(inputRecord.command||"").replace(/\n/g," ");ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`Bash: ${truncate3(cmd,60)}`});break}case"AskUserQuestion":{flushAll(ctx);let questions=inputRecord.questions;ctx.events.push({timestamp:entry.timestamp,type:"question",summary:`Question: ${truncate3(questions?.[0]?.question||"question",60)}`});break}default:flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`${normalizedName}: ${truncate3(entry.text.replace(/\n/g," "),60)}`})}}function processTranscriptEntry(entry,ctx){if(entry.role==="user"){flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"prompt",summary:truncate3(entry.text.replace(/\n/g," "),80)});return}if(entry.role==="tool_call"){processToolCallEntry(entry,ctx);return}if(entry.role==="assistant"&&entry.text.length>100)flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"response",summary:truncate3(entry.text.replace(/\n/g," "),80)})}function extractEvents(entries){let ctx={events:[],pendingReads:[],pendingEdits:[],lastReadTime:"",lastEditTime:""};for(let entry of entries)processTranscriptEntry(entry,ctx);return flushAll(ctx),ctx.events}function detectStatus(entries){if(entries.length===0)return"unknown";let lastEntries=entries.slice(-10);for(let entry of lastEntries.reverse())if(entry.role==="tool_call"&&entry.toolCall?.name==="AskUserQuestion")return"question";let last=entries[entries.length-1];if(last.role==="user")return"working";if(last.role==="assistant")return"idle";return"unknown"}function formatEventsForDisplay(events,stats){let lines=[];lines.push(""),lines.push(`Session: ${stats.workerId} [${stats.provider}]${stats.branch?` (${stats.branch})`:""} | ${stats.duration} | ${stats.totalEntries} entries \u2192 ${stats.compressedLines} lines`),lines.push("");for(let event of events){let time=formatTime(event.timestamp),icon=getEventIcon(event.type);if(lines.push(`[${time}] ${icon} ${event.summary}`),event.details&&event.details.length>0){for(let detail of event.details.slice(0,5))lines.push(` ${detail}`);if(event.details.length>5)lines.push(` ... and ${event.details.length-5} more`)}if(event.result)lines.push(` \u2192 ${event.result}`)}return lines.push(""),lines.push(`Status: ${stats.status.toUpperCase()} | ${stats.exchanges} exchanges | ${stats.toolCalls} tool calls | ${stats.compressionRatio.toFixed(0)}x compression`),lines.push(""),lines.join(`
2271
- `)}function getEventIcon(type2){switch(type2){case"prompt":return"\uD83D\uDCAC";case"read":return"\uD83D\uDCD6";case"edit":return"\u270F\uFE0F";case"write":return"\uD83D\uDCDD";case"bash":return"\u26A1";case"question":return"\u2753";case"answer":return"\u2705";case"permission":return"\uD83D\uDD10";case"thinking":return"\uD83E\uDD14";case"response":return"\uD83D\uDCAD";default:return"\u2022"}}function formatToolDetail(toolCall){let input=toolCall.input;if(toolCall.name==="Bash"||toolCall.name==="shell")return` ${toolCall.name}: ${input.command??""}`;if(["Read","Edit","Write"].includes(toolCall.name))return` ${toolCall.name}: ${input.file_path??""}`;return` ${toolCall.name}`}function formatTranscriptEntryForDisplay(entry){let time=formatTime(entry.timestamp);switch(entry.role){case"user":return[`
2272
- [${time}] USER:`,entry.text];case"tool_call":return entry.toolCall?[`
2273
- [${time}] TOOL:`,formatToolDetail(entry.toolCall)]:[];case"assistant":return[`
2274
- [${time}] ASSISTANT:`,truncate3(entry.text,500)];case"tool_result":return[`
2275
- [${time}] RESULT:`,` ${truncate3(entry.text,500)}`];case"system":return[`
2276
- [${time}] SYSTEM:`,entry.text];default:return[]}}function formatFullConversation(entries){return entries.flatMap(formatTranscriptEntryForDisplay).join(`
2277
- `)}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 buildFilter(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=buildFilter(options);if(transcriptFilter)filtered=applyFilter2(filtered,transcriptFilter);return filtered}function outputEntries(filtered,options){if(options.ndjson){for(let entry of filtered){let{raw:_raw,...rest}=entry;console.log(JSON.stringify(options.raw?entry:rest))}return!0}if(options.raw){for(let entry of filtered)console.log(JSON.stringify(entry.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,stats={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,events},null,2));return}console.log(formatEventsForDisplay(events,stats))}init_export_format();import{readFileSync as readFileSync17}from"fs";var IMPORT_LEVELS=[["schedules","sessions","projects","agent_templates","agent_checkpoints","tags","task_types","notification_preferences","app_store","os_config","golden_images","warm_pool","instances"],["triggers","boards","board_templates","agents","conversations","installed_apps","app_versions"],["tasks","runs","messages","conversation_members","mailbox","team_chat"],["task_tags","task_actors","task_dependencies","task_stage_log","heartbeats","machine_snapshots"]],SELF_REFERENTIAL_COLUMNS={tasks:"parent_id",messages:"reply_to_id",conversations:"parent_message_id"};function getTableLevel(table){for(let i2=0;i2<IMPORT_LEVELS.length;i2++)if(IMPORT_LEVELS[i2].includes(table))return i2;return-1}function sortByImportOrder(tables){return[...tables].sort((a,b2)=>{let la=getTableLevel(a),lb=getTableLevel(b2);return(la===-1?999:la)-(lb===-1?999:lb)})}function getPrimaryKey(table){return{task_tags:["task_id","tag_id"],task_actors:["task_id","actor_type","actor_id","role"],task_dependencies:["task_id","depends_on_id"],conversation_members:["conversation_id","actor_type","actor_id"],notification_preferences:["actor_type","actor_id","channel"]}[table]??["id"]}async function getSql2(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getActorName2(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables2(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}async function detectConflicts(sql,table,rows){if(rows.length===0)return[];let pk=getPrimaryKey(table);if(pk.length===1){let key=pk[0],ids=rows.map((r)=>r[key]),existing=await sql.unsafe(`SELECT ${key} FROM ${table} WHERE ${key} = ANY($1)`,[ids]),existingSet=new Set(existing.map((r)=>String(r[key])));return rows.filter((r)=>existingSet.has(String(r[key])))}let conflicts=[];for(let row of rows){let conditions=pk.map((col,i2)=>`${col} = $${i2+1}`).join(" AND "),values2=pk.map((col)=>row[col]);if((await sql.unsafe(`SELECT 1 FROM ${table} WHERE ${conditions} LIMIT 1`,values2)).length>0)conflicts.push(row)}return conflicts}function prepareRow(row,table,selfRefUpdates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],entries=Object.entries(row),columns=entries.map(([k])=>k),values2=entries.map(([,v])=>v);if(selfRefCol&&row[selfRefCol]!=null){let idx=columns.indexOf(selfRefCol);if(idx!==-1){let originalSelfRef=values2[idx];values2[idx]=null;let pk=getPrimaryKey(table);selfRefUpdates.push({pk:pk.length===1?row[pk[0]]:pk.map((k)=>row[k]),value:originalSelfRef})}}return{columns,values:values2,quotedCols:columns.map((c)=>`"${c}"`).join(", "),placeholders:values2.map((_,i2)=>`$${i2+1}`).join(", ")}}async function insertOneRow(tx,table,row,prepared,mode){let{quotedCols,placeholders,values:values2}=prepared,pk=getPrimaryKey(table);if(mode==="overwrite"){let pkCondition=pk.map((col,i2)=>`"${col}" = $${values2.length+i2+1}`).join(" AND "),pkValues=pk.map((col)=>row[col]);await tx.unsafe(`DELETE FROM ${table} WHERE ${pkCondition}`,pkValues),await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}else if(mode==="merge"){let onConflict=pk.map((c)=>`"${c}"`).join(", ");await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders}) ON CONFLICT (${onConflict}) DO NOTHING`,values2)}else await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}async function updateSelfRefs(tx,table,updates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],pk=getPrimaryKey(table);if(pk.length!==1)return;for(let{pk:pkVal,value}of updates)await tx.unsafe(`UPDATE ${table} SET "${selfRefCol}" = $1 WHERE "${pk[0]}" = $2`,[value,pkVal])}async function insertRows(tx,table,rows,mode){if(rows.length===0)return 0;let selfRefUpdates=[];for(let row of rows){let prepared=prepareRow(row,table,selfRefUpdates);await insertOneRow(tx,table,row,prepared,mode)}if(selfRefUpdates.length>0)await updateSelfRefs(tx,table,selfRefUpdates);return rows.length}function parseExportFile(filePath){let raw=readFileSync17(filePath,"utf-8"),parsed;try{parsed=JSON.parse(raw)}catch{throw Error(`Invalid JSON in ${filePath}`)}let validation=validateExportDocument(parsed);if(!validation.valid)throw Error(`Invalid export document: ${validation.error}`);return validation.doc}async function filterTablesByGroup(allTables,groupFilter){if(!groupFilter||groupFilter.length===0)return allTables;let{GROUP_TABLES:GROUP_TABLES2}=await Promise.resolve().then(() => (init_export_format(),exports_export_format)),allowedTables=new Set;for(let group of groupFilter){let tables=GROUP_TABLES2[group];if(tables)for(let t of tables)allowedTables.add(t);else console.warn(`Warning: Unknown group "${group}", skipping`)}return allTables.filter((t)=>allowedTables.has(t))}async function checkConflicts(sql,tables,data){for(let table of tables){let rows=data[table];if(!rows||rows.length===0)continue;let conflicts=await detectConflicts(sql,table,rows);if(conflicts.length>0){let pk=getPrimaryKey(table),ids=conflicts.slice(0,5).map((r)=>pk.map((k)=>r[k]).join(",")).join("; ");throw Error(`Conflict in table "${table}": ${conflicts.length} existing row(s) (e.g., ${ids}). Use --merge or --overwrite to resolve.`)}}}async function runImport(filePath,mode,groupFilter){let doc=parseExportFile(filePath),tablesToImport=await filterTablesByGroup(Object.keys(doc.data),groupFilter);if(tablesToImport.length===0){console.log("No tables to import.");return}tablesToImport=sortByImportOrder(tablesToImport);let sql=await getSql2(),{available}=await detectTables2(sql,tablesToImport),skippedTables=tablesToImport.filter((t)=>!available.includes(t));if(tablesToImport=available,skippedTables.length>0)console.log(`Skipping tables not in database: ${skippedTables.join(", ")}`);if(mode==="fail")await checkConflicts(sql,tablesToImport,doc.data);let totalInserted=0,tableStats={};await sql.begin(async(tx)=>{for(let table of tablesToImport){let rows=doc.data[table];if(!rows||rows.length===0)continue;let count=await insertRows(tx,table,rows,mode);tableStats[table]=count,totalInserted+=count}});let actor=await getActorName2(),{recordAuditEvent:recordAuditEvent2}=await Promise.resolve().then(() => (init_audit(),exports_audit));await recordAuditEvent2("import",filePath,"import_complete",actor,{mode,tables:tableStats,totalRows:totalInserted,skippedTables,sourceVersion:doc.version,sourceDate:doc.exportedAt}),console.log(`Import complete: ${totalInserted} rows across ${Object.keys(tableStats).length} tables`);for(let[table,count]of Object.entries(tableStats))if(count>0)console.log(` ${table}: ${count} rows`);if(skippedTables.length>0)console.log(`Skipped (not in DB): ${skippedTables.join(", ")}`)}function registerImportCommands(program2){program2.command("import <file>").description("Import genie data from JSON export").option("--fail","Abort on any conflict (default)").option("--merge","Skip existing rows, import new ones").option("--overwrite","Replace existing rows with imported data").option("--groups <list>","Comma-separated groups to import (e.g., boards,tags)").action(async(file,options)=>{try{let mode="fail";if(options.overwrite)mode="overwrite";else if(options.merge)mode="merge";let groupFilter=options.groups?.split(",").map((g)=>g.trim());await runImport(file,mode,groupFilter)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_workspace();init_templates();import{existsSync as existsSync36,mkdirSync as mkdirSync14,symlinkSync,writeFileSync as writeFileSync14}from"fs";import{basename as basename7,join as join43}from"path";function detectPgUrl(){if(process.env.DATABASE_URL)return process.env.DATABASE_URL;if(process.env.PG_URL)return process.env.PG_URL;try{let{execSync:execSync12}=__require("child_process"),match=execSync12("pgrep -af pgserve 2>/dev/null",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).match(/postgres(?:ql)?:\/\/[^\s]+/);if(match)return match[0]}catch{}return}async function initWorkspace2(){let cwd=process.cwd(),existing=findWorkspace(cwd);if(existing){console.log(`Already inside workspace: ${existing.root}`);return}let genieDir=join43(cwd,".genie");mkdirSync14(genieDir,{recursive:!0});let pgUrl=detectPgUrl(),config={name:basename7(cwd),pgUrl,tmuxSocket:"genie"};if(writeFileSync14(join43(genieDir,"workspace.json"),`${JSON.stringify(config,null,2)}
2278
- `),console.log(`Workspace created: ${cwd}`),pgUrl)console.log(` pgUrl: ${pgUrl}`);let agents=scanAgents2(cwd);if(agents.length>0){console.log(` Found ${agents.length} agent(s): ${agents.join(", ")}`);try{let{syncAgentDirectory:syncAgentDirectory2}=await Promise.resolve().then(() => (init_agent_sync(),exports_agent_sync)),result=await syncAgentDirectory2(cwd);if(result.registered.length>0)console.log(` Registered: ${result.registered.join(", ")}`);if(result.updated.length>0)console.log(` Updated: ${result.updated.join(", ")}`)}catch{}}}async function initAgent(name){let cwd=process.cwd(),ws=findWorkspace(cwd);if(!ws)console.error("Error: Not in a genie workspace. Run `genie init` first."),process.exit(1);let agentDir=join43(ws.root,"agents",name);if(existsSync36(agentDir))console.error(`Error: Agent directory already exists: ${agentDir}`),process.exit(1);mkdirSync14(agentDir,{recursive:!0}),mkdirSync14(join43(agentDir,"brain","memory"),{recursive:!0}),mkdirSync14(join43(agentDir,".claude"),{recursive:!0}),scaffoldAgentFiles(agentDir,name),writeFileSync14(join43(agentDir,".claude","settings.local.json"),`${JSON.stringify({agentName:name},null,2)}
2279
- `);let reposTarget=join43(ws.root,"repos");if(existsSync36(reposTarget))try{symlinkSync(reposTarget,join43(agentDir,"repos"))}catch{}if(console.log(`Agent scaffolded: agents/${name}/`),console.log(" AGENTS.md, SOUL.md, HEARTBEAT.md"),console.log(" brain/memory/"),console.log(" .claude/settings.local.json"),existsSync36(join43(agentDir,"repos")))console.log(" repos -> ../repos (symlink)")}function registerInitCommands(program2){program2.command("init").description("Initialize a genie workspace").action(async()=>{await initWorkspace2()}).command("agent <name>").description("Scaffold a new agent in the workspace").action(async(name)=>{await initAgent(name)})}init_agent_cache();init_audit();init_db();import{execSync as execSync12}from"child_process";import{existsSync as existsSync38,mkdirSync as mkdirSync15,rmSync as rmSync3}from"fs";import{homedir as homedir25}from"os";import{basename as basename9,join as join45}from"path";init_js_yaml();import{existsSync as existsSync37,readFileSync as readFileSync18}from"fs";import{basename as basename8,join as join44}from"path";var ITEM_TYPES=["agent","skill","app","board","workflow","stack","template","hook"];function parseManifest(yamlContent){let raw=load2(yamlContent);if(!raw||typeof raw!=="object")throw Error("Manifest YAML is empty or not an object");let obj=raw;if(!obj.name||typeof obj.name!=="string")throw Error("Manifest is missing required field: name");if(!obj.type||typeof obj.type!=="string")throw Error("Manifest is missing required field: type");if(!obj.version||typeof obj.version!=="string")throw Error("Manifest is missing required field: version");if(!ITEM_TYPES.includes(obj.type))throw Error(`Invalid item type "${obj.type}". Must be one of: ${ITEM_TYPES.join(", ")}`);return obj}var VALID_GATES=new Set(["human","agent","human+agent"]);function validateManifest(manifest,itemDir){let errors3=[],warnings=[];if(!manifest.name)errors3.push("Missing required field: name");if(!manifest.type)errors3.push("Missing required field: type");if(!manifest.version)errors3.push("Missing required field: version");if(manifest.type&&!ITEM_TYPES.includes(manifest.type))errors3.push(`Invalid item type "${manifest.type}". Must be one of: ${ITEM_TYPES.join(", ")}`);let{type:type2}=manifest;if(type2&&!manifest[type2])warnings.push(`Manifest type is "${type2}" but no "${type2}" section is defined`);if(manifest.agent)validateEntrypoint(manifest.agent.entrypoint,itemDir,"agent",errors3);if(manifest.skill)validateEntrypoint(manifest.skill.entrypoint,itemDir,"skill",errors3);if(manifest.app)validateEntrypoint(manifest.app.entrypoint,itemDir,"app",errors3);if(manifest.board)validateStages(manifest.board.stages,errors3);if(manifest.workflow)validateCron(manifest.workflow.cron,errors3);if(manifest.stack)validateStackItems(manifest.stack.items,errors3);return{valid:errors3.length===0,errors:errors3,warnings}}function validateEntrypoint(entrypoint,itemDir,section,errors3){if(!entrypoint){errors3.push(`${section} section is missing required field: entrypoint`);return}let fullPath=join44(itemDir,entrypoint);if(!existsSync37(fullPath))errors3.push(`${section} entrypoint not found: ${entrypoint} (expected at ${fullPath})`)}function validateStages(stages,errors3){if(!stages||!Array.isArray(stages)){errors3.push("board section is missing required field: stages");return}for(let i2=0;i2<stages.length;i2++){let stage=stages[i2];if(!stage.name)errors3.push(`board.stages[${i2}] is missing required field: name`);if(!stage.gate)errors3.push(`board.stages[${i2}] is missing required field: gate`);else if(!VALID_GATES.has(stage.gate))errors3.push(`board.stages[${i2}].gate "${stage.gate}" is invalid. Must be one of: human, agent, human+agent`)}}function validateCron(cron,errors3){if(!cron){errors3.push("workflow section is missing required field: cron");return}let fields=cron.trim().split(/\s+/);if(fields.length<5||fields.length>6)errors3.push(`workflow.cron "${cron}" is invalid: expected 5 or 6 space-separated fields, got ${fields.length}`)}function validateStackItems(items,errors3){if(!items||!Array.isArray(items)){errors3.push("stack section is missing required field: items");return}for(let i2=0;i2<items.length;i2++){let item=items[i2];if(!item.type)errors3.push(`stack.items[${i2}] is missing required field: type`);if(!item.source&&!item.inline)errors3.push(`stack.items[${i2}] must have either "source" or "inline: true"`)}}async function detectManifest(dir){let yamlPath=join44(dir,"genie.yaml");if(existsSync37(yamlPath))try{let content=readFileSync18(yamlPath,"utf-8");return{manifest:parseManifest(content),source:"genie.yaml"}}catch(err){return{error:`Failed to parse genie.yaml: ${err.message}`}}let dirName=basename8(dir),agentsPath=join44(dir,"AGENTS.md");if(existsSync37(agentsPath))return{manifest:inferAgentManifest(agentsPath,dirName),source:"AGENTS.md"};let manifestTsPath=join44(dir,"manifest.ts");if(existsSync37(manifestTsPath))return{manifest:{name:dirName,type:"app",version:"0.0.0"},source:"manifest.ts"};let skillPath=join44(dir,"skill.md");if(existsSync37(skillPath))return{manifest:{name:dirName,type:"skill",version:"0.0.0"},source:"skill.md"};return{error:"No manifest found. Create a genie.yaml or use a recognized file pattern (AGENTS.md, manifest.ts, skill.md)."}}function extractFrontmatter(filePath){let match=readFileSync18(filePath,"utf-8").match(/^---\s*\n([\s\S]*?)\n---/);if(!match)return null;try{let parsed=load2(match[1]);if(parsed&&typeof parsed==="object")return parsed}catch{}return null}function inferAgentManifest(agentsPath,dirName){let frontmatter=extractFrontmatter(agentsPath),name=frontmatter?.name||dirName,model=frontmatter?.model,roles=Array.isArray(frontmatter?.roles)?frontmatter.roles:void 0,manifest={name,type:"agent",version:"0.0.0"};if(model||roles)manifest.agent={entrypoint:"AGENTS.md",...model&&{model},...roles&&{roles}};return manifest}var GENIE_HOME4=process.env.GENIE_HOME??join45(homedir25(),".genie"),ITEMS_DIR=join45(GENIE_HOME4,"items");function parseInstallTarget(target){let url=target,version,atIdx=url.lastIndexOf("@");if(atIdx>0&&!url.slice(atIdx).includes("/"))version=url.slice(atIdx+1),url=url.slice(0,atIdx);if(!url.startsWith("http")&&!url.startsWith("git@")&&!url.startsWith("ssh://"))url=`https://${url}`;if(url.startsWith("https://")&&!url.endsWith(".git"))url=`${url}.git`;let name=basename9(url,".git");return{url,version,name}}function cloneRepo(url,dest,options){let args=["git","clone"];if(options.shallow!==!1)args.push("--depth","1");if(options.version)args.push("--branch",options.version);args.push(url,dest),execSync12(args.join(" "),{stdio:"pipe",timeout:120000})}function cleanupDir(dir){if(existsSync38(dir))rmSync3(dir,{recursive:!0,force:!0})}async function registerByType(manifest,installPath){switch(manifest.type){case"agent":await regenerateAgentCache();break;case"board":await registerBoard(manifest);break;case"workflow":await registerWorkflow(manifest);break;case"stack":await installStack(manifest,installPath);break}}async function registerBoard(manifest){if(!manifest.board?.stages)return;if(!await isAvailable())return;let sql=await getConnection(),stages=manifest.board.stages.map((s,i2)=>({id:crypto.randomUUID(),name:s.name,label:s.label??s.name,gate:s.gate,action:s.action??null,auto_advance:s.auto_advance??!1,roles:s.roles??["*"],color:s.color??"#94a3b8",parallel:!1,on_fail:null,position:i2,transitions:[]}));await sql`
2344
+ `);let tables=Object.keys(doc.data),rows=Object.values(doc.data).reduce((sum,arr)=>sum+arr.length,0);if(console.log(`Exported ${tables.length} tables (${rows} rows) to ${options.output}`),doc.skippedTables.length>0)console.log(`Skipped tables (not found): ${doc.skippedTables.join(", ")}`)}else console.log(json2)}function autoOutputName(){let d=new Date;return`genie-backup-${`${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}`}.json`}async function exportGroup(sql,group,filter){let tables=GROUP_TABLES[group],{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(filter)data[table]=[...await sql.unsafe(`SELECT * FROM ${table} WHERE ${filter.column} = $1`,[filter.value])];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}async function exportBoards(sql,name){let tables=GROUP_TABLES.boards,{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(name&&table==="boards")data[table]=[...await sql`SELECT * FROM boards WHERE name = ${name}`];else if(table==="task_types")data[table]=[...await sql`SELECT * FROM task_types WHERE is_builtin = false`];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}var TASK_JOIN_ALIASES={task_tags:"tt",task_actors:"ta",task_dependencies:"td",task_stage_log:"tsl"};async function resolveProjectId2(sql,projectName){let projects=await sql`SELECT id FROM projects WHERE name = ${projectName}`;if(projects.length===0)throw Error(`Project not found: ${projectName}`);return projects[0].id}function stripEphemeralFields(rows){return rows.map((r)=>{let{checkout_run_id,execution_locked_at,session_id,pane_id,...rest}=r;return rest})}async function exportTaskTable(sql,table,projectId){let alias=TASK_JOIN_ALIASES[table];if(table==="tasks"){let rows=projectId?[...await sql.unsafe("SELECT * FROM tasks WHERE project_id = $1",[projectId])]:[...await sql`SELECT * FROM tasks`];return stripEphemeralFields(rows)}if(alias&&projectId)return[...await sql.unsafe(`SELECT ${alias}.* FROM ${table} ${alias} JOIN tasks t ON ${alias}.task_id = t.id WHERE t.project_id = $1`,[projectId])];return[...await sql.unsafe(`SELECT * FROM ${table}`)]}async function exportTasks(sql,projectName){let tables=GROUP_TABLES.tasks,{available,skipped}=await detectTables(sql,tables),data={},projectId=projectName?await resolveProjectId2(sql,projectName):null;for(let table of available)data[table]=await exportTaskTable(sql,table,projectId);return{data,skipped}}async function exportSchedules(sql,name){let{available,skipped}=await detectTables(sql,["schedules"]),data={};if(available.includes("schedules"))if(name)data.schedules=[...await sql`SELECT * FROM schedules WHERE name = ${name}`];else data.schedules=[...await sql`SELECT * FROM schedules`];return{data,skipped}}async function exportTags(sql){let{available,skipped}=await detectTables(sql,["tags"]),data={};if(available.includes("tags"))data.tags=[...await sql`SELECT * FROM tags WHERE name NOT LIKE 'test-%'`];return{data,skipped}}async function exportAll(sql){let allSkipped=[],allData={};for(let group of ALL_GROUPS){let result;switch(group){case"boards":result=await exportBoards(sql);break;case"tasks":result=await exportTasks(sql);break;case"tags":result=await exportTags(sql);break;case"schedules":result=await exportSchedules(sql);break;default:result=await exportGroup(sql,group);break}Object.assign(allData,result.data),allSkipped.push(...result.skipped)}return{data:allData,skipped:allSkipped}}async function runExport(groups,type2,exportFn,options){let sql=await getSql(),[version,actor]=await Promise.all([getVersion(),getActorName()]),doc=createExportDocument(type2,groups,version,actor),{data,skipped}=await exportFn(sql);doc.data=data,doc.skippedTables=skipped,outputDocument(doc,options)}function registerExportCommands(program2){let exp=program2.command("export").description("Export genie data as JSON").option("--output <file>","Write to file instead of stdout").option("-o <file>","Alias for --output").option("--pretty","Pretty-print JSON").action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts=(cmd)=>cmd.option("--output <file>","Write to file instead of stdout").option("--pretty","Pretty-print JSON");sharedOpts(exp.command("all").description("Full backup (all present tables)")).action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("boards [name]").description("Export boards, templates, and task types")).action(async(name,options)=>{try{await runExport(["boards"],"partial",(sql)=>exportBoards(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tasks").description("Export tasks with deps, actors, and stage log").option("--project <name>","Filter by project name")).action(async(options)=>{try{await runExport(["tasks"],"partial",(sql)=>exportTasks(sql,options.project),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tags").description("Export tags")).action(async(options)=>{try{await runExport(["tags"],"partial",(sql)=>exportTags(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("projects").description("Export projects")).action(async(options)=>{try{await runExport(["projects"],"partial",(sql)=>exportGroup(sql,"projects"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("schedules [name]").description("Export schedules with run_spec")).action(async(name,options)=>{try{await runExport(["schedules"],"partial",(sql)=>exportSchedules(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("agents").description("Export agents, templates, and checkpoints")).action(async(options)=>{try{await runExport(["agents"],"partial",(sql)=>exportGroup(sql,"agents"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("apps").description("Export app store (graceful skip if missing)")).action(async(options)=>{try{await runExport(["apps"],"partial",(sql)=>exportGroup(sql,"apps"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("comms").description("Export conversations, messages, mailbox")).action(async(options)=>{try{await runExport(["comms"],"partial",(sql)=>exportGroup(sql,"comms"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("config").description("Export OS config (graceful skip if missing)")).action(async(options)=>{try{await runExport(["config"],"partial",(sql)=>exportGroup(sql,"config"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_history();init_export_format();import{readFileSync as readFileSync17}from"fs";var IMPORT_LEVELS=[["schedules","sessions","projects","agent_templates","agent_checkpoints","tags","task_types","notification_preferences","app_store","os_config","golden_images","warm_pool","instances"],["triggers","boards","board_templates","agents","conversations","installed_apps","app_versions"],["tasks","runs","messages","conversation_members","mailbox","team_chat"],["task_tags","task_actors","task_dependencies","task_stage_log","heartbeats","machine_snapshots"]],SELF_REFERENTIAL_COLUMNS={tasks:"parent_id",messages:"reply_to_id",conversations:"parent_message_id"};function getTableLevel(table){for(let i2=0;i2<IMPORT_LEVELS.length;i2++)if(IMPORT_LEVELS[i2].includes(table))return i2;return-1}function sortByImportOrder(tables){return[...tables].sort((a,b2)=>{let la=getTableLevel(a),lb=getTableLevel(b2);return(la===-1?999:la)-(lb===-1?999:lb)})}function getPrimaryKey(table){return{task_tags:["task_id","tag_id"],task_actors:["task_id","actor_type","actor_id","role"],task_dependencies:["task_id","depends_on_id"],conversation_members:["conversation_id","actor_type","actor_id"],notification_preferences:["actor_type","actor_id","channel"]}[table]??["id"]}async function getSql2(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getActorName2(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables2(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}async function detectConflicts(sql,table,rows){if(rows.length===0)return[];let pk=getPrimaryKey(table);if(pk.length===1){let key=pk[0],ids=rows.map((r)=>r[key]),existing=await sql.unsafe(`SELECT ${key} FROM ${table} WHERE ${key} = ANY($1)`,[ids]),existingSet=new Set(existing.map((r)=>String(r[key])));return rows.filter((r)=>existingSet.has(String(r[key])))}let conflicts=[];for(let row of rows){let conditions=pk.map((col,i2)=>`${col} = $${i2+1}`).join(" AND "),values2=pk.map((col)=>row[col]);if((await sql.unsafe(`SELECT 1 FROM ${table} WHERE ${conditions} LIMIT 1`,values2)).length>0)conflicts.push(row)}return conflicts}function prepareRow(row,table,selfRefUpdates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],entries=Object.entries(row),columns=entries.map(([k])=>k),values2=entries.map(([,v])=>v);if(selfRefCol&&row[selfRefCol]!=null){let idx=columns.indexOf(selfRefCol);if(idx!==-1){let originalSelfRef=values2[idx];values2[idx]=null;let pk=getPrimaryKey(table);selfRefUpdates.push({pk:pk.length===1?row[pk[0]]:pk.map((k)=>row[k]),value:originalSelfRef})}}return{columns,values:values2,quotedCols:columns.map((c)=>`"${c}"`).join(", "),placeholders:values2.map((_,i2)=>`$${i2+1}`).join(", ")}}async function insertOneRow(tx,table,row,prepared,mode){let{quotedCols,placeholders,values:values2}=prepared,pk=getPrimaryKey(table);if(mode==="overwrite"){let pkCondition=pk.map((col,i2)=>`"${col}" = $${values2.length+i2+1}`).join(" AND "),pkValues=pk.map((col)=>row[col]);await tx.unsafe(`DELETE FROM ${table} WHERE ${pkCondition}`,pkValues),await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}else if(mode==="merge"){let onConflict=pk.map((c)=>`"${c}"`).join(", ");await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders}) ON CONFLICT (${onConflict}) DO NOTHING`,values2)}else await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}async function updateSelfRefs(tx,table,updates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],pk=getPrimaryKey(table);if(pk.length!==1)return;for(let{pk:pkVal,value}of updates)await tx.unsafe(`UPDATE ${table} SET "${selfRefCol}" = $1 WHERE "${pk[0]}" = $2`,[value,pkVal])}async function insertRows(tx,table,rows,mode){if(rows.length===0)return 0;let selfRefUpdates=[];for(let row of rows){let prepared=prepareRow(row,table,selfRefUpdates);await insertOneRow(tx,table,row,prepared,mode)}if(selfRefUpdates.length>0)await updateSelfRefs(tx,table,selfRefUpdates);return rows.length}function parseExportFile(filePath){let raw=readFileSync17(filePath,"utf-8"),parsed;try{parsed=JSON.parse(raw)}catch{throw Error(`Invalid JSON in ${filePath}`)}let validation=validateExportDocument(parsed);if(!validation.valid)throw Error(`Invalid export document: ${validation.error}`);return validation.doc}async function filterTablesByGroup(allTables,groupFilter){if(!groupFilter||groupFilter.length===0)return allTables;let{GROUP_TABLES:GROUP_TABLES2}=await Promise.resolve().then(() => (init_export_format(),exports_export_format)),allowedTables=new Set;for(let group of groupFilter){let tables=GROUP_TABLES2[group];if(tables)for(let t of tables)allowedTables.add(t);else console.warn(`Warning: Unknown group "${group}", skipping`)}return allTables.filter((t)=>allowedTables.has(t))}async function checkConflicts(sql,tables,data){for(let table of tables){let rows=data[table];if(!rows||rows.length===0)continue;let conflicts=await detectConflicts(sql,table,rows);if(conflicts.length>0){let pk=getPrimaryKey(table),ids=conflicts.slice(0,5).map((r)=>pk.map((k)=>r[k]).join(",")).join("; ");throw Error(`Conflict in table "${table}": ${conflicts.length} existing row(s) (e.g., ${ids}). Use --merge or --overwrite to resolve.`)}}}async function runImport(filePath,mode,groupFilter){let doc=parseExportFile(filePath),tablesToImport=await filterTablesByGroup(Object.keys(doc.data),groupFilter);if(tablesToImport.length===0){console.log("No tables to import.");return}tablesToImport=sortByImportOrder(tablesToImport);let sql=await getSql2(),{available}=await detectTables2(sql,tablesToImport),skippedTables=tablesToImport.filter((t)=>!available.includes(t));if(tablesToImport=available,skippedTables.length>0)console.log(`Skipping tables not in database: ${skippedTables.join(", ")}`);if(mode==="fail")await checkConflicts(sql,tablesToImport,doc.data);let totalInserted=0,tableStats={};await sql.begin(async(tx)=>{for(let table of tablesToImport){let rows=doc.data[table];if(!rows||rows.length===0)continue;let count=await insertRows(tx,table,rows,mode);tableStats[table]=count,totalInserted+=count}});let actor=await getActorName2(),{recordAuditEvent:recordAuditEvent2}=await Promise.resolve().then(() => (init_audit(),exports_audit));await recordAuditEvent2("import",filePath,"import_complete",actor,{mode,tables:tableStats,totalRows:totalInserted,skippedTables,sourceVersion:doc.version,sourceDate:doc.exportedAt}),console.log(`Import complete: ${totalInserted} rows across ${Object.keys(tableStats).length} tables`);for(let[table,count]of Object.entries(tableStats))if(count>0)console.log(` ${table}: ${count} rows`);if(skippedTables.length>0)console.log(`Skipped (not in DB): ${skippedTables.join(", ")}`)}function registerImportCommands(program2){program2.command("import <file>").description("Import genie data from JSON export").option("--fail","Abort on any conflict (default)").option("--merge","Skip existing rows, import new ones").option("--overwrite","Replace existing rows with imported data").option("--groups <list>","Comma-separated groups to import (e.g., boards,tags)").action(async(file,options)=>{try{let mode="fail";if(options.overwrite)mode="overwrite";else if(options.merge)mode="merge";let groupFilter=options.groups?.split(",").map((g)=>g.trim());await runImport(file,mode,groupFilter)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_workspace();init_templates();import{existsSync as existsSync36,mkdirSync as mkdirSync14,symlinkSync,writeFileSync as writeFileSync14}from"fs";import{basename as basename7,join as join44}from"path";function detectPgUrl(){if(process.env.DATABASE_URL)return process.env.DATABASE_URL;if(process.env.PG_URL)return process.env.PG_URL;try{let{execSync:execSync12}=__require("child_process"),match=execSync12("pgrep -af pgserve 2>/dev/null",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).match(/postgres(?:ql)?:\/\/[^\s]+/);if(match)return match[0]}catch{}return}async function initWorkspace2(){let cwd=process.cwd(),existing=findWorkspace(cwd);if(existing){console.log(`Already inside workspace: ${existing.root}`);return}let genieDir=join44(cwd,".genie");mkdirSync14(genieDir,{recursive:!0});let pgUrl=detectPgUrl(),config={name:basename7(cwd),pgUrl,tmuxSocket:"genie"};if(writeFileSync14(join44(genieDir,"workspace.json"),`${JSON.stringify(config,null,2)}
2345
+ `),console.log(`Workspace created: ${cwd}`),pgUrl)console.log(` pgUrl: ${pgUrl}`);let agents=scanAgents2(cwd);if(agents.length>0){console.log(` Found ${agents.length} agent(s): ${agents.join(", ")}`);try{let{syncAgentDirectory:syncAgentDirectory2}=await Promise.resolve().then(() => (init_agent_sync(),exports_agent_sync)),result=await syncAgentDirectory2(cwd);if(result.registered.length>0)console.log(` Registered: ${result.registered.join(", ")}`);if(result.updated.length>0)console.log(` Updated: ${result.updated.join(", ")}`)}catch{}}}async function initAgent(name){let cwd=process.cwd(),ws=findWorkspace(cwd);if(!ws)console.error("Error: Not in a genie workspace. Run `genie init` first."),process.exit(1);let agentDir=join44(ws.root,"agents",name);if(existsSync36(agentDir))console.error(`Error: Agent directory already exists: ${agentDir}`),process.exit(1);mkdirSync14(agentDir,{recursive:!0}),mkdirSync14(join44(agentDir,"brain","memory"),{recursive:!0}),mkdirSync14(join44(agentDir,".claude"),{recursive:!0}),scaffoldAgentFiles(agentDir,name),writeFileSync14(join44(agentDir,".claude","settings.local.json"),`${JSON.stringify({agentName:name},null,2)}
2346
+ `);let reposTarget=join44(ws.root,"repos");if(existsSync36(reposTarget))try{symlinkSync(reposTarget,join44(agentDir,"repos"))}catch{}if(console.log(`Agent scaffolded: agents/${name}/`),console.log(" AGENTS.md, SOUL.md, HEARTBEAT.md"),console.log(" brain/memory/"),console.log(" .claude/settings.local.json"),existsSync36(join44(agentDir,"repos")))console.log(" repos -> ../repos (symlink)")}function registerInitCommands(program2){program2.command("init").description("Initialize a genie workspace").action(async()=>{await initWorkspace2()}).command("agent <name>").description("Scaffold a new agent in the workspace").action(async(name)=>{await initAgent(name)})}init_agent_cache();init_audit();init_db();import{execSync as execSync12}from"child_process";import{existsSync as existsSync38,mkdirSync as mkdirSync15,rmSync as rmSync3}from"fs";import{homedir as homedir26}from"os";import{basename as basename9,join as join46}from"path";init_js_yaml();import{existsSync as existsSync37,readFileSync as readFileSync18}from"fs";import{basename as basename8,join as join45}from"path";var ITEM_TYPES=["agent","skill","app","board","workflow","stack","template","hook"];function parseManifest(yamlContent){let raw=load2(yamlContent);if(!raw||typeof raw!=="object")throw Error("Manifest YAML is empty or not an object");let obj=raw;if(!obj.name||typeof obj.name!=="string")throw Error("Manifest is missing required field: name");if(!obj.type||typeof obj.type!=="string")throw Error("Manifest is missing required field: type");if(!obj.version||typeof obj.version!=="string")throw Error("Manifest is missing required field: version");if(!ITEM_TYPES.includes(obj.type))throw Error(`Invalid item type "${obj.type}". Must be one of: ${ITEM_TYPES.join(", ")}`);return obj}var VALID_GATES=new Set(["human","agent","human+agent"]);function validateManifest(manifest,itemDir){let errors3=[],warnings=[];if(!manifest.name)errors3.push("Missing required field: name");if(!manifest.type)errors3.push("Missing required field: type");if(!manifest.version)errors3.push("Missing required field: version");if(manifest.type&&!ITEM_TYPES.includes(manifest.type))errors3.push(`Invalid item type "${manifest.type}". Must be one of: ${ITEM_TYPES.join(", ")}`);let{type:type2}=manifest;if(type2&&!manifest[type2])warnings.push(`Manifest type is "${type2}" but no "${type2}" section is defined`);if(manifest.agent)validateEntrypoint(manifest.agent.entrypoint,itemDir,"agent",errors3);if(manifest.skill)validateEntrypoint(manifest.skill.entrypoint,itemDir,"skill",errors3);if(manifest.app)validateEntrypoint(manifest.app.entrypoint,itemDir,"app",errors3);if(manifest.board)validateStages(manifest.board.stages,errors3);if(manifest.workflow)validateCron(manifest.workflow.cron,errors3);if(manifest.stack)validateStackItems(manifest.stack.items,errors3);return{valid:errors3.length===0,errors:errors3,warnings}}function validateEntrypoint(entrypoint,itemDir,section,errors3){if(!entrypoint){errors3.push(`${section} section is missing required field: entrypoint`);return}let fullPath=join45(itemDir,entrypoint);if(!existsSync37(fullPath))errors3.push(`${section} entrypoint not found: ${entrypoint} (expected at ${fullPath})`)}function validateStages(stages,errors3){if(!stages||!Array.isArray(stages)){errors3.push("board section is missing required field: stages");return}for(let i2=0;i2<stages.length;i2++){let stage=stages[i2];if(!stage.name)errors3.push(`board.stages[${i2}] is missing required field: name`);if(!stage.gate)errors3.push(`board.stages[${i2}] is missing required field: gate`);else if(!VALID_GATES.has(stage.gate))errors3.push(`board.stages[${i2}].gate "${stage.gate}" is invalid. Must be one of: human, agent, human+agent`)}}function validateCron(cron,errors3){if(!cron){errors3.push("workflow section is missing required field: cron");return}let fields=cron.trim().split(/\s+/);if(fields.length<5||fields.length>6)errors3.push(`workflow.cron "${cron}" is invalid: expected 5 or 6 space-separated fields, got ${fields.length}`)}function validateStackItems(items,errors3){if(!items||!Array.isArray(items)){errors3.push("stack section is missing required field: items");return}for(let i2=0;i2<items.length;i2++){let item=items[i2];if(!item.type)errors3.push(`stack.items[${i2}] is missing required field: type`);if(!item.source&&!item.inline)errors3.push(`stack.items[${i2}] must have either "source" or "inline: true"`)}}async function detectManifest(dir){let yamlPath=join45(dir,"genie.yaml");if(existsSync37(yamlPath))try{let content=readFileSync18(yamlPath,"utf-8");return{manifest:parseManifest(content),source:"genie.yaml"}}catch(err){return{error:`Failed to parse genie.yaml: ${err.message}`}}let dirName=basename8(dir),agentsPath=join45(dir,"AGENTS.md");if(existsSync37(agentsPath))return{manifest:inferAgentManifest(agentsPath,dirName),source:"AGENTS.md"};let manifestTsPath=join45(dir,"manifest.ts");if(existsSync37(manifestTsPath))return{manifest:{name:dirName,type:"app",version:"0.0.0"},source:"manifest.ts"};let skillPath=join45(dir,"skill.md");if(existsSync37(skillPath))return{manifest:{name:dirName,type:"skill",version:"0.0.0"},source:"skill.md"};return{error:"No manifest found. Create a genie.yaml or use a recognized file pattern (AGENTS.md, manifest.ts, skill.md)."}}function extractFrontmatter(filePath){let match=readFileSync18(filePath,"utf-8").match(/^---\s*\n([\s\S]*?)\n---/);if(!match)return null;try{let parsed=load2(match[1]);if(parsed&&typeof parsed==="object")return parsed}catch{}return null}function inferAgentManifest(agentsPath,dirName){let frontmatter=extractFrontmatter(agentsPath),name=frontmatter?.name||dirName,model=frontmatter?.model,roles=Array.isArray(frontmatter?.roles)?frontmatter.roles:void 0,manifest={name,type:"agent",version:"0.0.0"};if(model||roles)manifest.agent={entrypoint:"AGENTS.md",...model&&{model},...roles&&{roles}};return manifest}var GENIE_HOME4=process.env.GENIE_HOME??join46(homedir26(),".genie"),ITEMS_DIR=join46(GENIE_HOME4,"items");function parseInstallTarget(target){let url=target,version,atIdx=url.lastIndexOf("@");if(atIdx>0&&!url.slice(atIdx).includes("/"))version=url.slice(atIdx+1),url=url.slice(0,atIdx);if(!url.startsWith("http")&&!url.startsWith("git@")&&!url.startsWith("ssh://"))url=`https://${url}`;if(url.startsWith("https://")&&!url.endsWith(".git"))url=`${url}.git`;let name=basename9(url,".git");return{url,version,name}}function cloneRepo(url,dest,options){let args=["git","clone"];if(options.shallow!==!1)args.push("--depth","1");if(options.version)args.push("--branch",options.version);args.push(url,dest),execSync12(args.join(" "),{stdio:"pipe",timeout:120000})}function cleanupDir(dir){if(existsSync38(dir))rmSync3(dir,{recursive:!0,force:!0})}async function registerByType(manifest,installPath){switch(manifest.type){case"agent":await regenerateAgentCache();break;case"board":await registerBoard(manifest);break;case"workflow":await registerWorkflow(manifest);break;case"stack":await installStack(manifest,installPath);break}}async function registerBoard(manifest){if(!manifest.board?.stages)return;if(!await isAvailable())return;let sql=await getConnection(),stages=manifest.board.stages.map((s,i2)=>({id:crypto.randomUUID(),name:s.name,label:s.label??s.name,gate:s.gate,action:s.action??null,auto_advance:s.auto_advance??!1,roles:s.roles??["*"],color:s.color??"#94a3b8",parallel:!1,on_fail:null,position:i2,transitions:[]}));await sql`
2280
2347
  INSERT INTO task_types (id, name, description, stages, is_builtin)
2281
2348
  VALUES (
2282
2349
  ${manifest.name},
@@ -2310,19 +2377,19 @@ Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderT
2310
2377
  INSERT INTO app_store (name, item_type, version, manifest)
2311
2378
  VALUES (${item.name}, ${item.type}, ${version}, ${tx.json(item.config??{})})
2312
2379
  ON CONFLICT (name) DO NOTHING
2313
- `}async function handleExternalStackItem(item,tx){if(!item.source)throw Error(`Stack item "${item.name}" is missing source`);let parsed=parseInstallTarget(item.source),itemDir=join45(ITEMS_DIR,parsed.name);mkdirSync15(ITEMS_DIR,{recursive:!0}),cloneRepo(parsed.url,itemDir,{version:parsed.version});let detection=await detectManifest(itemDir);if("error"in detection)throw Error(`Stack item "${item.name}" (${item.source}): ${detection.error}`);let validation=validateManifest(detection.manifest,itemDir);if(!validation.valid)throw Error(`Stack item "${item.name}" validation failed: ${validation.errors.join(", ")}`);return await tx`
2380
+ `}async function handleExternalStackItem(item,tx){if(!item.source)throw Error(`Stack item "${item.name}" is missing source`);let parsed=parseInstallTarget(item.source),itemDir=join46(ITEMS_DIR,parsed.name);mkdirSync15(ITEMS_DIR,{recursive:!0}),cloneRepo(parsed.url,itemDir,{version:parsed.version});let detection=await detectManifest(itemDir);if("error"in detection)throw Error(`Stack item "${item.name}" (${item.source}): ${detection.error}`);let validation=validateManifest(detection.manifest,itemDir);if(!validation.valid)throw Error(`Stack item "${item.name}" validation failed: ${validation.errors.join(", ")}`);return await tx`
2314
2381
  INSERT INTO app_store (name, item_type, version, git_url, install_path, manifest)
2315
2382
  VALUES (
2316
2383
  ${detection.manifest.name}, ${detection.manifest.type}, ${detection.manifest.version},
2317
2384
  ${item.source}, ${itemDir}, ${tx.json(detection.manifest)}
2318
2385
  )
2319
2386
  ON CONFLICT (name) DO NOTHING
2320
- `,await registerByType(detection.manifest,itemDir),parsed.name}async function installStack(manifest,_installPath){let stackItems=manifest.stack?.items;if(!stackItems)return;if(!await isAvailable())return;let sql=await getConnection(),installed=[];try{await sql.begin(async(tx)=>{for(let item of stackItems)if(item.inline)await handleInlineStackItem(item,manifest.version,tx),installed.push(item.name);else if(item.source){let name=await handleExternalStackItem(item,tx);installed.push(name)}})}catch(err){for(let name of installed)cleanupDir(join45(ITEMS_DIR,name)),await removeItemFromStore(name).catch(()=>{});throw err}}async function handleInstall(target,options){let parsed=parseInstallTarget(target),installDir=join45(ITEMS_DIR,parsed.name),existing=await getItemFromStore(parsed.name).catch(()=>null);if(existing&&!options.force)console.error(`Item "${parsed.name}" is already installed. Use --force to override.`),process.exit(1);if(existing&&options.force)await removeItemFromStore(parsed.name).catch(()=>{}),cleanupDir(installDir);mkdirSync15(ITEMS_DIR,{recursive:!0}),console.log(`Cloning ${parsed.url}${parsed.version?` @ ${parsed.version}`:""}...`);try{cloneRepo(parsed.url,installDir,{shallow:!options.full,version:parsed.version})}catch(err){cleanupDir(installDir),console.error(`Clone failed: ${err.message}`),process.exit(1)}let detection=await detectManifest(installDir);if("error"in detection)cleanupDir(installDir),console.error(`Manifest detection failed: ${detection.error}`),process.exit(1);let{manifest,source}=detection;console.log(`Detected ${manifest.type} manifest from ${source}`);let validation=validateManifest(manifest,installDir);for(let w of validation.warnings)console.log(` Warning: ${w}`);if(!validation.valid)cleanupDir(installDir),console.error(`Validation failed:
2387
+ `,await registerByType(detection.manifest,itemDir),parsed.name}async function installStack(manifest,_installPath){let stackItems=manifest.stack?.items;if(!stackItems)return;if(!await isAvailable())return;let sql=await getConnection(),installed=[];try{await sql.begin(async(tx)=>{for(let item of stackItems)if(item.inline)await handleInlineStackItem(item,manifest.version,tx),installed.push(item.name);else if(item.source){let name=await handleExternalStackItem(item,tx);installed.push(name)}})}catch(err){for(let name of installed)cleanupDir(join46(ITEMS_DIR,name)),await removeItemFromStore(name).catch(()=>{});throw err}}async function handleInstall(target,options){let parsed=parseInstallTarget(target),installDir=join46(ITEMS_DIR,parsed.name),existing=await getItemFromStore(parsed.name).catch(()=>null);if(existing&&!options.force)console.error(`Item "${parsed.name}" is already installed. Use --force to override.`),process.exit(1);if(existing&&options.force)await removeItemFromStore(parsed.name).catch(()=>{}),cleanupDir(installDir);mkdirSync15(ITEMS_DIR,{recursive:!0}),console.log(`Cloning ${parsed.url}${parsed.version?` @ ${parsed.version}`:""}...`);try{cloneRepo(parsed.url,installDir,{shallow:!options.full,version:parsed.version})}catch(err){cleanupDir(installDir),console.error(`Clone failed: ${err.message}`),process.exit(1)}let detection=await detectManifest(installDir);if("error"in detection)cleanupDir(installDir),console.error(`Manifest detection failed: ${detection.error}`),process.exit(1);let{manifest,source}=detection;console.log(`Detected ${manifest.type} manifest from ${source}`);let validation=validateManifest(manifest,installDir);for(let w of validation.warnings)console.log(` Warning: ${w}`);if(!validation.valid)cleanupDir(installDir),console.error(`Validation failed:
2321
2388
  ${validation.errors.map((e)=>` - ${e}`).join(`
2322
2389
  `)}`),process.exit(1);try{await registerItemInStore({name:manifest.name,itemType:manifest.type,version:manifest.version,description:manifest.description,authorName:manifest.author?.name,authorUrl:manifest.author?.url,gitUrl:parsed.url,installPath:installDir,manifest,tags:manifest.tags,category:manifest.category,license:manifest.license,dependencies:manifest.dependencies})}catch(err){cleanupDir(installDir),console.error(`Registration failed: ${err.message}`),process.exit(1)}await registerByType(manifest,installDir),recordAuditEvent("item",manifest.name,"item_installed",getActor(),{type:manifest.type,version:manifest.version,source:parsed.url,manifestSource:source}).catch(()=>{}),console.log(`
2323
- Installed ${manifest.type} "${manifest.name}" v${manifest.version}`),console.log(` Source: ${parsed.url}`),console.log(` Path: ${installDir}`)}function registerInstallCommand(program2){program2.command("install <target>").description("Install a genie item from a git URL (e.g. github.com/user/repo[@version])").option("--force","Override existing item with same name").option("--full","Full git clone instead of shallow").action(async(target,options)=>{try{await handleInstall(target,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{existsSync as existsSync39,readFileSync as readFileSync19,rmSync as rmSync4}from"fs";import{homedir as homedir26}from"os";import{join as join46}from"path";var GENIE_HOME5=process.env.GENIE_HOME??join46(homedir26(),".genie"),ITEMS_DIR2=join46(GENIE_HOME5,"items");async function deregisterByType(itemType,name){if(!await isAvailable())return;let sql=await getConnection();switch(itemType){case"agent":await sql`DELETE FROM agents WHERE id = ${`dir:${name}`}`.catch(()=>{}),await regenerateAgentCache();break;case"board":await sql`DELETE FROM task_types WHERE id = ${name}`.catch(()=>{});break;case"workflow":await sql`DELETE FROM schedules WHERE id = ${`sched-${name}`}`.catch(()=>{});break;case"app":await sql`DELETE FROM installed_apps WHERE app_store_id IN (
2390
+ Installed ${manifest.type} "${manifest.name}" v${manifest.version}`),console.log(` Source: ${parsed.url}`),console.log(` Path: ${installDir}`)}function registerInstallCommand(program2){program2.command("install <target>").description("Install a genie item from a git URL (e.g. github.com/user/repo[@version])").option("--force","Override existing item with same name").option("--full","Full git clone instead of shallow").action(async(target,options)=>{try{await handleInstall(target,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{existsSync as existsSync39,readFileSync as readFileSync19,rmSync as rmSync4}from"fs";import{homedir as homedir27}from"os";import{join as join47}from"path";var GENIE_HOME5=process.env.GENIE_HOME??join47(homedir27(),".genie"),ITEMS_DIR2=join47(GENIE_HOME5,"items");async function deregisterByType(itemType,name){if(!await isAvailable())return;let sql=await getConnection();switch(itemType){case"agent":await sql`DELETE FROM agents WHERE id = ${`dir:${name}`}`.catch(()=>{}),await regenerateAgentCache();break;case"board":await sql`DELETE FROM task_types WHERE id = ${name}`.catch(()=>{});break;case"workflow":await sql`DELETE FROM schedules WHERE id = ${`sched-${name}`}`.catch(()=>{});break;case"app":await sql`DELETE FROM installed_apps WHERE app_store_id IN (
2324
2391
  SELECT id FROM app_store WHERE name = ${name}
2325
- )`.catch(()=>{});break;case"stack":{let manifestPath=join46(ITEMS_DIR2,name,"genie.yaml");if(existsSync39(manifestPath))try{let stack=(await Promise.resolve().then(() => (init_js_yaml(),exports_js_yaml))).load(readFileSync19(manifestPath,"utf-8")).stack;if(stack?.items)for(let item of stack.items)await deregisterByType(item.type,item.name),await removeItemFromStore(item.name).catch(()=>{})}catch{}break}}}async function handleUninstall(name){let existing=await getItemFromStore(name).catch(()=>null);if(!existing)console.error(`Item "${name}" is not installed.`),process.exit(1);let itemType=existing.item_type;await deregisterByType(itemType,name),await removeItemFromStore(name);let installDir=join46(ITEMS_DIR2,name);if(existsSync39(installDir))rmSync4(installDir,{recursive:!0,force:!0});recordAuditEvent("item",name,"item_uninstalled",getActor(),{type:itemType,version:existing.version}).catch(()=>{}),console.log(`Uninstalled ${itemType} "${name}".`)}function registerItemUninstallCommand(parent){parent.command("uninstall <name>").description("Remove an installed genie item").action(async(name)=>{try{await handleUninstall(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{execSync as execSync13}from"child_process";import{existsSync as existsSync40}from"fs";import{homedir as homedir27}from"os";import{join as join47}from"path";var GENIE_HOME6=process.env.GENIE_HOME??join47(homedir27(),".genie"),ITEMS_DIR3=join47(GENIE_HOME6,"items");async function reregisterByType(manifest){if(!await isAvailable())return;let sql=await getConnection();switch(manifest.type){case"agent":await regenerateAgentCache();break;case"board":if(manifest.board?.stages){let stages=manifest.board.stages.map((s,i2)=>({id:crypto.randomUUID(),name:s.name,label:s.label??s.name,gate:s.gate,action:s.action??null,auto_advance:s.auto_advance??!1,roles:s.roles??["*"],color:s.color??"#94a3b8",parallel:!1,on_fail:null,position:i2,transitions:[]}));await sql`
2392
+ )`.catch(()=>{});break;case"stack":{let manifestPath=join47(ITEMS_DIR2,name,"genie.yaml");if(existsSync39(manifestPath))try{let stack=(await Promise.resolve().then(() => (init_js_yaml(),exports_js_yaml))).load(readFileSync19(manifestPath,"utf-8")).stack;if(stack?.items)for(let item of stack.items)await deregisterByType(item.type,item.name),await removeItemFromStore(item.name).catch(()=>{})}catch{}break}}}async function handleUninstall(name){let existing=await getItemFromStore(name).catch(()=>null);if(!existing)console.error(`Item "${name}" is not installed.`),process.exit(1);let itemType=existing.item_type;await deregisterByType(itemType,name),await removeItemFromStore(name);let installDir=join47(ITEMS_DIR2,name);if(existsSync39(installDir))rmSync4(installDir,{recursive:!0,force:!0});recordAuditEvent("item",name,"item_uninstalled",getActor(),{type:itemType,version:existing.version}).catch(()=>{}),console.log(`Uninstalled ${itemType} "${name}".`)}function registerItemUninstallCommand(parent){parent.command("uninstall <name>").description("Remove an installed genie item").action(async(name)=>{try{await handleUninstall(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{execSync as execSync13}from"child_process";import{existsSync as existsSync40}from"fs";import{homedir as homedir28}from"os";import{join as join48}from"path";var GENIE_HOME6=process.env.GENIE_HOME??join48(homedir28(),".genie"),ITEMS_DIR3=join48(GENIE_HOME6,"items");async function reregisterByType(manifest){if(!await isAvailable())return;let sql=await getConnection();switch(manifest.type){case"agent":await regenerateAgentCache();break;case"board":if(manifest.board?.stages){let stages=manifest.board.stages.map((s,i2)=>({id:crypto.randomUUID(),name:s.name,label:s.label??s.name,gate:s.gate,action:s.action??null,auto_advance:s.auto_advance??!1,roles:s.roles??["*"],color:s.color??"#94a3b8",parallel:!1,on_fail:null,position:i2,transitions:[]}));await sql`
2326
2393
  UPDATE task_types SET stages = ${sql.json(stages)}, updated_at = now()
2327
2394
  WHERE id = ${manifest.name}
2328
2395
  `.catch(()=>{})}break;case"workflow":if(manifest.workflow)await sql`
@@ -2333,33 +2400,10 @@ Installed ${manifest.type} "${manifest.name}" v${manifest.version}`),console.log
2333
2400
  run_spec = ${sql.json(manifest.workflow.run_spec??{})},
2334
2401
  updated_at = now()
2335
2402
  WHERE id = ${`sched-${manifest.name}`}
2336
- `.catch(()=>{});break}}async function handleUpdateSingle(name,version){let existing=await getItemFromStore(name).catch(()=>null);if(!existing)return console.error(`Item "${name}" is not installed.`),!1;let installDir=existing.install_path??join47(ITEMS_DIR3,name);if(!existsSync40(installDir))return console.error(`Install directory not found: ${installDir}`),!1;try{if(version)execSync13(`git fetch --tags && git checkout ${version}`,{cwd:installDir,stdio:"pipe",timeout:60000});else execSync13("git pull --ff-only",{cwd:installDir,stdio:"pipe",timeout:60000})}catch(err){return console.error(`Git update failed for "${name}": ${err.message}`),!1}let detection=await detectManifest(installDir);if("error"in detection)return console.error(`Manifest detection failed after update: ${detection.error}`),!1;let{manifest}=detection,validation=validateManifest(manifest,installDir);for(let w of validation.warnings)console.log(` Warning: ${w}`);if(!validation.valid)return console.error(`Validation failed after update:
2403
+ `.catch(()=>{});break}}async function handleUpdateSingle(name,version){let existing=await getItemFromStore(name).catch(()=>null);if(!existing)return console.error(`Item "${name}" is not installed.`),!1;let installDir=existing.install_path??join48(ITEMS_DIR3,name);if(!existsSync40(installDir))return console.error(`Install directory not found: ${installDir}`),!1;try{if(version)execSync13(`git fetch --tags && git checkout ${version}`,{cwd:installDir,stdio:"pipe",timeout:60000});else execSync13("git pull --ff-only",{cwd:installDir,stdio:"pipe",timeout:60000})}catch(err){return console.error(`Git update failed for "${name}": ${err.message}`),!1}let detection=await detectManifest(installDir);if("error"in detection)return console.error(`Manifest detection failed after update: ${detection.error}`),!1;let{manifest}=detection,validation=validateManifest(manifest,installDir);for(let w of validation.warnings)console.log(` Warning: ${w}`);if(!validation.valid)return console.error(`Validation failed after update:
2337
2404
  ${validation.errors.map((e)=>` - ${e}`).join(`
2338
2405
  `)}`),!1;return await updateItemInStore(name,{version:manifest.version,description:manifest.description,manifest}),await reregisterByType(manifest),recordAuditEvent("item",name,"item_updated",getActor(),{type:manifest.type,version:manifest.version,previousVersion:existing.version}).catch(()=>{}),console.log(`Updated ${manifest.type} "${name}" \u2192 v${manifest.version}`),!0}async function handleUpdate(nameOrVersion,options){if(options.all){let gitItems=(await listItemsFromStore()).filter((i2)=>i2.git_url);if(gitItems.length===0){console.log("No git-installed items to update.");return}console.log(`Updating ${gitItems.length} item(s)...`);let updated=0;for(let item of gitItems)if(await handleUpdateSingle(item.name))updated++;console.log(`
2339
- ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVersion)console.error("Usage: genie update <name>[@version] or genie update --all"),process.exit(1);let name=nameOrVersion,version,atIdx=name.lastIndexOf("@");if(atIdx>0)version=name.slice(atIdx+1),name=name.slice(0,atIdx);if(!await handleUpdateSingle(name,version))process.exit(1)}function registerItemUpdateCommand(parent){parent.command("update [name]").description("Update an installed item to the latest version or a specific tag").option("--all","Update all git-installed items").action(async(name,options)=>{try{await handleUpdate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_registry();init_mailbox();init_runtime_events();init_db();function rowToMessage2(row){return{id:row.id,sender:row.sender,body:row.body,timestamp:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)}}async function readMessages(repoPath,teamName,since){let sql=await getConnection();if(since)return(await sql`
2340
- SELECT * FROM team_chat
2341
- WHERE team = ${teamName} AND repo_path = ${repoPath} AND created_at >= ${since}
2342
- ORDER BY created_at ASC
2343
- `).map(rowToMessage2);return(await sql`
2344
- SELECT * FROM team_chat
2345
- WHERE team = ${teamName} AND repo_path = ${repoPath}
2346
- ORDER BY created_at ASC
2347
- `).map(rowToMessage2)}function mailboxActorKeys(agent){let keys=[agent.id];if(agent.role&&agent.role!==agent.id)keys.push(agent.role);if(agent.customName&&!keys.includes(agent.customName))keys.push(agent.customName);return keys}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys),team?readMessages(repoPath,team):Promise.resolve([])]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys)]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startPgFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startPgFollow(agents,repoPath,teamName,filter,onEvent)}async function startPgFollow(agents,repoPath,team,filter,onEvent){let kindsFilter=filter?.kinds?new Set(filter.kinds):null,agentIds=new Set(agents.map((agent)=>agent.id)),seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,matchesScope=(event)=>{if(team==="all")return agentIds.has(event.agent);if(team&&event.team===team)return!0;return agentIds.has(event.agent)},handleRuntimeEvent=(event)=>{if(!event.timestamp||!event.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;if(!matchesScope(event))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},handle=await followRuntimeEvents({repoPath,agentIds:[...agentIds],team:team&&team!=="all"?team:void 0,kinds:filter?.kinds,scopeMode:team&&team!=="all"?"any":"all"},handleRuntimeEvent,{pollIntervalMs:500});return{mode:"pg",stop:()=>handle.stop()}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
2348
- `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;case"shell":case"exec_command":return`$ ${(Array.isArray(input.command)?input.command.join(" "):String(input.command??"")).split(`
2349
- `)[0]}`;case"web_search":return`Search: ${input.query??""}`;default:return`${tc.name}`}}function formatEventBlock(event){let time=formatTime(event.timestamp,{seconds:!0,fallback:"??:??:??"}),icon=kindIcon(event.kind),color=kindColor(event.kind),agent=event.agent;if(event.direction==="in")agent=`${event.peer} \u2192 ${event.agent}`;else if(event.direction==="out")agent=`${event.agent} \u2192 ${event.peer}`;let header=`${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}`;if(event.kind==="tool_call"){let summary=summarizeToolCall2(event);return`${header} ${DIM}${summary}${RESET}`}if(event.kind==="tool_result"){let line=event.text.split(`
2350
- `)[0].slice(0,120);return`${header} ${DIM}${line}${RESET}`}let text=event.text.trim();if(text.length<80&&!text.includes(`
2351
- `))return`${header}
2352
- ${text}`;let indented=text.split(`
2353
- `).map((l)=>` ${l}`).join(`
2354
- `);return`${header}
2355
- ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push(""),lines.push(`${BOLD}Log: ${label}${RESET} (${events.length} events)`),lines.push(""),events.length===0)return lines.push(" No events found."),lines.push(""),lines.join(`
2356
- `);let lastKind=null;for(let event of events){if(lastKind!==null&&!(lastKind==="tool_call"&&event.kind==="tool_call"))lines.push("");lines.push(formatEventBlock(event)),lastKind=event.kind}return lines.push(""),lines.join(`
2357
- `)}async function findAgent(identifier,teamName){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;let all=await list(),teamMatch=(teamName?all.filter((a)=>a.team===teamName):[]).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()));if(teamMatch)return teamMatch;return all.find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
2358
- `)}function outputJson(events){process.stdout.write(`${JSON.stringify(events,null,2)}
2359
- `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter2(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie agent list\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
2360
- `);else{if(lastFollowKind!==null&&!(lastFollowKind==="tool_call"&&event.kind==="tool_call"))process.stdout.write(`
2361
- `);process.stdout.write(`${formatEventBlock(event)}
2362
- `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);label=`team:${options.team}${agents.length>0?` (${agents.length} agents)`:""}`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie agent list\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="pg"?"Postgres event log":handle.mode} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown3=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown3),process.on("SIGTERM",shutdown3)}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(DISTINCT agent_id)::int as cnt FROM executors WHERE state NOT IN ('done', 'error', 'terminated')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
2406
+ ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVersion)console.error("Usage: genie update <name>[@version] or genie update --all"),process.exit(1);let name=nameOrVersion,version,atIdx=name.lastIndexOf("@");if(atIdx>0)version=name.slice(atIdx+1),name=name.slice(0,atIdx);if(!await handleUpdateSingle(name,version))process.exit(1)}function registerItemUpdateCommand(parent){parent.command("update [name]").description("Update an installed item to the latest version or a specific tag").option("--all","Update all git-installed items").action(async(name,options)=>{try{await handleUpdate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(DISTINCT agent_id)::int as cnt FROM executors WHERE state NOT IN ('done', 'error', 'terminated')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
2363
2407
  SELECT active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at
2364
2408
  FROM machine_snapshots
2365
2409
  WHERE created_at >= ${sinceTs}::timestamptz
@@ -2371,22 +2415,12 @@ ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push
2371
2415
  FROM heartbeats
2372
2416
  ORDER BY worker_id, created_at DESC
2373
2417
  `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No heartbeats found.");return}let headers=["Worker","Status","Last Seen","Team","Wish"],data=rows.map((r)=>{let ctx=typeof r.context==="string"?JSON.parse(r.context):r.context??{};return[String(r.worker_id??"-").slice(0,20),r.status??"-",formatRelativeTimestamp(r.last_seen_at),ctx.team??"-",ctx.wish_slug??"-"]}),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(25,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));console.log(`
2374
- (${rows.length} worker${rows.length===1?"":"s"})`)}function registerMetricsCommands(program2){let metrics=program2.command("metrics").description("Machine metrics \u2014 snapshots, heartbeats, agents");metrics.command("now",{isDefault:!0}).description("Current machine state").option("--json","Output as JSON").action(async(options)=>{await metricsNowCommand(options)}),metrics.command("history").description("Machine snapshot history").option("--since <duration>","Time window (e.g., 1h, 6h, 1d)","1h").option("--json","Output as JSON").action(async(options)=>{await metricsHistoryCommand(options)}),metrics.command("agents").description("Per-agent heartbeat summary").option("--json","Output as JSON").action(async(options)=>{await metricsAgentsCommand(options)})}import{readFile as readFile9}from"fs/promises";import{homedir as homedir28}from"os";import{join as join48}from"path";var _registry;async function getRegistry(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}var _taskService2;async function getTaskService2(){if(!_taskService2)_taskService2=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService2}var _teamManager;async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}var _mailbox;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??join48(homedir28(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join48(configDir,"teams",sanitized,"config.json");try{let raw=await readFile9(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 teamManager=await getTeamManager(),teamName=teamContext??process.env.GENIE_TEAM;if(teamName){let config=await teamManager.getTeam(teamName);if(config?.leader)return config.leader}return"team-lead"}async function checkSendScope(_repoPath,sender,recipient){if(sender==="cli")return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function resolveSenderTeams(teams,sender){let senderTeams=teams.filter((t)=>t.members.includes(sender));if(sender==="team-lead"||teams.some((t)=>t.leader===sender)){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!senderTeams.some((t)=>t.name===leaderTeam.name))senderTeams=[...senderTeams,leaderTeam]}}return senderTeams}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient==="team-lead"||recipient===team.leader)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 resolveTeamName(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 getTaskService2(),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?truncate(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 getTaskService2(),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=truncate(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(`
2375
- ${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService2(),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 getTaskService2(),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 deliverToTeam2(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 deliverToTeam2(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam2(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}async function handleSend(body,options){let ts3=await getTaskService2(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team-lead)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").action(async(body,options)=>{try{await handleSend(body,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),program2.command("broadcast <body>").description("Send a message to your team conversation (PG-backed)").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Team name (auto-detected from context)").action(async(body,options)=>{try{let ts3=await getTaskService2(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(),teamName=await resolveTeamName(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let inbox2=program2.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages (PG-backed)").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
2376
- `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result},warn:(msg)=>console.log(msg)}),shutdown3=()=>{console.log(`
2377
- Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown3),process.on("SIGTERM",shutdown3),await new Promise(()=>{})});let chat=program2.command("chat").description("Conversation management (PG-backed)");chat.command("send <conversationId> <message>").description("Send a message to a specific conversation").option("--reply-to <msgId>","Reply to a specific message ID").option("--from <sender>","Sender ID (auto-detected)").action(async(conversationId,message,options)=>{try{let ts3=await getTaskService2(),from=options.from??await detectSenderIdentity(),actor=localActor(from),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.sendMessage(conversationId,actor,message,replyTo);console.log(`Message #${msg.id} sent to conversation ${conversationId}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("thread <messageId>").description("Create a threaded sub-conversation from a message").option("--name <name>","Thread name").option("--from <sender>","Sender ID (auto-detected)").action(async(messageId,options)=>{try{await handleChatThread(messageId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("list").description("List conversations with filters").option("--type <type>","Filter by type: dm, group").option("--linked <entity>","Filter by linked entity: task, team").option("--json","Output as JSON").option("--from <sender>","Actor ID (auto-detected)").action(async(options)=>{try{await handleChatList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),chat.command("read <conversationId>").description("Read messages in a conversation").option("--since <timestamp>","Show messages since timestamp").option("--limit <n>","Limit number of messages","50").option("--json","Output as JSON").action(async(conversationId,options)=>{try{await handleChatRead(conversationId,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}function currentActor(){return{actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"}}async function handleNotifySet(options){let ts3=await getTaskService3(),actor=currentActor(),pref=await ts3.setPreference(actor,options.channel,{priorityThreshold:options.priority,isDefault:options.default}),defaultLabel=pref.isDefault?", default":"";console.log(`Notification preference set: ${pref.channel} (threshold: ${pref.priorityThreshold}${defaultLabel}).`)}function printPrefsTable(prefs){console.log(` ${padRight("CHANNEL",15)} ${padRight("THRESHOLD",12)} ${padRight("DEFAULT",10)} ENABLED`),console.log(` ${"\u2500".repeat(45)}`);for(let p of prefs){let dflt=p.isDefault?"yes":"no",enabled=p.enabled?"yes":"no";console.log(` ${padRight(p.channel,15)} ${padRight(p.priorityThreshold,12)} ${padRight(dflt,10)} ${enabled}`)}console.log(`
2378
- ${prefs.length} preference${prefs.length===1?"":"s"}`)}async function handleNotifyList(options){let ts3=await getTaskService3(),actor=currentActor(),prefs=await ts3.getPreferences(actor);if(options.json){console.log(JSON.stringify(prefs,null,2));return}if(prefs.length===0){console.log("No notification preferences configured.");return}printPrefsTable(prefs)}async function handleNotifyRemove(options){let ts3=await getTaskService3(),actor=currentActor();if(await ts3.deletePreference(actor,options.channel))console.log(`Removed notification preference for channel: ${options.channel}`);else console.log(`No preference found for channel: ${options.channel}`)}function registerNotifyCommands(program2){let notify=program2.command("notify").description("Notification preference management");notify.command("set").description("Set notification preference for a channel").requiredOption("--channel <channel>","Channel: whatsapp, telegram, email, slack, discord, tmux").option("--priority <priority>","Minimum priority threshold","normal").option("--default","Set as default channel").action(async(options)=>{try{await handleNotifySet(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("list").description("List notification preferences").option("--json","Output as JSON").action(async(options)=>{try{await handleNotifyList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("remove").description("Remove a notification preference").requiredOption("--channel <channel>","Channel to remove").action(async(options)=>{try{await handleNotifyRemove(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_tmux();function debug(msg){if(process.env.DEBUG)console.error(`[target-resolver] ${msg}`)}async function defaultTmuxLookup(sessionName,windowName){try{let tmux=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),session=await tmux.findSessionByName(sessionName);if(!session)return null;let windows=await tmux.listWindows(session.id);if(!windows||windows.length===0)return null;let targetWindow;if(windowName){if(targetWindow=windows.find((w)=>w.name===windowName),!targetWindow)return null}else targetWindow=windows.find((w)=>w.active)||windows[0];let panes=await tmux.listPanes(targetWindow.id);if(!panes||panes.length===0)return null;return{paneId:(panes.find((p)=>p.active)||panes[0]).id,session:sessionName}}catch{return null}}async function defaultIsPaneLive(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{pane_id}'`)).trim()===paneId}catch{return!1}}async function defaultCleanupDeadPane(workerId,paneId){try{await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).removeSubPane(workerId,paneId)}catch{}}async function defaultDeriveSession(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{session_name}'`)).trim()||null}catch{return null}}async function assertLive(paneId,isPaneLive,errorMsg,cleanup){if(!await isPaneLive(paneId)){if(cleanup)await cleanup();throw Error(errorMsg)}}async function resolveRawPane(target,opts){if(opts.checkLiveness)await assertLive(target,opts.isPaneLive,`Pane ${target} is dead or does not exist. Check with: tmux list-panes -a`);let session=await opts.deriveSession(target);return{paneId:target,session:session??void 0,resolvedVia:"raw"}}async function resolveWindowId(target,workers,opts){let matchingWorker=Object.values(workers).find((w)=>w.windowId===target);if(!matchingWorker)throw Error(`Window "${target}" not found in worker registry.
2379
- Run 'genie agent list' to list agents.`);if(opts.checkLiveness)await assertLive(matchingWorker.paneId,opts.isPaneLive,`Window ${target}: worker ${matchingWorker.id} pane ${matchingWorker.paneId} is dead. Run 'genie agent kill${matchingWorker.id}' to clean up.`);return{paneId:matchingWorker.paneId,session:matchingWorker.session,workerId:matchingWorker.id,resolvedVia:"worker"}}function resolveWorkerSubPane(worker,leftSide,rightSide){let index=Number.parseInt(rightSide,10);if(Number.isNaN(index)||index<0)throw Error(`Invalid sub-pane index "${rightSide}" for worker "${leftSide}". Use a non-negative integer (0 = primary, 1+ = sub-panes).`);let paneId=getPaneByIndex(worker,index);if(!paneId){let maxIndex=worker.subPanes?worker.subPanes.length:0;throw Error(`Worker "${leftSide}" has no sub-pane index ${index}. Available: 0 (primary)${maxIndex>0?`, 1-${maxIndex} (sub-panes)`:""}. Sub-pane index ${index} does not exist.`)}return paneId}function pickUnique(target,candidates,label){if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 ${label}: ${ids}
2380
- Use the full ID instead.`)}function resolveByRole(target,workers,currentTeam){if(!currentTeam)return null;let candidates=Object.entries(workers).filter(([,w])=>w.role===target&&w.team===currentTeam);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}" in team "${currentTeam}"`)}function resolveByCustomName(target,workers,currentTeam){if(currentTeam){let teamCandidates=Object.entries(workers).filter(([,w])=>w.customName===target&&w.team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(([,w])=>w.customName===target);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName "${target}"`)}function resolveByPartialId(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&id.endsWith(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
2381
- Use the full ID instead.`)}function resolveBySubstring(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&!id.endsWith(target)&&id.includes(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
2382
- Use the full ID instead.`)}function resolveByRoleGlobal(target,workers){let candidates=Object.entries(workers).filter(([,w])=>w.role===target);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}"`)}function resolveByPartialRole(target,workers,currentTeam){let matches=([,w])=>w.role!==void 0&&w.role!==target&&w.role.startsWith(target);if(currentTeam){let teamCandidates=Object.entries(workers).filter((e)=>matches(e)&&e[1].team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with role starting with "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(matches);return pickUnique(target,allCandidates,`${allCandidates.length} workers with role starting with "${target}"`)}function resolveByPartialCustomName(target,workers,currentTeam){let matches=([,w])=>w.customName!==void 0&&w.customName!==target&&w.customName.startsWith(target);if(currentTeam){let teamCandidates=Object.entries(workers).filter((e)=>matches(e)&&e[1].team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName starting with "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(matches);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName starting with "${target}"`)}async function resolveColonTarget(target,workers,opts){let colonIndex=target.indexOf(":"),leftSide=target.substring(0,colonIndex),rightSide=target.substring(colonIndex+1),worker=workers[leftSide];if(worker){let paneId=resolveWorkerSubPane(worker,leftSide,rightSide),index=Number.parseInt(rightSide,10);if(opts.checkLiveness)await assertLive(paneId,opts.isPaneLive,`Worker ${leftSide}: pane ${paneId} is dead. Run 'genie agent kill${leftSide}' to clean up.`,()=>opts.cleanupDeadPane(leftSide,paneId));return{paneId,session:worker.session,workerId:leftSide,paneIndex:index,resolvedVia:"worker"}}let sessionWindowResult=await opts.tmuxLookup(leftSide,rightSide);if(!sessionWindowResult)throw Error(`Target "${target}" not found. No worker "${leftSide}" in registry and no tmux session:window "${leftSide}:${rightSide}" found.
2383
- Run 'genie agent list' to list agents.`);if(opts.checkLiveness)await assertLive(sessionWindowResult.paneId,opts.isPaneLive,`Session "${leftSide}" window "${rightSide}": pane ${sessionWindowResult.paneId} is dead.`);return{paneId:sessionWindowResult.paneId,session:sessionWindowResult.session,resolvedVia:"session:window"}}async function resolveBareName(target,workers,opts){let worker=workers[target];if(worker){if(opts.checkLiveness)await assertLive(worker.paneId,opts.isPaneLive,`Worker ${target}: pane ${worker.paneId} is dead. Run 'genie agent kill${target}' to clean up.`,()=>opts.cleanupDeadPane(target,worker.paneId));return{paneId:worker.paneId,session:worker.session,workerId:target,resolvedVia:"worker"}}let currentTeam=opts.getCurrentTeam?await opts.getCurrentTeam():await getCurrentSessionName()??process.env.GENIE_TEAM??null,fuzzyMatch=resolveByRole(target,workers,currentTeam)??resolveByCustomName(target,workers,currentTeam)??resolveByPartialId(target,workers,currentTeam)??resolveBySubstring(target,workers,currentTeam)??resolveByRoleGlobal(target,workers)??resolveByPartialRole(target,workers,currentTeam)??resolveByPartialCustomName(target,workers,currentTeam);if(fuzzyMatch){let rid=fuzzyMatch.workerId??target;if(opts.checkLiveness)await assertLive(fuzzyMatch.paneId,opts.isPaneLive,`Worker ${rid}: pane ${fuzzyMatch.paneId} is dead.`,()=>opts.cleanupDeadPane(rid,fuzzyMatch.paneId));return fuzzyMatch}throw Error(`Target "${target}" not found. Not a worker or pane ID.
2384
- Run 'genie agent list' to list agents.`)}async function resolveTarget(target,options={}){let{checkLiveness=!1,workers:injectedWorkers,tmuxLookup=defaultTmuxLookup,isPaneLive=defaultIsPaneLive,cleanupDeadPane=defaultCleanupDeadPane,deriveSession=defaultDeriveSession}=options;if(debug(`resolving "${target}"`),target.startsWith("%"))return resolveRawPane(target,{checkLiveness,isPaneLive,deriveSession});if(target.startsWith("@")){let workers2=await getWorkers(injectedWorkers,options.registryPath);return resolveWindowId(target,workers2,{checkLiveness,isPaneLive})}let workers=await getWorkers(injectedWorkers,options.registryPath);if(target.indexOf(":")!==-1)return resolveColonTarget(target,workers,{checkLiveness,isPaneLive,cleanupDeadPane,tmuxLookup});return resolveBareName(target,workers,{checkLiveness,isPaneLive,cleanupDeadPane,getCurrentTeam:options.getCurrentTeam})}function formatResolvedLabel(resolved,originalTarget){let parts=[];if(resolved.workerId){if(parts.push(resolved.workerId),resolved.paneIndex!==void 0&&resolved.paneIndex>0)parts[parts.length-1]+=`:${resolved.paneIndex}`}else parts.push(originalTarget);let details=[`pane ${resolved.paneId}`];if(resolved.session)details.push(`session ${resolved.session}`);return`${parts[0]} (${details.join(", ")})`}async function getWorkers(injected,_registryPath){if(injected!==void 0)return injected;try{let workersList=await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).list(),map2={};for(let w of workersList)map2[w.id]=w;return map2}catch{return{}}}function getPaneByIndex(worker,index){if(index===0)return worker.paneId;let subIndex=index-1;if(!worker.subPanes||subIndex>=worker.subPanes.length||subIndex<0)return null;return worker.subPanes[subIndex]}init_tmux();init_orchestrator();async function resolveOrcTarget(target){let resolved=await resolveTarget(target);return{paneId:resolved.paneId,session:resolved.session||target,label:formatResolvedLabel(resolved,target)}}async function sendTextChoice(paneId,text){await executeTmux2(`send-keys -t '${paneId}' End`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' ${shellEscape(text)}`),await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}function findCurrentOption(output){let lines=stripAnsi(output).split(`
2385
- `);for(let line of lines){let match=line.match(/^\s*\u276F\s*(\d+)\./);if(match)return Number.parseInt(match[1],10)}return 1}async function navigateToOption(paneId,targetOption,currentOption){let diff=targetOption-currentOption,key=diff>0?"Down":"Up";for(let i2=0;i2<Math.abs(diff);i2++)await executeTmux2(`send-keys -t '${paneId}' ${key}`),await sleep2(50);await sleep2(100),await executeTmux2(`send-keys -t '${paneId}' Enter`)}async function answerQuestion(target,choice){try{let{paneId,label}=await resolveOrcTarget(target),output=await capturePaneContent(paneId,50),state=detectState(output);if(state.type!=="question"){console.log(`No question pending (state: ${state.type})`);return}if(choice.startsWith("text:")){let text=choice.slice(5);await sendTextChoice(paneId,text),console.log(`Sent feedback: "${text.substring(0,50)}${text.length>50?"...":""}"`)}else if(/^\d+$/.test(choice)){let targetOption=Number.parseInt(choice,10);await navigateToOption(paneId,targetOption,findCurrentOption(output)),console.log(`Selected option ${targetOption} for ${label}`)}else await executeTmux2(`send-keys -t '${paneId}' '${choice}'`),console.log(`Sent '${choice}' to ${label}`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}function shellEscape(str5){return`"${str5.replace(/"/g,"\\\"").replace(/\$/g,"\\$")}"`}function sleep2(ms){return new Promise((resolve6)=>setTimeout(resolve6,ms))}var _taskService4;async function getTaskService4(){if(!_taskService4)_taskService4=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService4}async function printProjectList(ts3,projects){let counts={};for(let p of projects){let tasks=await ts3.listTasks({projectName:p.name,allProjects:!0});counts[p.id]=tasks.length}console.log(` ${padRight("NAME",20)} ${padRight("TYPE",10)} ${padRight("TASKS",8)} ${padRight("CREATED",12)} PATH`),console.log(` ${"\u2500".repeat(80)}`);for(let p of projects){let type2=p.repoPath?"repo":"virtual",path2=p.repoPath?truncate(p.repoPath,40):"-";console.log(` ${padRight(p.name,20)} ${padRight(type2,10)} ${padRight(String(counts[p.id]??0),8)} ${padRight(formatDate(p.createdAt),12)} ${path2}`)}console.log(`
2418
+ (${rows.length} worker${rows.length===1?"":"s"})`)}function registerMetricsCommands(program2){let metrics=program2.command("metrics").description("Machine metrics \u2014 snapshots, heartbeats, agents");metrics.command("now",{isDefault:!0}).description("Current machine state").option("--json","Output as JSON").action(async(options)=>{await metricsNowCommand(options)}),metrics.command("history").description("Machine snapshot history").option("--since <duration>","Time window (e.g., 1h, 6h, 1d)","1h").option("--json","Output as JSON").action(async(options)=>{await metricsHistoryCommand(options)}),metrics.command("agents").description("Per-agent heartbeat summary").option("--json","Output as JSON").action(async(options)=>{await metricsAgentsCommand(options)})}init_msg();var _taskService4;async function getTaskService4(){if(!_taskService4)_taskService4=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService4}function currentActor(){return{actorType:"local",actorId:process.env.GENIE_AGENT_NAME??"cli"}}async function handleNotifySet(options){let ts3=await getTaskService4(),actor=currentActor(),pref=await ts3.setPreference(actor,options.channel,{priorityThreshold:options.priority,isDefault:options.default}),defaultLabel=pref.isDefault?", default":"";console.log(`Notification preference set: ${pref.channel} (threshold: ${pref.priorityThreshold}${defaultLabel}).`)}function printPrefsTable(prefs){console.log(` ${padRight("CHANNEL",15)} ${padRight("THRESHOLD",12)} ${padRight("DEFAULT",10)} ENABLED`),console.log(` ${"\u2500".repeat(45)}`);for(let p of prefs){let dflt=p.isDefault?"yes":"no",enabled=p.enabled?"yes":"no";console.log(` ${padRight(p.channel,15)} ${padRight(p.priorityThreshold,12)} ${padRight(dflt,10)} ${enabled}`)}console.log(`
2419
+ ${prefs.length} preference${prefs.length===1?"":"s"}`)}async function handleNotifyList(options){let ts3=await getTaskService4(),actor=currentActor(),prefs=await ts3.getPreferences(actor);if(options.json){console.log(JSON.stringify(prefs,null,2));return}if(prefs.length===0){console.log("No notification preferences configured.");return}printPrefsTable(prefs)}async function handleNotifyRemove(options){let ts3=await getTaskService4(),actor=currentActor();if(await ts3.deletePreference(actor,options.channel))console.log(`Removed notification preference for channel: ${options.channel}`);else console.log(`No preference found for channel: ${options.channel}`)}function registerNotifyCommands(program2){let notify=program2.command("notify").description("Notification preference management");notify.command("set").description("Set notification preference for a channel").requiredOption("--channel <channel>","Channel: whatsapp, telegram, email, slack, discord, tmux").option("--priority <priority>","Minimum priority threshold","normal").option("--default","Set as default channel").action(async(options)=>{try{await handleNotifySet(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("list").description("List notification preferences").option("--json","Output as JSON").action(async(options)=>{try{await handleNotifyList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),notify.command("remove").description("Remove a notification preference").requiredOption("--channel <channel>","Channel to remove").action(async(options)=>{try{await handleNotifyRemove(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService5;async function getTaskService5(){if(!_taskService5)_taskService5=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService5}async function printProjectList(ts3,projects){let counts={};for(let p of projects){let tasks=await ts3.listTasks({projectName:p.name,allProjects:!0});counts[p.id]=tasks.length}console.log(` ${padRight("NAME",20)} ${padRight("TYPE",10)} ${padRight("TASKS",8)} ${padRight("CREATED",12)} PATH`),console.log(` ${"\u2500".repeat(80)}`);for(let p of projects){let type2=p.repoPath?"repo":"virtual",path2=p.repoPath?truncate2(p.repoPath,40):"-";console.log(` ${padRight(p.name,20)} ${padRight(type2,10)} ${padRight(String(counts[p.id]??0),8)} ${padRight(formatDate(p.createdAt),12)} ${path2}`)}console.log(`
2386
2420
  ${projects.length} project${projects.length===1?"":"s"}`)}function printProjectDetail(p,tasks){let byStatus={},byStage={};for(let t of tasks)byStatus[t.status]=(byStatus[t.status]??0)+1,byStage[t.stage]=(byStage[t.stage]??0)+1;if(console.log(`
2387
2421
  Project: ${p.name}`),console.log("\u2500".repeat(50)),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`);if(console.log(` Created: ${formatDate(p.createdAt)}`),console.log(` Tasks: ${tasks.length}`),tasks.length>0){console.log(`
2388
2422
  By status:`);for(let[status,count]of Object.entries(byStatus).sort())console.log(` ${padRight(status,15)} ${count}`);console.log(`
2389
- By stage:`);for(let[stage,count]of Object.entries(byStage).sort())console.log(` ${padRight(stage,15)} ${count}`)}}function registerProjectCommands(program2){let project=program2.command("project").description("Project management \u2014 named task boards");project.command("list").description("List all projects").option("--all","Include archived projects").option("--json","Output as JSON").action(async(options)=>{try{let ts3=await getTaskService4(),projects=await ts3.listProjectsFiltered(options.all);if(options.json){console.log(JSON.stringify(projects,null,2));return}if(projects.length===0){console.log("No projects found. Projects are auto-created when you run `genie task create`.");return}await printProjectList(ts3,projects)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("create <name>").description("Create a new project").option("--virtual","Create a virtual project (not tied to a repo)").option("--repo <path>","Repo path for the project").option("--description <text>","Project description").action(async(name,options)=>{try{let ts3=await getTaskService4(),repoPath=options.virtual?null:options.repo??null,p=await ts3.createProject({name,repoPath,description:options.description});if(console.log(`Created project "${p.name}"`),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("show <name>").description("Show project detail with task stats").option("--json","Output as JSON").action(async(name,options)=>{try{let ts3=await getTaskService4(),p=await ts3.getProjectByName(name);if(!p){console.error(`Error: Project not found: ${name}`),process.exit(1);return}if(options.json){console.log(JSON.stringify(p,null,2));return}let tasks=await ts3.listTasks({projectName:name,allProjects:!0});printProjectDetail(p,tasks)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("archive <name>").description("Archive a project (cascades to boards and unfinished tasks)").action(async(name)=>{try{await(await getTaskService4()).archiveProject(name),console.log(`Archived project "${name}" and cascaded to boards + unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("unarchive <name>").description("Restore an archived project and its boards (tasks stay as-is)").action(async(name)=>{try{await(await getTaskService4()).unarchiveProject(name),console.log(`Unarchived project "${name}" and restored boards.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("set-default <name>").description("Set default project for when outside any repo").action(async(name)=>{try{if(!await(await getTaskService4()).getProjectByName(name))console.error(`Error: Project not found: ${name}`),process.exit(1);let{loadGenieConfig:loadGenieConfig2,saveGenieConfig:saveGenieConfig2}=await Promise.resolve().then(() => (init_genie_config2(),exports_genie_config)),config=await loadGenieConfig2();config.defaultProject=name,await saveGenieConfig2(config),console.log(`Default project set to "${name}"`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{execSync as execSync14}from"child_process";function getGitRemoteTags(cwd){try{return execSync14("git tag --list --merged HEAD",{cwd,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim().split(`
2423
+ By stage:`);for(let[stage,count]of Object.entries(byStage).sort())console.log(` ${padRight(stage,15)} ${count}`)}}function registerProjectCommands(program2){let project=program2.command("project").description("Project management \u2014 named task boards");project.command("list").description("List all projects").option("--all","Include archived projects").option("--json","Output as JSON").action(async(options)=>{try{let ts3=await getTaskService5(),projects=await ts3.listProjectsFiltered(options.all);if(options.json){console.log(JSON.stringify(projects,null,2));return}if(projects.length===0){console.log("No projects found. Projects are auto-created when you run `genie task create`.");return}await printProjectList(ts3,projects)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("create <name>").description("Create a new project").option("--virtual","Create a virtual project (not tied to a repo)").option("--repo <path>","Repo path for the project").option("--description <text>","Project description").action(async(name,options)=>{try{let ts3=await getTaskService5(),repoPath=options.virtual?null:options.repo??null,p=await ts3.createProject({name,repoPath,description:options.description});if(console.log(`Created project "${p.name}"`),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("show <name>").description("Show project detail with task stats").option("--json","Output as JSON").action(async(name,options)=>{try{let ts3=await getTaskService5(),p=await ts3.getProjectByName(name);if(!p){console.error(`Error: Project not found: ${name}`),process.exit(1);return}if(options.json){console.log(JSON.stringify(p,null,2));return}let tasks=await ts3.listTasks({projectName:name,allProjects:!0});printProjectDetail(p,tasks)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("archive <name>").description("Archive a project (cascades to boards and unfinished tasks)").action(async(name)=>{try{await(await getTaskService5()).archiveProject(name),console.log(`Archived project "${name}" and cascaded to boards + unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("unarchive <name>").description("Restore an archived project and its boards (tasks stay as-is)").action(async(name)=>{try{await(await getTaskService5()).unarchiveProject(name),console.log(`Unarchived project "${name}" and restored boards.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("set-default <name>").description("Set default project for when outside any repo").action(async(name)=>{try{if(!await(await getTaskService5()).getProjectByName(name))console.error(`Error: Project not found: ${name}`),process.exit(1);let{loadGenieConfig:loadGenieConfig2,saveGenieConfig:saveGenieConfig2}=await Promise.resolve().then(() => (init_genie_config2(),exports_genie_config)),config=await loadGenieConfig2();config.defaultProject=name,await saveGenieConfig2(config),console.log(`Default project set to "${name}"`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_cache();init_audit();init_db();import{execSync as execSync14}from"child_process";function getGitRemoteTags(cwd){try{return execSync14("git tag --list --merged HEAD",{cwd,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim().split(`
2390
2424
  `).filter(Boolean)}catch{return[]}}function isTagPushed(tag,cwd){try{return execSync14(`git ls-remote --tags origin refs/tags/${tag}`,{cwd,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}),!0}catch{return!1}}function getGitSha(cwd){try{return execSync14("git rev-parse HEAD",{cwd,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}async function handlePublish(){let cwd=process.cwd(),detection=await detectManifest(cwd);if("error"in detection)console.error(`Cannot publish: ${detection.error}`),process.exit(1);let{manifest,source}=detection,validation=validateManifest(manifest,cwd);for(let w of validation.warnings)console.log(` Warning: ${w}`);if(!validation.valid)console.error(`Validation failed:
2391
2425
  ${validation.errors.map((e)=>` - ${e}`).join(`
2392
2426
  `)}`),process.exit(1);let expectedTag=`v${manifest.version}`,tags=getGitRemoteTags(cwd);if(!(tags.includes(expectedTag)||tags.includes(manifest.version)))console.error(`No git tag "${expectedTag}" found. Create and push a tag first:`),console.error(` git tag ${expectedTag} && git push origin ${expectedTag}`),process.exit(1);let tagToPush=tags.includes(expectedTag)?expectedTag:manifest.version;if(!isTagPushed(tagToPush,cwd))console.error(`Tag "${tagToPush}" exists locally but is not pushed to remote.`),console.error(` git push origin ${tagToPush}`),process.exit(1);let gitSha=getGitSha(cwd);if(await getItemFromStore(manifest.name).catch(()=>null)){if(await updateItemInStore(manifest.name,{version:manifest.version,description:manifest.description,manifest}),await isAvailable())await(await getConnection())`
@@ -2487,19 +2521,7 @@ Team: ${teamName}
2487
2521
  `}function formatSetupInstructions(spec,teamName,genieCmd,followFile,followPidFile,followSinceFile){return spec.setup.map((s)=>{if(s.kind==="spawn"){let provider=s.options.provider||"claude";return`- Spawn agent: \`${genieCmd} spawn ${s.target} --provider ${provider} --team ${teamName}\``}if(s.kind==="follow")return`- Start detached runtime follow with this exact command (do not use Claude background tasks): \`date -u +"%Y-%m-%dT%H:%M:%SZ" > "${followSinceFile}" && nohup ${genieCmd} log --follow --team ${teamName} --ndjson > "${followFile}" 2>&1 < /dev/null & echo $! > "${followPidFile}" && sleep 2\``;return`- Unknown setup step: ${s.kind}`}).join(`
2488
2522
  `)}function buildQaCheckCommand(genieCmd,specFile,teamName,followSinceFile){return`${genieCmd} qa check "${specFile}" --team ${teamName} --since-file "${followSinceFile}"`}function formatActionInstructions(spec,teamName,genieCmd,qaCheckCmd){return spec.actions.map((a,i2)=>{let isFinalAction=i2===spec.actions.length-1;if(a.kind==="send")return`${i2+1}. Send message: \`${genieCmd} send '${a.message}' --to ${a.to} --team ${teamName}\``;if(a.kind==="wait"){if(isFinalAction)return`${i2+1}. Finalize in one command: \`sleep ${a.seconds??1} && ${qaCheckCmd}\``;return`${i2+1}. Wait ${a.seconds??1} seconds`}if(a.kind==="run")return`${i2+1}. Run command: \`${rewriteRunCommand(a.command??"",genieCmd)}\``;return`${i2+1}. Unknown action: ${a.kind}`}).join(`
2489
2523
  `)}function rewriteRunCommand(command,genieCmd){if(!command)return command;return command.replace(/(^|[;&|()\s])genie(?=\s|$)/g,`$1${genieCmd}`)}function formatSpecForPrompt(spec){return["### Setup",...spec.setup.map(formatSetupStep),"### Actions",...spec.actions.map(formatActionStep),"### Expectations",...spec.expect.map((e)=>`- [ ] ${e.description}`)].join(`
2490
- `)}function formatSetupStep(s){if(s.kind==="spawn"){let opts=Object.entries(s.options).map(([k,v])=>`${k}: ${v}`).join(", ");return`- spawn ${s.target}${opts?` (${opts})`:""}`}return`- follow ${s.target}`}function formatActionStep(a){if(a.kind==="send")return`- send "${a.message}" to ${a.to}`;if(a.kind==="wait")return`- wait ${a.seconds??1}s`;if(a.kind==="run")return`- run ${a.command}`;return`- ${a.kind}`}async function waitForResult(spec,repoPath,teamName,timeoutMs,start2){let subject=`genie.qa.${teamName}.result`,remainingMs=Math.max(timeoutMs-(Date.now()-start2),1),event=await waitForRuntimeEvent({repoPath,subject,team:teamName},remainingMs);if(!event)return makeErrorReport(spec,start2,`Timeout after ${timeoutMs}ms waiting for team-lead report in PG event log`);return parseTeamLeadReport(spec,event.data??{},start2)}function parseTeamLeadReport(spec,payload,start2){try{let data=payload;return{name:spec.name,file:spec.file,result:data.result==="pass"?"pass":"fail",expectations:data.expectations??[],collectedEvents:data.collectedEvents??[],durationMs:Date.now()-start2}}catch(err){return makeErrorReport(spec,start2,`Failed to parse team-lead report: ${err}`)}}function makeErrorReport(spec,start2,error2){return{name:spec.name,file:spec.file,result:"error",expectations:[],collectedEvents:[],durationMs:Date.now()-start2,error:error2}}function computeEffectiveTimeoutMs(spec,requestedTimeoutMs){let totalWaitMs=spec.actions.reduce((sum,action)=>sum+(action.kind==="wait"?(action.seconds??0)*1000:0),0),setupSpawnCount=spec.setup.filter((step)=>step.kind==="spawn").length,orchestrationSlackMs=Math.max(30000,Math.min(90000,15000+Math.floor(totalWaitMs/2)+setupSpawnCount*15000));return requestedTimeoutMs+orchestrationSlackMs}function defaultSpecDir(repoPath){return join50(resolve7(repoPath??process.cwd()),".genie","qa")}init_runtime_events();async function qaCommand(target,options){let specDir=defaultSpecDir(),runnerOpts={timeout:options.timeout??3600,parallel:options.parallel??5,verbose:options.verbose??!1,repoPath:process.cwd(),ndjson:options.ndjson??!1},reports;if(target)reports=await resolveAndRun(specDir,target,runnerOpts);else reports=await runAllSpecs(specDir,runnerOpts);if(reports.length===0){console.error(`\x1B[33mNo QA specs found for "${target??"all"}"\x1B[0m`),process.exitCode=1;return}if(options.ndjson)for(let report of reports)console.log(JSON.stringify(report));else printRunResults(reports,options.verbose??!1);if(!reports.every((r)=>r.result==="pass"))process.exitCode=1}async function qaCheckCommand(specFile,options){let team=options.team??process.env.GENIE_TEAM;if(!team){console.error("Error: QA team not set. Use --team <name> or run inside a QA worker."),process.exitCode=1;return}let repoPath=process.cwd(),spec=await parseQaSpec(specFile),since=options.since??(options.sinceFile?await readSinceValue(options.sinceFile):void 0),teamAgents=(await list()).filter((agent)=>agent.team===team),events=await readTeamLog(teamAgents,repoPath,team,buildQaCheckLogFilter(since)),expectations=evaluateExpectations(spec.expect,events),collectedEvents=toCollectedEvents(events),result=expectations.every((exp)=>exp.result==="pass")?"pass":"fail";await publishSubjectEvent(repoPath,`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${result}`,data:{result,expectations,collectedEvents},source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}async function qaStatusCommand(options){let specDir=defaultSpecDir(),repoPath=process.cwd(),specs=await listAllSpecs(specDir),results=await loadResults(repoPath);if(specs.length===0){if(options?.json)console.log(JSON.stringify({specs:[],summary:{total:0,pass:0,fail:0,stale:0,never:0}}));else console.error("\x1B[33mNo QA specs found.\x1B[0m");return}if(options?.json)await printJsonStatus(specs,results,repoPath);else await printHumanStatus(specs,results,repoPath)}async function printJsonStatus(specs,results,repoPath){let jsonSpecs=[];for(let spec of specs){let stored=results[spec.key],stale=stored?await isStale(repoPath,spec.key,spec.filePath):!1,status=!stored?"never":stale?"stale":stored.result;jsonSpecs.push({key:spec.key,domain:spec.domain,name:spec.name,status,durationMs:stored?.durationMs??null,lastRun:stored?.lastRun??null,expectations:stored?.expectations??[],error:stored?.error??null})}let counts={total:specs.length,pass:jsonSpecs.filter((s)=>s.status==="pass").length,fail:jsonSpecs.filter((s)=>s.status==="fail"||s.status==="error").length,stale:jsonSpecs.filter((s)=>s.status==="stale").length,never:jsonSpecs.filter((s)=>s.status==="never").length};console.log(JSON.stringify({specs:jsonSpecs,summary:counts}))}async function printHumanStatus(specs,results,repoPath){console.log(),console.log("\x1B[1m QA Status\x1B[0m"),console.log(" \x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),console.log();let currentDomain="",counts={pass:0,fail:0,stale:0,never:0};for(let spec of specs){if(spec.domain!==currentDomain)currentDomain=spec.domain,console.log(` \x1B[1m${currentDomain}/\x1B[0m`);let stored=results[spec.key],stale=stored?await isStale(repoPath,spec.key,spec.filePath):!1;console.log(formatStatusLine(spec,stored,stale)),tallyResult(counts,stored,stale)}printStatusSummary(specs.length,counts)}function tallyResult(counts,stored,stale){if(!stored)counts.never++;else if(stale)counts.stale++;else if(stored.result==="pass")counts.pass++;else counts.fail++}function printStatusSummary(total,counts){console.log();let parts=[`${counts.pass}/${total} pass`,counts.fail?`${counts.fail} fail`:"",counts.stale?`${counts.stale} stale`:"",counts.never?`${counts.never} never`:""].filter(Boolean);console.log(` ${parts.join(" | ")}`),console.log()}async function readSinceValue(path2){try{return(await readFile12(path2,"utf-8")).trim()||void 0}catch{return}}function evaluateExpectations(expectations,events){return expectations.map((expectation)=>{let matched=events.filter((event)=>eventMatchesExpectationSource(event,expectation.source)).find((event)=>eventMatchesExpectation(event,expectation.matchers));if(matched)return{description:expectation.description,result:"pass",evidence:`${matched.kind} ${matched.agent}: ${matched.text.slice(0,120)}`};return{description:expectation.description,result:"fail",reason:"No matching event found in team log/transcript snapshot"}})}function eventMatchesExpectation(event,matchers){return Object.entries(matchers).every(([field,expected])=>matcherMatches(readEventField(event,field),expected,field))}function buildQaCheckLogFilter(since){if(since)return{since};return{last:200}}function eventMatchesExpectationSource(event,source){switch(source){case"inbox":return event.source==="mailbox"&&event.direction==="in";case"output":return event.source==="provider";default:return!0}}function readEventField(event,field){switch(field){case"timestamp":return event.timestamp;case"kind":return event.kind;case"agent":return event.agent;case"team":return event.team;case"direction":return event.direction;case"peer":return event.peer;case"text":return event.text;case"source":return event.source;default:break}return event.data?.[field]}function matcherMatches(actual,expected,field){if(actual==null)return!1;if(field==="kind"&&expected==="message")return["message","assistant","user"].includes(String(actual));let actualText=String(actual);if(expected.startsWith("~"))return actualText.includes(expected.slice(1));return actualText===expected}function toCollectedEvents(events){return events.slice(-50).map((event)=>({timestamp:event.timestamp,kind:event.kind,agent:event.agent,text:event.text}))}async function qaHistoryCommand(){let repoPath=process.cwd(),results=await loadResults(repoPath),entries=Object.entries(results).map(([key,r])=>({key,...r})).sort((a,b2)=>new Date(b2.lastRun).getTime()-new Date(a.lastRun).getTime());if(entries.length===0){console.error("\x1B[33mNo QA history found. Run `genie qa` first.\x1B[0m");return}console.log(),console.log("\x1B[1m QA History\x1B[0m"),console.log();let limit=20;for(let entry of entries.slice(0,limit)){let icon=resultIcon(entry.result),duration=entry.durationMs?`${(entry.durationMs/1000).toFixed(1)}s`:"-",ago=formatTimeAgo(entry.lastRun);console.log(` ${icon} ${entry.key.padEnd(35)} ${duration.padStart(6)} \x1B[2m${ago}\x1B[0m`)}if(entries.length>limit)console.log(` \x1B[2m... and ${entries.length-limit} more\x1B[0m`);console.log()}async function resolveAndRun(specDir,target,opts){let domainDir=join51(specDir,target);if(await isDirectory(domainDir))return runDomainSpecs(specDir,target,opts);let specPath=await resolveSpecPath(specDir,target);if(specPath){let spec=await parseQaSpec(specPath),key=specKeyFromPath(specDir,specPath),report=await runSpec(spec,{...opts,specKey:key}),repoPath=opts.repoPath??process.cwd();return await saveResult(repoPath,key,report),[report]}console.error(`\x1B[31mSpec or domain not found: ${target}\x1B[0m`),console.error("Available:");let allSpecs=await listAllSpecs(specDir),domains=[...new Set(allSpecs.map((s)=>s.domain))];for(let d of domains)console.error(` \x1B[1m${d}/\x1B[0m`);for(let s of allSpecs)console.error(` ${s.key}`);return process.exitCode=1,[]}async function resolveSpecPath(specDir,name){let candidates=[join51(specDir,name),join51(specDir,`${name}.md`)];for(let path2 of candidates)if(await isFile(path2))return path2;return null}function resultIcon(result){if(result==="pass")return"\x1B[32m\u2705\x1B[0m";if(result==="fail")return"\x1B[31m\u274C\x1B[0m";if(result==="error")return"\x1B[33m\u26A0\uFE0F\x1B[0m";return"\uD83D\uDD18"}function formatStatusLine(spec,stored,stale){if(!stored)return` \uD83D\uDD18 ${spec.name.padEnd(25)} ${"-".padStart(6)} \x1B[2mnever\x1B[0m`;if(stale){let ago2=formatTimeAgo(stored.lastRun);return` \u26A0\uFE0F ${spec.name.padEnd(25)} ${"-".padStart(6)} \x1B[33m${ago2} (stale)\x1B[0m`}let icon=resultIcon(stored.result),duration=stored.durationMs?`${(stored.durationMs/1000).toFixed(0)}s`:"-",ago=formatTimeAgo(stored.lastRun);return` ${icon} ${spec.name.padEnd(25)} ${duration.padStart(6)} \x1B[2m${ago}\x1B[0m`}function printRunResults(reports,verbose){console.log(),console.log("\x1B[1m QA Results\x1B[0m"),console.log();for(let report of reports)printReport(report,verbose);let passed=reports.filter((r)=>r.result==="pass").length,failed=reports.filter((r)=>r.result==="fail").length,errors3=reports.filter((r)=>r.result==="error").length,total=reports.length,color=failed+errors3>0?"\x1B[31m":"\x1B[32m",failedStr=failed?` \x1B[31m${failed} failed\x1B[0m`:"",errorsStr=errors3?` \x1B[33m${errors3} errors\x1B[0m`:"";console.log(` ${color}${passed}/${total} passed\x1B[0m${failedStr}${errorsStr}`),console.log()}function printReport(report,verbose){let duration=`${(report.durationMs/1000).toFixed(1)}s`;if(console.log(` ${resultIcon(report.result)} ${report.name} \x1B[2m(${duration})\x1B[0m`),report.error)console.log(` \x1B[31mError: ${report.error}\x1B[0m`);for(let exp of report.expectations)printExpectation(exp);if(verbose&&report.collectedEvents.length>0){console.log(` \x1B[2m--- Collected events (${report.collectedEvents.length}) ---\x1B[0m`);for(let event of report.collectedEvents)console.log(` \x1B[2m ${event.timestamp} ${event.kind} ${event.agent}: ${event.text.slice(0,80)}\x1B[0m`)}console.log()}function printExpectation(exp){let icon=exp.result==="pass"?"\x1B[32m\u2713\x1B[0m":"\x1B[31m\u2717\x1B[0m";if(console.log(` ${icon} ${exp.description}`),exp.result==="pass"&&exp.evidence)console.log(` \x1B[2m${exp.evidence}\x1B[0m`);if(exp.result==="fail"&&exp.reason)console.log(` \x1B[31m${exp.reason}\x1B[0m`)}async function isDirectory(path2){try{return(await stat5(path2)).isDirectory()}catch{return!1}}async function isFile(path2){try{return(await stat5(path2)).isFile()}catch{return!1}}init_genie_config2();init_tmux();function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
2491
- `).filter((line)=>!isTmuxMarkerOrNoise(line));while(filtered.length>0&&filtered[0].trim()==="")filtered.shift();while(filtered.length>0&&filtered[filtered.length-1].trim()==="")filtered.pop();return filtered.join(`
2492
- `)}async function resolveActivePaneId(sessionName,session){let windows=await listWindows(session.id);if(!windows||windows.length===0)throw Error(`No windows found in session "${sessionName}"`);let activeWindow=windows.find((w)=>w.active)||windows[0],panes=await listPanes(activeWindow.id);if(!panes||panes.length===0)throw Error(`No panes found in session "${sessionName}"`);return(panes.find((p)=>p.active)||panes[0]).id}function maybeReverse(content,reverse){return reverse?content.split(`
2493
- `).reverse().join(`
2494
- `):content}function readRange(paneContent,from,to,reverse){let lines=stripTmuxMarkers(paneContent).split(`
2495
- `);return maybeReverse(lines.slice(from,to+1).join(`
2496
- `),reverse)}function searchContent(paneContent,pattern,reverse){let lines=stripTmuxMarkers(paneContent).split(`
2497
- `);try{let regex=new RegExp(pattern,"i"),matched=lines.filter((line)=>regex.test(line));return maybeReverse(matched.join(`
2498
- `),reverse)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);throw Error(`Invalid regex pattern: ${message}`)}}async function readSessionLogs(sessionName,options={}){let paneId=options.pane?options.pane.startsWith("%")?options.pane:`%${options.pane}`:await(async()=>{let session=await findSessionByName(sessionName);if(!session)throw Error(`Session "${sessionName}" not found`);return resolveActivePaneId(sessionName,session)})();if(options.range){let parts=options.range.split(":");if(parts.length===2)options.from=Number.parseInt(parts[0],10),options.to=Number.parseInt(parts[1],10)}if(options.all)return stripTmuxMarkers(await capturePaneContent(paneId,1e4));if(options.from!==void 0&&options.to!==void 0)return readRange(await capturePaneContent(paneId,1e4),options.from,options.to,options.reverse);if(options.search||options.grep){let pattern=options.search??options.grep??"";return searchContent(await capturePaneContent(paneId,1e4),pattern,options.reverse)}let content=stripTmuxMarkers(await capturePaneContent(paneId,options.lines||100));return maybeReverse(content,options.reverse)}async function followSessionLogs(sessionName,callback,options={}){let paneId;if(options.pane)paneId=options.pane.startsWith("%")?options.pane:`%${options.pane}`;else{let session=await findSessionByName(sessionName);if(!session)throw Error(`Session "${sessionName}" not found`);let windows=await listWindows(session.id);if(!windows||windows.length===0)throw Error(`No windows found in session "${sessionName}"`);let activeWindow=windows.find((w)=>w.active)||windows[0],panes=await listPanes(activeWindow.id);if(!panes||panes.length===0)throw Error(`No panes found in session "${sessionName}"`);paneId=(panes.find((p)=>p.active)||panes[0]).id}let lastContent="",following=!0;function emitNewLines(oldContent,newContent){let newLines=newContent.split(`
2499
- `),oldLines=oldContent.split(`
2500
- `),startIndex=oldLines.length>0?oldLines.length-1:0,lastOldLine=oldLines[oldLines.length-1];for(let line of newLines.slice(startIndex))if(line&&line!==lastOldLine)callback(line)}let pollInterval=setInterval(async()=>{if(!following){clearInterval(pollInterval);return}try{let content=stripTmuxMarkers(await capturePaneContent(paneId,100));if(content!==lastContent)emitNewLines(lastContent,content),lastContent=content}catch{clearInterval(pollInterval),following=!1}},500);return()=>{following=!1,clearInterval(pollInterval)}}async function readSessionLogs2(target,options){try{let resolved=await resolveTarget(target),resolvedPaneId=resolved.paneId,sessionName=resolved.session||target,defaultLines=getTerminalConfig().readLines,readOptions={lines:options.lines?Number.parseInt(options.lines,10):defaultLines,from:options.from?Number.parseInt(options.from,10):void 0,to:options.to?Number.parseInt(options.to,10):void 0,range:options.range,search:options.search,grep:options.grep,follow:options.follow,all:options.all,reverse:options.reverse,pane:resolvedPaneId};if(options.follow){console.log(`Following "${target}" (Ctrl+C to stop)...`),console.log("");let stopFollowing=await followSessionLogs(sessionName,(line)=>{console.log(line)},{pane:resolvedPaneId});process.on("SIGINT",()=>{stopFollowing(),console.log(`
2501
- Stopped following`),process.exit(0)}),await new Promise(()=>{});return}let content=await readSessionLogs(sessionName,readOptions);if(options.json){let lines=content.split(`
2502
- `);console.log(JSON.stringify({target,session:sessionName,lineCount:lines.length,content:lines},null,2));return}console.log(content)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error reading logs: ${message}`),process.exit(1)}}var _taskService5;async function getTaskService5(){if(!_taskService5)_taskService5=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService5}function registerReleaseCommands(program2){let release=program2.command("release").description("Release management");release.command("create <name>").description("Create a release and assign tasks to it").requiredOption("--tasks <ids...>","Task IDs or #seqs to include").action(async(name,options)=>{try{let updated=await(await getTaskService5()).setRelease(options.tasks,name);console.log(`Release "${name}" created with ${updated} task${updated===1?"":"s"}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),release.command("list").description("List all releases").option("--json","Output as JSON").action(async(options)=>{try{let releases=await(await getTaskService5()).listReleases();if(options.json){console.log(JSON.stringify(releases,null,2));return}if(releases.length===0){console.log("No releases found.");return}console.log(` ${padRight("RELEASE",30)} TASKS`),console.log(` ${"\u2500".repeat(40)}`);for(let r of releases)console.log(` ${padRight(r.releaseId,30)} ${r.count}`);console.log(`
2524
+ `)}function formatSetupStep(s){if(s.kind==="spawn"){let opts=Object.entries(s.options).map(([k,v])=>`${k}: ${v}`).join(", ");return`- spawn ${s.target}${opts?` (${opts})`:""}`}return`- follow ${s.target}`}function formatActionStep(a){if(a.kind==="send")return`- send "${a.message}" to ${a.to}`;if(a.kind==="wait")return`- wait ${a.seconds??1}s`;if(a.kind==="run")return`- run ${a.command}`;return`- ${a.kind}`}async function waitForResult(spec,repoPath,teamName,timeoutMs,start2){let subject=`genie.qa.${teamName}.result`,remainingMs=Math.max(timeoutMs-(Date.now()-start2),1),event=await waitForRuntimeEvent({repoPath,subject,team:teamName},remainingMs);if(!event)return makeErrorReport(spec,start2,`Timeout after ${timeoutMs}ms waiting for team-lead report in PG event log`);return parseTeamLeadReport(spec,event.data??{},start2)}function parseTeamLeadReport(spec,payload,start2){try{let data=payload;return{name:spec.name,file:spec.file,result:data.result==="pass"?"pass":"fail",expectations:data.expectations??[],collectedEvents:data.collectedEvents??[],durationMs:Date.now()-start2}}catch(err){return makeErrorReport(spec,start2,`Failed to parse team-lead report: ${err}`)}}function makeErrorReport(spec,start2,error2){return{name:spec.name,file:spec.file,result:"error",expectations:[],collectedEvents:[],durationMs:Date.now()-start2,error:error2}}function computeEffectiveTimeoutMs(spec,requestedTimeoutMs){let totalWaitMs=spec.actions.reduce((sum,action)=>sum+(action.kind==="wait"?(action.seconds??0)*1000:0),0),setupSpawnCount=spec.setup.filter((step)=>step.kind==="spawn").length,orchestrationSlackMs=Math.max(30000,Math.min(90000,15000+Math.floor(totalWaitMs/2)+setupSpawnCount*15000));return requestedTimeoutMs+orchestrationSlackMs}function defaultSpecDir(repoPath){return join50(resolve7(repoPath??process.cwd()),".genie","qa")}init_runtime_events();async function qaCommand(target,options){let specDir=defaultSpecDir(),runnerOpts={timeout:options.timeout??3600,parallel:options.parallel??5,verbose:options.verbose??!1,repoPath:process.cwd(),ndjson:options.ndjson??!1},reports;if(target)reports=await resolveAndRun(specDir,target,runnerOpts);else reports=await runAllSpecs(specDir,runnerOpts);if(reports.length===0){console.error(`\x1B[33mNo QA specs found for "${target??"all"}"\x1B[0m`),process.exitCode=1;return}if(options.ndjson)for(let report of reports)console.log(JSON.stringify(report));else printRunResults(reports,options.verbose??!1);if(!reports.every((r)=>r.result==="pass"))process.exitCode=1}async function qaCheckCommand(specFile,options){let team=options.team??process.env.GENIE_TEAM;if(!team){console.error("Error: QA team not set. Use --team <name> or run inside a QA worker."),process.exitCode=1;return}let repoPath=process.cwd(),spec=await parseQaSpec(specFile),since=options.since??(options.sinceFile?await readSinceValue(options.sinceFile):void 0),teamAgents=(await list()).filter((agent)=>agent.team===team),events=await readTeamLog(teamAgents,repoPath,team,buildQaCheckLogFilter(since)),expectations=evaluateExpectations(spec.expect,events),collectedEvents=toCollectedEvents(events),result=expectations.every((exp)=>exp.result==="pass")?"pass":"fail";await publishSubjectEvent(repoPath,`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${result}`,data:{result,expectations,collectedEvents},source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}async function qaStatusCommand(options){let specDir=defaultSpecDir(),repoPath=process.cwd(),specs=await listAllSpecs(specDir),results=await loadResults(repoPath);if(specs.length===0){if(options?.json)console.log(JSON.stringify({specs:[],summary:{total:0,pass:0,fail:0,stale:0,never:0}}));else console.error("\x1B[33mNo QA specs found.\x1B[0m");return}if(options?.json)await printJsonStatus(specs,results,repoPath);else await printHumanStatus(specs,results,repoPath)}async function printJsonStatus(specs,results,repoPath){let jsonSpecs=[];for(let spec of specs){let stored=results[spec.key],stale=stored?await isStale(repoPath,spec.key,spec.filePath):!1,status=!stored?"never":stale?"stale":stored.result;jsonSpecs.push({key:spec.key,domain:spec.domain,name:spec.name,status,durationMs:stored?.durationMs??null,lastRun:stored?.lastRun??null,expectations:stored?.expectations??[],error:stored?.error??null})}let counts={total:specs.length,pass:jsonSpecs.filter((s)=>s.status==="pass").length,fail:jsonSpecs.filter((s)=>s.status==="fail"||s.status==="error").length,stale:jsonSpecs.filter((s)=>s.status==="stale").length,never:jsonSpecs.filter((s)=>s.status==="never").length};console.log(JSON.stringify({specs:jsonSpecs,summary:counts}))}async function printHumanStatus(specs,results,repoPath){console.log(),console.log("\x1B[1m QA Status\x1B[0m"),console.log(" \x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),console.log();let currentDomain="",counts={pass:0,fail:0,stale:0,never:0};for(let spec of specs){if(spec.domain!==currentDomain)currentDomain=spec.domain,console.log(` \x1B[1m${currentDomain}/\x1B[0m`);let stored=results[spec.key],stale=stored?await isStale(repoPath,spec.key,spec.filePath):!1;console.log(formatStatusLine(spec,stored,stale)),tallyResult(counts,stored,stale)}printStatusSummary(specs.length,counts)}function tallyResult(counts,stored,stale){if(!stored)counts.never++;else if(stale)counts.stale++;else if(stored.result==="pass")counts.pass++;else counts.fail++}function printStatusSummary(total,counts){console.log();let parts=[`${counts.pass}/${total} pass`,counts.fail?`${counts.fail} fail`:"",counts.stale?`${counts.stale} stale`:"",counts.never?`${counts.never} never`:""].filter(Boolean);console.log(` ${parts.join(" | ")}`),console.log()}async function readSinceValue(path2){try{return(await readFile12(path2,"utf-8")).trim()||void 0}catch{return}}function evaluateExpectations(expectations,events){return expectations.map((expectation)=>{let matched=events.filter((event)=>eventMatchesExpectationSource(event,expectation.source)).find((event)=>eventMatchesExpectation(event,expectation.matchers));if(matched)return{description:expectation.description,result:"pass",evidence:`${matched.kind} ${matched.agent}: ${matched.text.slice(0,120)}`};return{description:expectation.description,result:"fail",reason:"No matching event found in team log/transcript snapshot"}})}function eventMatchesExpectation(event,matchers){return Object.entries(matchers).every(([field,expected])=>matcherMatches(readEventField(event,field),expected,field))}function buildQaCheckLogFilter(since){if(since)return{since};return{last:200}}function eventMatchesExpectationSource(event,source){switch(source){case"inbox":return event.source==="mailbox"&&event.direction==="in";case"output":return event.source==="provider";default:return!0}}function readEventField(event,field){switch(field){case"timestamp":return event.timestamp;case"kind":return event.kind;case"agent":return event.agent;case"team":return event.team;case"direction":return event.direction;case"peer":return event.peer;case"text":return event.text;case"source":return event.source;default:break}return event.data?.[field]}function matcherMatches(actual,expected,field){if(actual==null)return!1;if(field==="kind"&&expected==="message")return["message","assistant","user"].includes(String(actual));let actualText=String(actual);if(expected.startsWith("~"))return actualText.includes(expected.slice(1));return actualText===expected}function toCollectedEvents(events){return events.slice(-50).map((event)=>({timestamp:event.timestamp,kind:event.kind,agent:event.agent,text:event.text}))}async function qaHistoryCommand(){let repoPath=process.cwd(),results=await loadResults(repoPath),entries=Object.entries(results).map(([key,r])=>({key,...r})).sort((a,b2)=>new Date(b2.lastRun).getTime()-new Date(a.lastRun).getTime());if(entries.length===0){console.error("\x1B[33mNo QA history found. Run `genie qa` first.\x1B[0m");return}console.log(),console.log("\x1B[1m QA History\x1B[0m"),console.log();let limit=20;for(let entry of entries.slice(0,limit)){let icon=resultIcon(entry.result),duration=entry.durationMs?`${(entry.durationMs/1000).toFixed(1)}s`:"-",ago=formatTimeAgo(entry.lastRun);console.log(` ${icon} ${entry.key.padEnd(35)} ${duration.padStart(6)} \x1B[2m${ago}\x1B[0m`)}if(entries.length>limit)console.log(` \x1B[2m... and ${entries.length-limit} more\x1B[0m`);console.log()}async function resolveAndRun(specDir,target,opts){let domainDir=join51(specDir,target);if(await isDirectory(domainDir))return runDomainSpecs(specDir,target,opts);let specPath=await resolveSpecPath(specDir,target);if(specPath){let spec=await parseQaSpec(specPath),key=specKeyFromPath(specDir,specPath),report=await runSpec(spec,{...opts,specKey:key}),repoPath=opts.repoPath??process.cwd();return await saveResult(repoPath,key,report),[report]}console.error(`\x1B[31mSpec or domain not found: ${target}\x1B[0m`),console.error("Available:");let allSpecs=await listAllSpecs(specDir),domains=[...new Set(allSpecs.map((s)=>s.domain))];for(let d of domains)console.error(` \x1B[1m${d}/\x1B[0m`);for(let s of allSpecs)console.error(` ${s.key}`);return process.exitCode=1,[]}async function resolveSpecPath(specDir,name){let candidates=[join51(specDir,name),join51(specDir,`${name}.md`)];for(let path2 of candidates)if(await isFile(path2))return path2;return null}function resultIcon(result){if(result==="pass")return"\x1B[32m\u2705\x1B[0m";if(result==="fail")return"\x1B[31m\u274C\x1B[0m";if(result==="error")return"\x1B[33m\u26A0\uFE0F\x1B[0m";return"\uD83D\uDD18"}function formatStatusLine(spec,stored,stale){if(!stored)return` \uD83D\uDD18 ${spec.name.padEnd(25)} ${"-".padStart(6)} \x1B[2mnever\x1B[0m`;if(stale){let ago2=formatTimeAgo(stored.lastRun);return` \u26A0\uFE0F ${spec.name.padEnd(25)} ${"-".padStart(6)} \x1B[33m${ago2} (stale)\x1B[0m`}let icon=resultIcon(stored.result),duration=stored.durationMs?`${(stored.durationMs/1000).toFixed(0)}s`:"-",ago=formatTimeAgo(stored.lastRun);return` ${icon} ${spec.name.padEnd(25)} ${duration.padStart(6)} \x1B[2m${ago}\x1B[0m`}function printRunResults(reports,verbose){console.log(),console.log("\x1B[1m QA Results\x1B[0m"),console.log();for(let report of reports)printReport(report,verbose);let passed=reports.filter((r)=>r.result==="pass").length,failed=reports.filter((r)=>r.result==="fail").length,errors3=reports.filter((r)=>r.result==="error").length,total=reports.length,color=failed+errors3>0?"\x1B[31m":"\x1B[32m",failedStr=failed?` \x1B[31m${failed} failed\x1B[0m`:"",errorsStr=errors3?` \x1B[33m${errors3} errors\x1B[0m`:"";console.log(` ${color}${passed}/${total} passed\x1B[0m${failedStr}${errorsStr}`),console.log()}function printReport(report,verbose){let duration=`${(report.durationMs/1000).toFixed(1)}s`;if(console.log(` ${resultIcon(report.result)} ${report.name} \x1B[2m(${duration})\x1B[0m`),report.error)console.log(` \x1B[31mError: ${report.error}\x1B[0m`);for(let exp of report.expectations)printExpectation(exp);if(verbose&&report.collectedEvents.length>0){console.log(` \x1B[2m--- Collected events (${report.collectedEvents.length}) ---\x1B[0m`);for(let event of report.collectedEvents)console.log(` \x1B[2m ${event.timestamp} ${event.kind} ${event.agent}: ${event.text.slice(0,80)}\x1B[0m`)}console.log()}function printExpectation(exp){let icon=exp.result==="pass"?"\x1B[32m\u2713\x1B[0m":"\x1B[31m\u2717\x1B[0m";if(console.log(` ${icon} ${exp.description}`),exp.result==="pass"&&exp.evidence)console.log(` \x1B[2m${exp.evidence}\x1B[0m`);if(exp.result==="fail"&&exp.reason)console.log(` \x1B[31m${exp.reason}\x1B[0m`)}async function isDirectory(path2){try{return(await stat5(path2)).isDirectory()}catch{return!1}}async function isFile(path2){try{return(await stat5(path2)).isFile()}catch{return!1}}init_read();var _taskService6;async function getTaskService6(){if(!_taskService6)_taskService6=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService6}function registerReleaseCommands(program2){let release=program2.command("release").description("Release management");release.command("create <name>").description("Create a release and assign tasks to it").requiredOption("--tasks <ids...>","Task IDs or #seqs to include").action(async(name,options)=>{try{let updated=await(await getTaskService6()).setRelease(options.tasks,name);console.log(`Release "${name}" created with ${updated} task${updated===1?"":"s"}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),release.command("list").description("List all releases").option("--json","Output as JSON").action(async(options)=>{try{let releases=await(await getTaskService6()).listReleases();if(options.json){console.log(JSON.stringify(releases,null,2));return}if(releases.length===0){console.log("No releases found.");return}console.log(` ${padRight("RELEASE",30)} TASKS`),console.log(` ${"\u2500".repeat(40)}`);for(let r of releases)console.log(` ${padRight(r.releaseId,30)} ${r.count}`);console.log(`
2503
2525
  ${releases.length} release${releases.length===1?"":"s"}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_cron();init_db();function formatTimestamp2(iso){return formatTimestamp(iso,{seconds:!0})}function parseAbsoluteTime(input){let date=new Date(input);if(Number.isNaN(date.getTime()))throw Error(`Invalid time: "${input}". Expected ISO 8601 format (e.g., 2026-03-21T09:00)`);return date}function isCronExpression(input){let parts=input.trim().split(/\s+/);return parts.length>=5&&parts.length<=6}function computeFirstDueAt(options){if(options.at){let dueAt=parseAbsoluteTime(options.at);if(dueAt.getTime()<=Date.now())throw Error(`Schedule time is in the past: ${options.at}`);return{dueAt,cronExpr:"@once",scheduleType:"once"}}if(options.after){let delayMs=parseDuration(options.after);return{dueAt:new Date(Date.now()+delayMs),cronExpr:"@once",scheduleType:"once"}}if(options.every){if(isCronExpression(options.every))return{dueAt:computeNextCronDue(options.every,{timezone:options.timezone}),cronExpr:options.every,scheduleType:"cron"};let intervalMs=parseDuration(options.every);return{dueAt:new Date(Date.now()+intervalMs),cronExpr:`@every ${options.every.trim()}`,scheduleType:"interval"}}throw Error("One of --at, --every, or --after is required")}function generateId(){return crypto.randomUUID()}function formatDuration(ms){if(ms==null||ms<0)return"-";if(ms<1000)return`${ms}ms`;if(ms<60000)return`${(ms/1000).toFixed(1)}s`;if(ms<3600000)return`${(ms/60000).toFixed(1)}m`;return`${(ms/3600000).toFixed(1)}h`}function printTable2(headers,rows){let widths=headers.map((h,i2)=>{let colValues=rows.map((r)=>(r[i2]??"").length);return Math.max(h.length,...colValues)}),headerLine=headers.map((h,i2)=>padRight(h,widths[i2])).join(" ");console.log(headerLine),console.log(widths.map((w)=>"\u2500".repeat(w)).join("\u2500\u2500"));for(let row of rows){let line=row.map((val,i2)=>padRight(val??"",widths[i2])).join(" ");console.log(line)}console.log(`(${rows.length} row${rows.length===1?"":"s"})`)}async function scheduleCreateCommand(name,options){if(!options.command)console.error("Error: --command is required"),process.exit(1);if(!options.at&&!options.every&&!options.after)console.error("Error: one of --at, --every, or --after is required"),process.exit(1);try{let{dueAt,cronExpr,scheduleType}=computeFirstDueAt(options),sql=await getConnection();if((await sql`SELECT id FROM schedules WHERE name = ${name} AND status = 'active'`).length>0)console.error(`Error: schedule "${name}" already exists. Cancel it first or use a different name.`),process.exit(1);let scheduleId=generateId(),triggerId=generateId(),runSpec2=options.leaseTimeout?{lease_timeout_ms:parseDuration(options.leaseTimeout)}:{},metadata={type:scheduleType,original_spec:options.at??options.every??options.after,timezone:options.timezone??"UTC"};await sql.begin(async(tx)=>{await tx`
2504
2526
  INSERT INTO schedules (id, name, cron_expression, timezone, command, run_spec, metadata, status)
2505
2527
  VALUES (${scheduleId}, ${name}, ${cronExpr}, ${options.timezone??"UTC"}, ${options.command}, ${JSON.stringify(runSpec2)}, ${JSON.stringify(metadata)}, 'active')
@@ -2625,20 +2647,26 @@ History for "${schedule.name}":
2625
2647
  ORDER BY sc.timestamp DESC
2626
2648
  LIMIT ${limit}
2627
2649
  `;if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log(`No results for "${query}".`);return}for(let r of rows)console.log(`[${formatRelativeTimestamp(r.timestamp)}] ${r.agent_label??"orphaned"} / ${r.session_id.slice(0,12)}`),console.log(` ${r.role}${r.tool_name?` [${r.tool_name}]`:""}: ${r.headline}`);console.log(`
2628
- (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions2=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions2.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--limit <n>","Max number of sessions to return (default: 50)").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions2.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions2.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query,options)=>{await sessionsSearchCommand(query,options)}),sessions2.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}var _taskService6;async function getTaskService6(){if(!_taskService6)_taskService6=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService6}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService6()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
2629
- ${tags.length} tag${tags.length===1?"":"s"}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),tag.command("create <name>").description("Create a custom tag").option("--color <hex>","Tag color (hex)","#9ca3af").option("--type <typeId>","Associate with a task type").action(async(name,options)=>{try{let ts3=await getTaskService6(),id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createTag({id,name,color:options.color,typeId:options.type});console.log(`Created tag "${t.name}" (${t.id}) with color ${t.color}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}var _boardService2;async function getBoardService2(){if(!_boardService2)_boardService2=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService2}var _closeMergedService;async function getCloseMergedService(){if(!_closeMergedService)_closeMergedService=await Promise.resolve().then(() => (init_task_close_merged(),exports_task_close_merged));return _closeMergedService}function localActor2(name){return{actorType:"local",actorId:name}}function currentActor2(){let name=process.env.GENIE_AGENT_NAME??"cli";return localActor2(name)}function getRunId(){return process.env.GENIE_RUN_ID??`run-${Date.now()}`}var PRIORITY_COLORS={urgent:"\x1B[31m",high:"\x1B[33m",normal:"\x1B[0m",low:"\x1B[90m"},RESET2="\x1B[0m";async function resolveDefaultBoardId(){try{let{execSync:execSync16}=await import("child_process"),repoRoot=execSync16("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),{join:join52}=await import("path"),configPath2=join52(repoRoot,".genie","config.json"),{existsSync:existsSync41,readFileSync:readFileSync20}=await import("fs");if(existsSync41(configPath2)){let config=JSON.parse(readFileSync20(configPath2,"utf-8"));if(config.activeBoard)return config.activeBoard}}catch{}return null}async function handleInvalidStageError(taskId,message){try{let task=await(await getTaskService7()).getTask(taskId);if(!task?.boardId)return;let board=await(await getBoardService2()).getBoard(task.boardId);if(!board)return;let validCols=board.columns.sort((a,b2)=>a.position-b2.position).map((c)=>c.name).join(" \u2192 ");console.error(`Error: ${message}
2630
- Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}async function resolveBoardOption(boardName){if(boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);return board.id}return await resolveDefaultBoardId()??void 0}function getProjectName(repoPath){let parts=repoPath.split("/");return parts[parts.length-1]||repoPath}function formatTaskRow(t,showProject,hasExternal){let seq2=showProject?`${getProjectName(t.repoPath)}#${t.seq}`:`#${t.seq}`,title=truncate(t.title,38),color=t.status==="archived"?"\x1B[90m":PRIORITY_COLORS[t.priority]??"",due=formatDate(t.dueDate),proj=showProject?`${padRight(getProjectName(t.repoPath),16)} `:"",ext=hasExternal?`${padRight(truncate(t.externalId??"",25),27)} `:"",statusLabel=t.status==="archived"?"\x1B[90m[archived]\x1B[0m":t.status;return` ${padRight(seq2,showProject?22:6)} ${proj}${padRight(title,40)} ${ext}${padRight(t.stage,12)} ${padRight(statusLabel,12)} ${color}${padRight(t.priority,10)}${RESET2} ${padRight(due,12)}`}function printTaskList(tasks,showProject=!1){if(tasks.length===0){console.log("No tasks found.");return}let hasExternal=tasks.some((t)=>t.externalId),extCol=hasExternal?`${padRight("EXTERNAL",27)} `:"",projCol=showProject?`${padRight("PROJECT",16)} `:"",header=` ${padRight("#",6)} ${projCol}${padRight("TITLE",40)} ${extCol}${padRight("STAGE",12)} ${padRight("STATUS",12)} ${padRight("PRIORITY",10)} ${padRight("DUE",12)}`,lineLen=(showProject?108:92)+(hasExternal?28:0);console.log(header),console.log(` ${"\u2500".repeat(lineLen)}`);for(let t of tasks)console.log(formatTaskRow(t,showProject,hasExternal));console.log(`
2631
- ${tasks.length} task${tasks.length===1?"":"s"}`)}function printTaskFields(task){console.log(""),console.log(`Task #${task.seq}: ${task.title}`),console.log("\u2500".repeat(60)),console.log(` ID: ${task.id}`),console.log(` Type: ${task.typeId}`),console.log(` Stage: ${task.stage}`),console.log(` Status: ${task.status}`),console.log(` Priority: ${task.priority}`);let optionalFields=[["Description",task.description],["Criteria",task.acceptanceCriteria],["Effort",task.estimatedEffort],["Start",task.startDate?formatDate(task.startDate):null],["Due",task.dueDate?formatDate(task.dueDate):null],["Blocked",task.blockedReason],["Parent",task.parentId],["Release",task.releaseId],["Wish",task.wishFile],["External",task.externalId],["Ext URL",task.externalUrl]];for(let[label,value]of optionalFields)if(value)console.log(` ${padRight(`${label}:`,12)} ${value}`);if(task.checkoutRunId)console.log(` Checkout: ${task.checkoutRunId} (since ${formatTimestamp(task.executionLockedAt)})`);if(console.log(` Created: ${formatTimestamp(task.createdAt)}`),task.startedAt)console.log(` Started: ${formatTimestamp(task.startedAt)}`);if(task.endedAt)console.log(` Ended: ${formatTimestamp(task.endedAt)}`)}async function printTaskRelations(task){let ts3=await getTaskService7(),actors=await ts3.getTaskActors(task.id,task.repoPath);if(actors.length>0){console.log(`
2650
+ (${rows.length} result${rows.length===1?"":"s"})`)}async function sessionsSyncStatusCommand(){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),status=await getBackfillStatus(sql);if(!status){console.log("No backfill has been started. It runs automatically on first daemon start.");return}let pct=status.totalFiles>0?(status.processedFiles/status.totalFiles*100).toFixed(1):"0.0",mbRead=(status.processedBytes/1024/1024).toFixed(1),mbTotal=(status.totalBytes/1024/1024).toFixed(1);console.log(`Session backfill: ${status.processedFiles} / ${status.totalFiles} files (${pct}%)`),console.log(`Bytes read: ${mbRead} MB / ${mbTotal} MB`),console.log(`Errors: ${status.errors}`),console.log(`Status: ${status.status}`)}function registerSessionsCommands(program2){let sessions2=program2.command("sessions").description("Session history \u2014 list, replay, search");sessions2.command("list",{isDefault:!0}).description("List Claude Code sessions").option("--active","Show only active sessions").option("--orphaned","Show only orphaned sessions").option("--agent <name>","Filter by agent").option("--limit <n>","Max number of sessions to return (default: 50)").option("--json","Output as JSON").action(async(options)=>{await sessionsListCommand(options)}),sessions2.command("replay <session-id>").description("Replay a session \u2014 interleave content + events").option("--json","Output as JSON").action(async(sessionId,options)=>{await sessionsReplayCommand(sessionId,options)}),sessions2.command("search <query>").description("Full-text search across session content").option("--json","Output as JSON").option("--limit <n>","Max results","20").action(async(query,options)=>{await sessionsSearchCommand(query,options)}),sessions2.command("sync").description("Check session backfill progress").action(async()=>{await sessionsSyncStatusCommand()})}var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}function registerTagCommands(program2){let tag=program2.command("tag").description("Tag management");tag.command("list").description("List all tags").option("--type <typeId>","Filter by task type").option("--json","Output as JSON").action(async(options)=>{try{let tags=await(await getTaskService7()).listTags(options.type);if(options.json){console.log(JSON.stringify(tags,null,2));return}console.log(` ${padRight("ID",20)} ${padRight("NAME",20)} ${padRight("COLOR",10)} TYPE`),console.log(` ${"\u2500".repeat(55)}`);for(let t of tags)console.log(` ${padRight(t.id,20)} ${padRight(t.name,20)} ${padRight(t.color,10)} ${t.typeId??"-"}`);console.log(`
2651
+ ${tags.length} tag${tags.length===1?"":"s"}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),tag.command("create <name>").description("Create a custom tag").option("--color <hex>","Tag color (hex)","#9ca3af").option("--type <typeId>","Associate with a task type").action(async(name,options)=>{try{let ts3=await getTaskService7(),id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createTag({id,name,color:options.color,typeId:options.type});console.log(`Created tag "${t.name}" (${t.id}) with color ${t.color}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}var _boardService2;async function getBoardService2(){if(!_boardService2)_boardService2=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService2}var _closeMergedService;async function getCloseMergedService(){if(!_closeMergedService)_closeMergedService=await Promise.resolve().then(() => (init_task_close_merged(),exports_task_close_merged));return _closeMergedService}function localActor2(name){return{actorType:"local",actorId:name}}function currentActor2(){let name=process.env.GENIE_AGENT_NAME??"cli";return localActor2(name)}function getRunId(){return process.env.GENIE_RUN_ID??`run-${Date.now()}`}var PRIORITY_COLORS={urgent:"\x1B[31m",high:"\x1B[33m",normal:"\x1B[0m",low:"\x1B[90m"},RESET2="\x1B[0m";async function resolveDefaultBoardId(){try{let{execSync:execSync16}=await import("child_process"),repoRoot=execSync16("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim(),{join:join52}=await import("path"),configPath2=join52(repoRoot,".genie","config.json"),{existsSync:existsSync41,readFileSync:readFileSync20}=await import("fs");if(existsSync41(configPath2)){let config=JSON.parse(readFileSync20(configPath2,"utf-8"));if(config.activeBoard)return config.activeBoard}}catch{}return null}async function handleInvalidStageError(taskId,message){try{let task=await(await getTaskService8()).getTask(taskId);if(!task?.boardId)return;let board=await(await getBoardService2()).getBoard(task.boardId);if(!board)return;let validCols=board.columns.sort((a,b2)=>a.position-b2.position).map((c)=>c.name).join(" \u2192 ");console.error(`Error: ${message}
2652
+ Valid columns for board "${board.name}": ${validCols}`),process.exit(1)}catch{}}async function resolveBoardOption(boardName){if(boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);return board.id}return await resolveDefaultBoardId()??void 0}function getProjectName(repoPath){let parts=repoPath.split("/");return parts[parts.length-1]||repoPath}function formatTaskRow(t,showProject,hasExternal){let seq2=showProject?`${getProjectName(t.repoPath)}#${t.seq}`:`#${t.seq}`,title=truncate2(t.title,38),color=t.status==="archived"?"\x1B[90m":PRIORITY_COLORS[t.priority]??"",due=formatDate(t.dueDate),proj=showProject?`${padRight(getProjectName(t.repoPath),16)} `:"",ext=hasExternal?`${padRight(truncate2(t.externalId??"",25),27)} `:"",statusLabel=t.status==="archived"?"\x1B[90m[archived]\x1B[0m":t.status;return` ${padRight(seq2,showProject?22:6)} ${proj}${padRight(title,40)} ${ext}${padRight(t.stage,12)} ${padRight(statusLabel,12)} ${color}${padRight(t.priority,10)}${RESET2} ${padRight(due,12)}`}function printTaskList(tasks,showProject=!1){if(tasks.length===0){console.log("No tasks found.");return}let hasExternal=tasks.some((t)=>t.externalId),extCol=hasExternal?`${padRight("EXTERNAL",27)} `:"",projCol=showProject?`${padRight("PROJECT",16)} `:"",header=` ${padRight("#",6)} ${projCol}${padRight("TITLE",40)} ${extCol}${padRight("STAGE",12)} ${padRight("STATUS",12)} ${padRight("PRIORITY",10)} ${padRight("DUE",12)}`,lineLen=(showProject?108:92)+(hasExternal?28:0);console.log(header),console.log(` ${"\u2500".repeat(lineLen)}`);for(let t of tasks)console.log(formatTaskRow(t,showProject,hasExternal));console.log(`
2653
+ ${tasks.length} task${tasks.length===1?"":"s"}`)}function printTaskFields(task){console.log(""),console.log(`Task #${task.seq}: ${task.title}`),console.log("\u2500".repeat(60)),console.log(` ID: ${task.id}`),console.log(` Type: ${task.typeId}`),console.log(` Stage: ${task.stage}`),console.log(` Status: ${task.status}`),console.log(` Priority: ${task.priority}`);let optionalFields=[["Description",task.description],["Criteria",task.acceptanceCriteria],["Effort",task.estimatedEffort],["Start",task.startDate?formatDate(task.startDate):null],["Due",task.dueDate?formatDate(task.dueDate):null],["Blocked",task.blockedReason],["Parent",task.parentId],["Release",task.releaseId],["Wish",task.wishFile],["External",task.externalId],["Ext URL",task.externalUrl]];for(let[label,value]of optionalFields)if(value)console.log(` ${padRight(`${label}:`,12)} ${value}`);if(task.checkoutRunId)console.log(` Checkout: ${task.checkoutRunId} (since ${formatTimestamp(task.executionLockedAt)})`);if(console.log(` Created: ${formatTimestamp(task.createdAt)}`),task.startedAt)console.log(` Started: ${formatTimestamp(task.startedAt)}`);if(task.endedAt)console.log(` Ended: ${formatTimestamp(task.endedAt)}`)}async function printTaskRelations(task){let ts3=await getTaskService8(),actors=await ts3.getTaskActors(task.id,task.repoPath);if(actors.length>0){console.log(`
2632
2654
  Actors:`);for(let a of actors)console.log(` ${a.role}: ${a.actorId} (${a.actorType})`)}let tags=await ts3.getTaskTags(task.id,task.repoPath);if(tags.length>0)console.log(`
2633
2655
  Tags: ${tags.map((t)=>t.name).join(", ")}`);let blockers=await ts3.getBlockers(task.id,task.repoPath);if(blockers.length>0){console.log(`
2634
2656
  Dependencies:`);for(let dep of blockers){let depTask=await ts3.getTask(dep.dependsOnId,task.repoPath),label=depTask?`#${depTask.seq} ${depTask.title}`:dep.dependsOnId;console.log(` ${dep.depType}: ${label}`)}}let stageLog=await ts3.getStageLog(task.id,task.repoPath);if(stageLog.length>0){console.log(`
2635
- Stage History:`);for(let entry of stageLog.slice(0,10)){let who=entry.actorId??"system";console.log(` ${formatTimestamp(entry.createdAt)}: ${entry.fromStage??"(new)"} \u2192 ${entry.toStage} by ${who}`)}}}async function printTaskMessages(task){let ts3=await getTaskService7(),conv=await ts3.findOrCreateConversation({linkedEntity:"task",linkedEntityId:task.id,name:`Task #${task.seq}`}),messages2=await ts3.getMessages(conv.id,{limit:20});if(messages2.length>0){console.log(`
2657
+ Stage History:`);for(let entry of stageLog.slice(0,10)){let who=entry.actorId??"system";console.log(` ${formatTimestamp(entry.createdAt)}: ${entry.fromStage??"(new)"} \u2192 ${entry.toStage} by ${who}`)}}}async function printTaskMessages(task){let ts3=await getTaskService8(),conv=await ts3.findOrCreateConversation({linkedEntity:"task",linkedEntityId:task.id,name:`Task #${task.seq}`}),messages2=await ts3.getMessages(conv.id,{limit:20});if(messages2.length>0){console.log(`
2636
2658
  Messages:`);for(let msg of messages2){let time=formatTimestamp(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}}}async function printTaskDetail(task){printTaskFields(task),await printTaskRelations(task),await printTaskMessages(task),console.log("")}function printColumnTasks(label,colTasks,useColor=!0){if(console.log(`
2637
- \u2500\u2500 ${label} (${colTasks.length} task${colTasks.length===1?"":"s"}) \u2500\u2500`),colTasks.length===0){console.log(" (empty)");return}for(let t of colTasks){let pc=useColor?PRIORITY_COLORS[t.priority]??"":"",reset2=useColor?RESET2:"";console.log(` ${pc}#${t.seq}${reset2} ${padRight(truncate(t.title,35),37)} ${padRight(t.status,14)} ${t.priority}`)}}async function printByColumn(tasks,boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);console.log(`
2638
- Board: ${board.name} (${board.id})`),console.log("\u2550".repeat(40));let columns=[...board.columns].sort((a,b2)=>a.position-b2.position);for(let col of columns)printColumnTasks(col.label,tasks.filter((t)=>t.columnId===col.id));let columnIds=new Set(columns.map((c)=>c.id)),orphaned=tasks.filter((t)=>t.columnId&&!columnIds.has(t.columnId));if(orphaned.length>0)printColumnTasks("Orphaned",orphaned,!1);console.log("")}function parseGhRef(gh){let match=gh.match(/^([^#]+)#(\d+)$/);if(!match)console.error(`Error: Invalid --gh format. Expected owner/repo#N, got: ${gh}`),process.exit(1);let[,ownerRepo,num]=match;return{externalId:`${ownerRepo}#${num}`,externalUrl:`https://github.com/${ownerRepo}/issues/${num}`}}async function handleTaskCreate(title,options){let ts3=await getTaskService7(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts3.getProjectByName(options.project);if(!project)project=await ts3.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts3.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let boardId=await resolveBoardOption(options.board),externalId=options.externalId,externalUrl=options.externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}let task=await ts3.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId,externalId,externalUrl},repoPath,projectId);if(await ts3.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts3.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts3.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts3.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}async function handleCloseMerged(options){let result=await(await getCloseMergedService()).closeMergedTasks({since:options.since,dryRun:options.dryRun,repo:options.repo});if(options.dryRun)console.log("[dry-run] Would close:");for(let d of result.details){let prefix=options.dryRun?" [dry-run]":" \u2713";console.log(`${prefix} #${d.taskSeq} "${d.taskTitle}" \u2190 PR #${d.prNumber} (${d.slug})`)}let mode=options.dryRun?"[dry-run] ":"";console.log(`
2639
- ${mode}Closed ${result.closed} task${result.closed===1?"":"s"} from ${result.prsScanned} merged PR${result.prsScanned===1?"":"s"} (${result.alreadyShipped} already shipped)`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").option("--board <name>","Board name to assign task to").option("--gh <owner/repo#N>","Link to GitHub issue (sets external_id + external_url)").option("--external-id <id>","External tracker ID (e.g., JIRA-123)").option("--external-url <url>","External tracker URL").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});async function handleTaskList(options){let ts3=await getTaskService7(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,boardName:options.board,externalId:options.gh?parseGhRef(options.gh).externalId:void 0,allProjects:options.all,includeArchived:options.all,limit:Number(options.limit)||100,offset:Number(options.offset)||0,...options.all?{limit:1e4}:{}},tasks;if(options.mine)tasks=await ts3.listTasksForActor(currentActor2(),filters);else tasks=await ts3.listTasks(filters);if(options.byColumn){if(!options.board)console.error("Error: --by-column requires --board"),process.exit(1);if(!options.includeDone)tasks=tasks.filter((t)=>t.status!=="done");await printByColumn(tasks,options.board);return}if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--board <name>","Filter by board name").option("--gh <owner/repo#N>","Filter by GitHub issue link").option("--by-column","Group tasks by board column (kanban view)").option("--include-done","Include done tasks in kanban view (hidden by default)").option("--all","Show tasks from ALL projects").option("--limit <n>","Max number of tasks to return","100").option("--offset <n>","Skip first N tasks (for pagination)","0").option("--json","Output as JSON").action(async(options)=>{try{await handleTaskList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService7()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("link <id>").description("Link task to an external tracker (GitHub, Jira, etc.)").option("--gh <owner/repo#N>","Link to GitHub issue").option("--external-id <id>","External tracker ID").option("--external-url <url>","External tracker URL").action(async(id,options)=>{try{let ts3=await getTaskService7(),externalId,externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}else if(options.externalId&&options.externalUrl)externalId=options.externalId,externalUrl=options.externalUrl;else console.error("Error: Provide --gh or both --external-id and --external-url."),process.exit(1);let t=await ts3.linkTask(id,externalId,externalUrl);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(console.log(`Linked task #${t.seq} to ${externalId}`),t.externalUrl)console.log(` URL: ${t.externalUrl}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);if(message.includes("Invalid stage"))await handleInvalidStageError(id,message);console.error(`Error: ${message}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts3=await getTaskService7(),actor=currentActor2();if(await ts3.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts3.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService7()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts3=await getTaskService7(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts3=await getTaskService7(),runId=getRunId(),t=await ts3.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts3=await getTaskService7(),runId=getRunId(),t=await ts3.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService7()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("close-merged").description("Auto-close tasks whose wish slugs match recently merged PRs").option("--since <duration>",'Time window for merged PRs (e.g., "24h", "7d")',"24h").option("--dry-run","Show what would be closed without acting").option("--repo <owner/repo>","Override GitHub remote detection").action(async(options)=>{try{await handleCloseMerged(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("archive <id>").description("Archive a task (soft-delete \u2014 preserves all data)").action(async(id)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.archiveTask(id,actor);console.log(`Archived task #${t.seq}: ${t.title}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unarchive <id>").description("Restore an archived task to its previous status").action(async(id)=>{try{let ts3=await getTaskService7(),actor=currentActor2(),t=await ts3.unarchiveTask(id,actor);console.log(`Unarchived task #${t.seq}: ${t.title} (status: ${t.status})`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts3=await getTaskService7();if(options.remove){if(await ts3.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts3.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts3.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts3.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team();function registerTemplateCommands(program2){let tmpl=program2.command("template").description("Board template management");tmpl.command("list",{isDefault:!0}).description("List available templates").option("--json","Output as JSON").action(async(options)=>{let{listTemplates:listTemplates3}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),templates=await listTemplates3();if(options.json){console.log(JSON.stringify(templates,null,2));return}if(templates.length===0){console.log("No templates found.");return}let maxName=Math.max(...templates.map((t)=>t.name.length),4);for(let t of templates){let cols=t.columns?.length??0,tag=t.isBuiltin?" (builtin)":"";console.log(` ${padRight(t.name,maxName)} ${cols} columns${tag} ${t.id}`)}console.log(`
2659
+ \u2500\u2500 ${label} (${colTasks.length} task${colTasks.length===1?"":"s"}) \u2500\u2500`),colTasks.length===0){console.log(" (empty)");return}for(let t of colTasks){let pc=useColor?PRIORITY_COLORS[t.priority]??"":"",reset2=useColor?RESET2:"";console.log(` ${pc}#${t.seq}${reset2} ${padRight(truncate2(t.title,35),37)} ${padRight(t.status,14)} ${t.priority}`)}}async function printByColumn(tasks,boardName){let board=await(await getBoardService2()).getBoard(boardName);if(!board)console.error(`Error: Board not found: ${boardName}`),process.exit(1);console.log(`
2660
+ Board: ${board.name} (${board.id})`),console.log("\u2550".repeat(40));let columns=[...board.columns].sort((a,b2)=>a.position-b2.position);for(let col of columns)printColumnTasks(col.label,tasks.filter((t)=>t.columnId===col.id));let columnIds=new Set(columns.map((c)=>c.id)),orphaned=tasks.filter((t)=>t.columnId&&!columnIds.has(t.columnId));if(orphaned.length>0)printColumnTasks("Orphaned",orphaned,!1);console.log("")}function parseGhRef(gh){let match=gh.match(/^([^#]+)#(\d+)$/);if(!match)console.error(`Error: Invalid --gh format. Expected owner/repo#N, got: ${gh}`),process.exit(1);let[,ownerRepo,num]=match;return{externalId:`${ownerRepo}#${num}`,externalUrl:`https://github.com/${ownerRepo}/issues/${num}`}}async function handleTaskCreate(title,options){let ts3=await getTaskService8(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts3.getProjectByName(options.project);if(!project)project=await ts3.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts3.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let boardId=await resolveBoardOption(options.board),externalId=options.externalId,externalUrl=options.externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}let task=await ts3.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort,boardId,externalId,externalUrl},repoPath,projectId);if(await ts3.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts3.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts3.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts3.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}async function handleCloseMerged(options){let result=await(await getCloseMergedService()).closeMergedTasks({since:options.since,dryRun:options.dryRun,repo:options.repo});if(options.dryRun)console.log("[dry-run] Would close:");for(let d of result.details){let prefix=options.dryRun?" [dry-run]":" \u2713";console.log(`${prefix} #${d.taskSeq} "${d.taskTitle}" \u2190 PR #${d.prNumber} (${d.slug})`)}let mode=options.dryRun?"[dry-run] ":"";console.log(`
2661
+ ${mode}Closed ${result.closed} task${result.closed===1?"":"s"} from ${result.prsScanned} merged PR${result.prsScanned===1?"":"s"} (${result.alreadyShipped} already shipped)`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").option("--board <name>","Board name to assign task to").option("--gh <owner/repo#N>","Link to GitHub issue (sets external_id + external_url)").option("--external-id <id>","External tracker ID (e.g., JIRA-123)").option("--external-url <url>","External tracker URL").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});async function handleTaskList(options){let ts3=await getTaskService8(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,boardName:options.board,externalId:options.gh?parseGhRef(options.gh).externalId:void 0,allProjects:options.all,includeArchived:options.all,limit:Number(options.limit)||100,offset:Number(options.offset)||0,...options.all?{limit:1e4}:{}},tasks;if(options.mine)tasks=await ts3.listTasksForActor(currentActor2(),filters);else tasks=await ts3.listTasks(filters);if(options.byColumn){if(!options.board)console.error("Error: --by-column requires --board"),process.exit(1);if(!options.includeDone)tasks=tasks.filter((t)=>t.status!=="done");await printByColumn(tasks,options.board);return}if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--board <name>","Filter by board name").option("--gh <owner/repo#N>","Filter by GitHub issue link").option("--by-column","Group tasks by board column (kanban view)").option("--include-done","Include done tasks in kanban view (hidden by default)").option("--all","Show tasks from ALL projects").option("--limit <n>","Max number of tasks to return","100").option("--offset <n>","Skip first N tasks (for pagination)","0").option("--json","Output as JSON").action(async(options)=>{try{await handleTaskList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService8()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("link <id>").description("Link task to an external tracker (GitHub, Jira, etc.)").option("--gh <owner/repo#N>","Link to GitHub issue").option("--external-id <id>","External tracker ID").option("--external-url <url>","External tracker URL").action(async(id,options)=>{try{let ts3=await getTaskService8(),externalId,externalUrl;if(options.gh){let parsed=parseGhRef(options.gh);externalId=parsed.externalId,externalUrl=parsed.externalUrl}else if(options.externalId&&options.externalUrl)externalId=options.externalId,externalUrl=options.externalUrl;else console.error("Error: Provide --gh or both --external-id and --external-url."),process.exit(1);let t=await ts3.linkTask(id,externalId,externalUrl);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(console.log(`Linked task #${t.seq} to ${externalId}`),t.externalUrl)console.log(` URL: ${t.externalUrl}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);if(message.includes("Invalid stage"))await handleInvalidStageError(id,message);console.error(`Error: ${message}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts3=await getTaskService8(),actor=currentActor2();if(await ts3.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts3.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService8()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts3=await getTaskService8(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts3.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts3=await getTaskService8(),runId=getRunId(),t=await ts3.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts3=await getTaskService8(),runId=getRunId(),t=await ts3.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService8()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("close-merged").description("Auto-close tasks whose wish slugs match recently merged PRs").option("--since <duration>",'Time window for merged PRs (e.g., "24h", "7d")',"24h").option("--dry-run","Show what would be closed without acting").option("--repo <owner/repo>","Override GitHub remote detection").action(async(options)=>{try{await handleCloseMerged(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("archive <id>").description("Archive a task (soft-delete \u2014 preserves all data)").action(async(id)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.archiveTask(id,actor);console.log(`Archived task #${t.seq}: ${t.title}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unarchive <id>").description("Restore an archived task to its previous status").action(async(id)=>{try{let ts3=await getTaskService8(),actor=currentActor2(),t=await ts3.unarchiveTask(id,actor);console.log(`Unarchived task #${t.seq}: ${t.title} (status: ${t.status})`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts3=await getTaskService8();if(options.remove){if(await ts3.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts3.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts3.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts3.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team();function registerTemplateCommands(program2){let tmpl=program2.command("template").description("Board template management");tmpl.command("list",{isDefault:!0}).description("List available templates").option("--json","Output as JSON").action(async(options)=>{let{listTemplates:listTemplates3}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),templates=await listTemplates3();if(options.json){console.log(JSON.stringify(templates,null,2));return}if(templates.length===0){console.log("No templates found.");return}let maxName=Math.max(...templates.map((t)=>t.name.length),4);for(let t of templates){let cols=t.columns?.length??0,tag=t.isBuiltin?" (builtin)":"";console.log(` ${padRight(t.name,maxName)} ${cols} columns${tag} ${t.id}`)}console.log(`
2640
2662
  ${templates.length} template${templates.length===1?"":"s"}`)}),tmpl.command("show <name>").description("Show template details").option("--json","Output as JSON").action(async(name,options)=>{let{getTemplate:getTemplate2}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),t=await getTemplate2(name);if(!t)console.error(`Template "${name}" not found.`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}if(console.log(`
2641
- Name: ${t.name}`),console.log(`ID: ${t.id}`),console.log(`Builtin: ${t.isBuiltin}`),console.log(`Description: ${t.description??"(none)"}`),t.columns&&t.columns.length>0)console.log(`Columns: ${t.columns.map((c)=>c.name).join(", ")}`);console.log("")}),tmpl.command("delete <name>").description("Delete a template").action(async(name)=>{let{deleteTemplate:deleteTemplate2,getTemplate:getTemplate2}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),t=await getTemplate2(name);if(!t)console.error(`Template "${name}" not found.`),process.exit(1);let ok=await deleteTemplate2(t.id);console.log(ok?`Deleted template "${t.name}".`:"Delete failed.")})}var _taskService8;async function getTaskService8(){if(!_taskService8)_taskService8=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService8}function printTypeTable(types4){console.log(` ${padRight("ID",20)} ${padRight("NAME",30)} ${padRight("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types4){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight(t.id,20)} ${padRight(t.name,30)} ${padRight(String(stageCount),8)} ${builtin}`)}console.log(`
2663
+ Name: ${t.name}`),console.log(`ID: ${t.id}`),console.log(`Builtin: ${t.isBuiltin}`),console.log(`Description: ${t.description??"(none)"}`),t.columns&&t.columns.length>0)console.log(`Columns: ${t.columns.map((c)=>c.name).join(", ")}`);console.log("")}),tmpl.command("delete <name>").description("Delete a template").action(async(name)=>{let{deleteTemplate:deleteTemplate2,getTemplate:getTemplate2}=await Promise.resolve().then(() => (init_template_service(),exports_template_service)),t=await getTemplate2(name);if(!t)console.error(`Template "${name}" not found.`),process.exit(1);let ok=await deleteTemplate2(t.id);console.log(ok?`Deleted template "${t.name}".`:"Delete failed.")})}var _taskService9;async function getTaskService9(){if(!_taskService9)_taskService9=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService9}function printTypeTable(types4){console.log(` ${padRight("ID",20)} ${padRight("NAME",30)} ${padRight("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types4){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight(t.id,20)} ${padRight(t.name,30)} ${padRight(String(stageCount),8)} ${builtin}`)}console.log(`
2642
2664
  ${types4.length} type${types4.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
2643
2665
  Type: ${t.name} (${t.id})`),t.description)console.log(`Description: ${t.description}`);if(t.icon)console.log(`Icon: ${t.icon}`);console.log(`Built-in: ${t.isBuiltin?"yes":"no"}`),console.log("\u2500".repeat(60)),console.log(`
2644
- Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService8()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService8()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts3=await getTaskService8(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}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{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync4}=await import("child_process"),result=spawnSync4("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.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").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);registerAgentNamespace(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBriefCommands(program2);registerInstallCommand(program2);registerPublishCommand(program2);var itemCmd=program2.command("item").description("Item registry management");registerItemUninstallCommand(itemCmd);registerItemUpdateCommand(itemCmd);var auditTimers=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(()=>{})});program2.hook("postAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs}).catch(()=>{})}).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",process.env.GENIE_TEAM??"genie").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("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(process.env.GENIE_TUI_PANE==="left"&&args.length===0){process.env.GENIE_TUI_PANE=void 0;let{launchTui:launchTui2}=await Promise.resolve().then(() => exports_tui);await launchTui2(),process.exit(0)}process.env.GENIE_TUI_PANE=void 0;if(args.length===0){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` to create one."),process.exit(1);let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(ws.agent)process.env.GENIE_TUI_AGENT=ws.agent;if(ws.agent){let{writeFileSync:writeFileSync16}=await import("fs"),{join:join55}=await import("path"),home=process.env.GENIE_HOME??join55((await import("os")).homedir(),".genie");try{writeFileSync16(join55(home,"tui-initial-agent"),ws.agent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}
2666
+ Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService9()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService9()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts3=await getTaskService9(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}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{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);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
2667
+ `)}});async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync4}=await import("child_process"),result=spawnSync4("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.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").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);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBriefCommands(program2);registerInstallCommand(program2);registerPublishCommand(program2);var itemCmd=program2.command("item").description("Item registry management");registerItemUninstallCommand(itemCmd);registerItemUpdateCommand(itemCmd);var auditTimers=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(()=>{})});program2.hook("postAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs}).catch(()=>{})}).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",process.env.GENIE_TEAM??"genie").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("--no-auto-resume","Disable auto-resume on pane death").addHelpText("after",`
2668
+ Examples:
2669
+ genie spawn engineer # Spawn built-in engineer role
2670
+ genie spawn researcher --model sonnet # Spawn with model override
2671
+ genie spawn my-agent --team my-feature # Spawn into a specific team
2672
+ genie spawn council--questioner --provider codex # Use Codex provider`).action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v2)=>Number(v2),3600).option("--parallel <n>","Max specs to run in parallel",(v2)=>Number(v2),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});qaCmd.command("check <specFile>").description("Evaluate a QA spec against current team logs and publish qa-report").option("--team <name>","Team name (defaults to GENIE_TEAM)").option("--since <timestamp>","Only consider events after this ISO timestamp").option("--since-file <path>","Read the lower-bound timestamp from a file").action(async(specFile,options)=>{await qaCheckCommand(specFile,options)});program2.command("qa-report <json>").description("Publish QA result to the PG event log (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let data=JSON.parse(json2),{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),`genie.qa.${team}.result`,{kind:"qa",agent:"qa",team,text:`QA result: ${String(data.result??"unknown")}`,data,source:"hook"}),console.log(`QA result published to PG event log as genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(process.env.GENIE_TUI_PANE==="left"&&args.length===0){process.env.GENIE_TUI_PANE=void 0;let{launchTui:launchTui2}=await Promise.resolve().then(() => exports_tui);await launchTui2(),process.exit(0)}process.env.GENIE_TUI_PANE=void 0;if(args.length===0){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` to create one."),process.exit(1);let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2,isTuiSessionReady:isTuiSessionReady2,ensureTuiSession:ensureTuiSession2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();else if(!isTuiSessionReady2())ensureTuiSession2(ws.root);if(ws.root)process.env.GENIE_TUI_WORKSPACE=ws.root;if(ws.agent)process.env.GENIE_TUI_AGENT=ws.agent;if(ws.agent){let{writeFileSync:writeFileSync16}=await import("fs"),{join:join55}=await import("path"),home=process.env.GENIE_HOME??join55((await import("os")).homedir(),".genie");try{writeFileSync16(join55(home,"tui-initial-agent"),ws.agent,"utf-8")}catch{}}let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2(),process.exit(0)}if(args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:!0}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_2,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}}else try{await program2.parseAsync(process.argv)}finally{stopOtelReceiver(),await shutdown().catch(()=>{})}