@automagik/genie 4.260501.2 → 4.260501.4

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
@@ -931,7 +931,7 @@ ${errCtx.stack}`;d.reject(err)}else d.resolve(msg)}});return sub.requestSubject=
931
931
  FROM session_sync
932
932
  WHERE id = 'backfill'
933
933
  LIMIT 1
934
- `;if(rows.length===0)return{driftPct:null,detail:"no prior backfill row \u2014 first run will seed"};let row=rows[0];if(row.total_bytes<=0)return{driftPct:0,detail:"no JSONL bytes discovered yet"};let pct=Math.max(0,row.total_bytes-row.processed_bytes)/row.total_bytes*100,display=pct.toFixed(1);return{driftPct:pct,detail:`processed ${row.processed_bytes}/${row.total_bytes} bytes (drift ${display}%)`}}catch(err){return{driftPct:null,detail:`drift probe failed: ${err.message}`}}}async function defaultRunBackfillSync(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};return(async()=>{try{let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql)}catch{}})(),{ranSync:!0,driftPct:null,detail:"background convergence kicked \u2014 `genie doctor --fix` to wait"}}async function defaultRunBackfillBlocking(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql);let after=await defaultMeasureBackfillDrift();return{ranSync:!0,driftPct:after.driftPct,detail:after.detail}}async function defaultListOrphanedZombies(){try{let{listExhaustedZombies:listExhaustedZombies2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return await listExhaustedZombies2()}catch{return[]}}function safeIsDirectory(path2){try{return statSync5(path2).isDirectory()}catch{return!1}}function summarizeInboxes(inboxesDir2){let inboxFiles=[];try{inboxFiles=readdirSync6(inboxesDir2).filter((f)=>f.endsWith(".json"))}catch{return{newestMs:null,hasContent:!1}}let newestMs=null,hasContent=!1;for(let f of inboxFiles)try{let st=statSync5(join24(inboxesDir2,f));if(newestMs===null||st.mtimeMs>newestMs)newestMs=st.mtimeMs;if(st.size>2)hasContent=!0}catch{}return{newestMs,hasContent}}function classifyTeamDir2(name,base,now){let dir=join24(base,name);if(!safeIsDirectory(dir))return null;if(existsSync19(join24(dir,"config.json")))return null;let inboxesDir2=join24(dir,"inboxes");if(!safeIsDirectory(inboxesDir2))return null;let{newestMs,hasContent}=summarizeInboxes(inboxesDir2),orphan={teamName:name,path:dir,newestInboxMs:newestMs,hasContent},active=hasContent&&newestMs!==null&&now-newestMs<ORPHAN_FRESH_WINDOW_MS;return{orphan,active}}function defaultScanTeamConfigOrphans(){let base=teamsBaseDir3(),result2={active:[],stale:[]};if(!existsSync19(base))return result2;let now=Date.now();for(let name of readdirSync6(base)){if(name.startsWith(".")||name==="_archive")continue;let classified=classifyTeamDir2(name,base,now);if(!classified)continue;(classified.active?result2.active:result2.stale).push(classified.orphan)}return result2}function defaultArchiveStaleTeamConfigs(orphans){if(orphans.length===0)return[];let archiveRoot=join24(teamsBaseDir3(),"_archive");mkdirSync9(archiveRoot,{recursive:!0});let ts3=new Date().toISOString().replace(/[:.]/g,"-"),archived=[];for(let o of orphans){let dest=join24(archiveRoot,`${o.teamName}-${ts3}`);try{renameSync4(o.path,dest),archived.push(dest)}catch{}}return archived}async function defaultRecordAudit(eventType,name,details){await recordAuditEvent("command","serve_start",eventType,"serve",{precondition:name,...details})}async function checkPartition(health,autoFix,deps){if(health.partition_health==="ok"||health.partition_health==="warn")return{name:"partition",status:"ok",detail:health.next_rotation_at?`next rotation: ${health.next_rotation_at}`:void 0};if(health.partition_health==="unknown")return{name:"partition",status:"skipped",detail:"pg unavailable \u2014 skipping partition probe"};if(!autoFix)return{name:"partition",status:"refused",detail:"today's partition is missing or rotation is overdue",fixCommand:"genie doctor --observability # then re-run; or `genie serve start` (without --no-fix)"};let result2=await deps.runPartitionMaintenance();return{name:"partition",status:"fixed",detail:`created/present ${result2.createdOrPresent}, dropped ${result2.dropped}; next rotation ${result2.nextRotationAt??"unknown"}`}}async function checkWatchdog(health,autoFix,deps){if(deps.platform!=="linux")return{name:"watchdog",status:"skipped",detail:"watchdog install is Linux/systemd only"};if(health.watchdog==="ok")return{name:"watchdog",status:"ok"};if(!autoFix)return{name:"watchdog",status:"refused",detail:health.watchdog_detail??"watchdog units missing",fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"};try{let result2=await deps.installWatchdog();return{name:"watchdog",status:"fixed",detail:`wrote ${result2.filesWritten.length}, skipped ${result2.filesSkipped.length}`}}catch(err){return{name:"watchdog",status:"refused",detail:`auto-install failed: ${err.message}`,fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"}}}async function checkBackfill(autoFix,deps){let drift=await deps.measureBackfillDrift();if(drift.driftPct===null)return{name:"backfill",status:"skipped",detail:drift.detail};if(drift.driftPct<BACKFILL_DRIFT_THRESHOLD_PCT)return{name:"backfill",status:"ok",detail:drift.detail};if(!autoFix)return{name:"backfill",status:"refused",detail:drift.detail,fixCommand:`genie sessions sync # drift ${drift.driftPct.toFixed(1)}% > ${BACKFILL_DRIFT_THRESHOLD_PCT}%`};return{name:"backfill",status:"fixed",detail:(await deps.runBackfillSync()).detail}}async function checkDeadPaneZombies(deps){let orphans=await deps.listOrphanedZombies();if(orphans.length===0)return{name:"dead_pane_zombies",status:"ok"};return{name:"dead_pane_zombies",status:"refused",detail:`${orphans.length} exhausted zombie(s) past TTL; visible in \`genie status\``,fixCommand:"genie prune --zombies # archive eligible rows"}}function checkTeamConfigOrphans(autoFix,deps){let scan=deps.scanTeamConfigOrphans();if(scan.active.length===0&&scan.stale.length===0)return{name:"team_config_orphans",status:"ok"};if(!autoFix){let summary=`active=${scan.active.length} stale=${scan.stale.length}`,firstActive=scan.active[0]?.teamName;return{name:"team_config_orphans",status:"refused",detail:summary,fixCommand:firstActive?`genie team repair ${firstActive} # active orphan; stale dirs archive on auto-fix`:"genie serve start # auto-fix archives stale orphans"}}let archivedPaths=deps.archiveStaleTeamConfigs(scan.stale);if(scan.active.length>0)return{name:"team_config_orphans",status:"refused",detail:`archived ${archivedPaths.length} stale; ${scan.active.length} active orphan(s) need repair`,fixCommand:`genie team repair ${scan.active[0].teamName}`};return{name:"team_config_orphans",status:"fixed",detail:`archived ${archivedPaths.length} stale orphan(s)`}}function bindDefaults(deps){return{collectHealth:deps?.collectHealth??collectObservabilityHealth,runPartitionMaintenance:deps?.runPartitionMaintenance??defaultRunPartitionMaintenance,installWatchdog:deps?.installWatchdog??defaultInstallWatchdog,runBackfillSync:deps?.runBackfillSync??defaultRunBackfillSync,measureBackfillDrift:deps?.measureBackfillDrift??defaultMeasureBackfillDrift,listOrphanedZombies:deps?.listOrphanedZombies??defaultListOrphanedZombies,scanTeamConfigOrphans:deps?.scanTeamConfigOrphans??defaultScanTeamConfigOrphans,archiveStaleTeamConfigs:deps?.archiveStaleTeamConfigs??defaultArchiveStaleTeamConfigs,platform:deps?.platform??process.platform,recordAudit:deps?.recordAudit??defaultRecordAudit,log:deps?.log??((line)=>console.log(line))}}async function ensureServeReady(opts){let deps=bindDefaults(opts.deps),health=await deps.collectHealth(),results=[];results.push(await checkPartition(health,opts.autoFix,deps)),results.push(await checkBackfill(opts.autoFix,deps)),results.push(await checkDeadPaneZombies(deps)),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}function formatDuration(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function logMaintenanceStepStart(log,silent,label){if(!silent)log(` ${label}...`);return Date.now()}function logMaintenanceStepDone(log,silent,label,startedAt){if(!silent)log(` done ${label} (${formatDuration(Date.now()-startedAt)})`)}async function runDoctorMaintenance(opts={}){let rawRunBackfillSync=opts.deps?.runBackfillSync??defaultRunBackfillBlocking,deps=bindDefaults({...opts.deps,runBackfillSync:defaultRunBackfillBlocking,log:opts.silent?()=>{}:opts.deps?.log});deps.runBackfillSync=async()=>{let startedAt2=logMaintenanceStepStart(deps.log,opts.silent,"Running session backfill convergence (can take minutes on large transcript history)");try{return await rawRunBackfillSync()}finally{logMaintenanceStepDone(deps.log,opts.silent,"session backfill convergence",startedAt2)}};let startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Collecting observability health"),health=await deps.collectHealth();logMaintenanceStepDone(deps.log,opts.silent,"observability health",startedAt);let results=[];startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking runtime event partitions"),results.push(await checkPartition(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"runtime event partitions",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking watchdog install"),results.push(await checkWatchdog(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"watchdog install",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking session backfill drift"),results.push(await checkBackfill(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"session backfill drift",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking exhausted zombie rows"),results.push(await checkDeadPaneZombies(deps)),logMaintenanceStepDone(deps.log,opts.silent,"exhausted zombie rows",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking Claude team config orphans"),results.push(checkTeamConfigOrphans(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"Claude team config orphans",startedAt),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}async function emitAuditEvents(results,deps){for(let result2 of results)if(result2.status==="fixed")await deps.recordAudit("serve.precondition.fixed",result2.name,{detail:result2.detail??null}).catch(()=>{});else if(result2.status==="refused")await deps.recordAudit("serve.precondition.refused",result2.name,{detail:result2.detail??null,fix_command:result2.fixCommand??null}).catch(()=>{})}function printReport(results,log){log(" Preconditions:");for(let r of results){let tag=statusTag(r.status),suffix=r.detail?` \u2014 ${r.detail}`:"";if(log(` ${tag} ${r.name}${suffix}`),r.status==="refused"&&r.fixCommand)log(` \u2192 ${r.fixCommand}`)}}function statusTag(status){switch(status){case"ok":return"[ok]";case"fixed":return"[fix]";case"refused":return"[!!]";case"skipped":return"[--]"}}var BACKFILL_DRIFT_THRESHOLD_PCT=5,ORPHAN_FRESH_WINDOW_MS=86400000;var init_ensure_ready=__esm(()=>{init_observability_health();init_audit();init_db()});var exports_doctor={};__export(exports_doctor,{runPostUpdateMaintenance:()=>runPostUpdateMaintenance,findStaleGenieCandidates:()=>findStaleGenieCandidates,findBundledTmuxConfigDir:()=>findBundledTmuxConfigDir,doctorCommand:()=>doctorCommand,checkTmuxConfigs:()=>checkTmuxConfigs,checkLegacyAgentFrontmatter:()=>checkLegacyAgentFrontmatter,checkGenieAgentTemplate:()=>checkGenieAgentTemplate});import{execFileSync as execFileSync2}from"child_process";import{copyFileSync as copyFileSync2,existsSync as existsSync20,mkdirSync as mkdirSync10,readFileSync as readFileSync13,readdirSync as readdirSync7,statSync as statSync6,unlinkSync as unlinkSync6,writeFileSync as writeFileSync7}from"fs";import{homedir as homedir20}from"os";import{dirname as dirname8,join as join25,resolve as resolve3}from"path";import{fileURLToPath as fileURLToPath3}from"url";var{$:$3}=globalThis.Bun;function printSectionHeader(title){console.log(),console.log(`\x1B[1m${title}:\x1B[0m`)}function printCheckResult(result2){let icon={pass:"\x1B[32m\u2713\x1B[0m",fail:"\x1B[31m\u2717\x1B[0m",warn:"\x1B[33m!\x1B[0m"}[result2.status],message=result2.message?` ${result2.message}`:"";if(console.log(` ${icon} ${result2.name}${message}`),result2.suggestion)console.log(` \x1B[2m${result2.suggestion}\x1B[0m`)}async function checkPrerequisites(){let results=[],tmuxCheck=await checkCommand("tmux");if(tmuxCheck.exists)results.push({name:"tmux",status:"pass",message:tmuxCheck.version||""});else results.push({name:"tmux",status:"fail",suggestion:"Install with: brew install tmux (or apt install tmux)"});let jqCheck=await checkCommand("jq");if(jqCheck.exists)results.push({name:"jq",status:"pass",message:jqCheck.version||""});else results.push({name:"jq",status:"fail",suggestion:"Install with: brew install jq (or apt install jq)"});let bunCheck=await checkCommand("bun");if(bunCheck.exists)results.push({name:"bun",status:"pass",message:bunCheck.version||""});else results.push({name:"bun",status:"fail",suggestion:"Install with: curl -fsSL https://bun.sh/install | bash"});let claudeCheck=await checkCommand("claude");if(claudeCheck.exists)results.push({name:"Claude Code",status:"pass",message:claudeCheck.version||""});else results.push({name:"Claude Code",status:"warn",suggestion:"Install with: npm install -g @anthropic-ai/claude-code"});let codexCheck=await checkCommand("codex");if(codexCheck.exists)results.push({name:"Codex CLI",status:"pass",message:codexCheck.version||""});else results.push({name:"Codex CLI",status:"warn",suggestion:"Install via OpenAI account; codex is optional unless using --provider codex"});let requiredForSpawn=["genie","bun","node","npm","git","claude","codex"];for(let bin of requiredForSpawn){let interactivePath=await resolveBinaryInteractive(bin),nonInteractivePath=await resolveBinaryNonInteractive(bin);if(interactivePath&&nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"pass",message:nonInteractivePath});else if(interactivePath&&!nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"interactive-only",suggestion:`Move PATH export from ~/.bashrc to ~/.profile so spawn-scripts can resolve ${bin}. (Or use a stable symlink in ~/.local/bin.)`});else if(!interactivePath&&bin!=="codex"){if(bin==="genie"||bin==="node"||bin==="npm"||bin==="git")results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"not in non-interactive PATH",suggestion:`Add ${bin} to ~/.profile (not just ~/.bashrc). Some flows (e.g. genie update) shell out to bare '${bin}' from non-interactive subprocesses.`})}}return results}async function resolveBinaryInteractive(bin){try{return(await $3`command -v ${bin}`.quiet().text()).trim()||null}catch{return null}}async function resolveBinaryNonInteractive(bin){try{return(await $3`sh -c "command -v ${bin}"`.quiet().text()).trim()||null}catch{return null}}async function checkConfiguration(){let results=[];if(genieConfigExists())results.push({name:"Genie config exists",status:"pass",message:contractClaudePath(getGenieConfigPath())});else results.push({name:"Genie config exists",status:"warn",message:"not found",suggestion:"Run: genie setup"});if(isSetupComplete())results.push({name:"Setup complete",status:"pass"});else results.push({name:"Setup complete",status:"warn",message:"not completed",suggestion:"Run: genie setup"});let claudeSettingsPath=getClaudeSettingsPath();if(existsSync20(claudeSettingsPath))results.push({name:"Claude settings exists",status:"pass",message:contractClaudePath(claudeSettingsPath)});else results.push({name:"Claude settings exists",status:"warn",message:"not found",suggestion:"Claude Code creates this on first run"});return results}async function checkTmux(){let results=[];try{if((await $3`${tmuxBin()} -L genie list-sessions 2>/dev/null`.quiet()).exitCode===0)results.push({name:"Server running",status:"pass"});else return results.push({name:"Server running",status:"warn",message:"no sessions",suggestion:"Start with: tmux new-session -d -s genie"}),results}catch{return results.push({name:"Server running",status:"warn",message:"could not check"}),results}let sessionName=(await loadGenieConfig()).session.name;try{if((await $3`${tmuxBin()} -L genie has-session -t ${`=${sessionName}`} 2>/dev/null`.quiet()).exitCode===0)results.push({name:`Session '${sessionName}' exists`,status:"pass"});else results.push({name:`Session '${sessionName}' exists`,status:"warn",suggestion:`Start with: tmux new-session -d -s ${sessionName}`})}catch{results.push({name:`Session '${sessionName}' exists`,status:"warn",message:"could not check"})}return results}async function checkWorkerProfiles(){let results=[];if(!genieConfigExists())return results.push({name:"Worker profiles",status:"warn",message:"no genie config",suggestion:"Run: genie setup"}),results;let config=await loadGenieConfig(),profiles=config.workerProfiles;if(!profiles||Object.keys(profiles).length===0)return results.push({name:"Worker profiles",status:"pass",message:"none configured (using defaults)"}),results;let totalProfiles=Object.keys(profiles).length;results.push({name:"Profiles configured",status:"pass",message:`${totalProfiles} profile${totalProfiles===1?"":"s"}`});for(let name of Object.keys(profiles))results.push({name:`Profile '${name}'`,status:"pass",message:"claude (direct)"});if(config.defaultWorkerProfile)if(profiles[config.defaultWorkerProfile])results.push({name:"Default profile",status:"pass",message:config.defaultWorkerProfile});else results.push({name:"Default profile",status:"warn",message:`'${config.defaultWorkerProfile}' not found`,suggestion:"Run: genie profiles default <profile>"});return results}async function checkBridge(){let results=[];try{let{getBridgeStatus:getBridgeStatus2,removeBridgePidfile:removeBridgePidfile2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2(void 0,{});if(res.state==="stopped")return results.push({name:"Bridge",status:"warn",message:"stopped (no pidfile)",suggestion:"Start the bridge with: genie serve"}),results;if(res.state==="stale"){if(res.pidfile)removeBridgePidfile2();return results.push({name:"Bridge",status:"fail",message:`stale: ${res.detail}`,suggestion:"Restart the bridge with: genie serve restart"}),results}let{pong,pidfile}=res;if(!pong||!pidfile)return results.push({name:"Bridge",status:"warn",message:"running state missing pong/pidfile metadata"}),results;let uptimeSec=Math.round(pong.uptimeMs/1000);results.push({name:"Bridge running",status:"pass",message:`running (pid ${pong.pid}, uptime ${uptimeSec}s)`}),results.push({name:"NATS ping",status:"pass",message:`pong in ${res.latencyMs??0}ms (${pidfile.natsUrl})`}),results.push({name:"Subjects",status:"pass",message:pong.subjects.join(", ")})}catch(err){let detail=err instanceof Error?err.message:String(err);results.push({name:"Bridge module",status:"warn",message:`could not probe bridge: ${detail}`})}return results}function checkGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir))return[];let agentsMd=join25(genieDir,"AGENTS.md"),agentYaml=join25(genieDir,"agent.yaml"),issues=[];if(existsSync20(agentsMd))try{if(readFileSync13(agentsMd,"utf-8").includes(STALE_GENIE_AGENTS_MD_MARKER))issues.push("AGENTS.md uses generic placeholder template")}catch{}if(existsSync20(agentYaml))try{let text=readFileSync13(agentYaml,"utf-8");if(!STALE_GENIE_AGENT_YAML_MISSING_MODEL_REGEX.test(text))issues.push("agent.yaml missing model field (TUI falls back to gray)")}catch{}if(issues.length===0)return[{name:"agents/genie scaffold up to date",status:"pass"}];return[{name:"agents/genie stale scaffold",status:"warn",message:issues.join("; "),suggestion:"Run: genie doctor --fix (re-emits genie specialist templates, preserves user edits)"}]}function findBundledTmuxConfigDir(){try{let moduleDir=dirname8(fileURLToPath3(import.meta.url));for(let i2=0;i2<6;i2+=1){let candidate=resolve3(moduleDir,"../".repeat(i2+1),"scripts","tmux");if(existsSync20(join25(candidate,"tui-tmux.conf"))&&existsSync20(join25(candidate,"genie.tmux.conf")))return candidate}}catch{}return null}function checkTmuxConfigs(){if(!findBundledTmuxConfigDir())return[{name:"tmux configs",status:"pass",message:"bundled configs unavailable (skipped)"}];let home=join25(homedir20(),".genie"),stale=[],expectedSnippet="unbind -n MouseDown3Pane";for(let file of["tui-tmux.conf","tmux.conf"]){let installedPath=join25(home,file);if(!existsSync20(installedPath))continue;try{if(!readFileSync13(installedPath,"utf-8").includes(expectedSnippet))stale.push(file)}catch{stale.push(file)}}if(stale.length===0)return[{name:"~/.genie tmux configs up to date",status:"pass"}];return[{name:"~/.genie tmux configs stale",status:"warn",message:`missing right-click unbind in: ${stale.join(", ")}`,suggestion:"Run: genie doctor --fix (refreshes ~/.genie tmux configs from bundled scripts/tmux/)"}]}function isAgentDirectory(path2){try{return statSync6(path2).isDirectory()}catch{return!1}}function hasLegacyFrontmatter(agentDir){let yamlPath=join25(agentDir,"agent.yaml"),agentsMdPath=join25(agentDir,"AGENTS.md");if(!existsSync20(yamlPath)||!existsSync20(agentsMdPath))return!1;try{return readFileSync13(agentsMdPath,"utf-8").slice(0,4).startsWith("---")}catch{return!1}}function checkLegacyAgentFrontmatter(workspaceRoot){let results=[],root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let agentsDir=join25(root,"agents");if(!existsSync20(agentsDir))return[];let entries;try{entries=readdirSync7(agentsDir)}catch{return[]}for(let name of entries){let agentDir=join25(agentsDir,name);if(!isAgentDirectory(agentDir))continue;if(!hasLegacyFrontmatter(agentDir))continue;results.push({name:`agents/${name}/AGENTS.md`,status:"warn",message:"legacy frontmatter detected (ignored by sync)",suggestion:`Move config into agents/${name}/agent.yaml \u2014 AGENTS.md is prompt-only post-migration.`})}if(results.length===0)results.push({name:"No legacy frontmatter in agents/*/AGENTS.md",status:"pass"});return results}function runCheckSection(label,results,counts){printSectionHeader(label);for(let result2 of results){if(printCheckResult(result2),result2.status==="fail")counts.errors=!0;if(result2.status==="warn")counts.warnings=!0}}async function doctorCommand(options){if(options?.fix){await doctorFix();return}if(options?.fixTeamOrphans){await runFixTeamOrphans({dryRun:Boolean(options.dryRun),json:Boolean(options.json)});return}if(options?.observability){await runObservabilityCheck(Boolean(options.json));return}if(options?.perf){let{runPerfCheck:runPerfCheck2}=await Promise.resolve().then(() => (init_perf_check(),exports_perf_check));await runPerfCheck2(Boolean(options.json));return}console.log(),console.log("\x1B[1mGenie Doctor\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`);let counts={errors:!1,warnings:!1};if(runCheckSection("Prerequisites",await checkPrerequisites(),counts),runCheckSection("Installer Resolution",await collectInstallerResolution(),counts),runCheckSection("Configuration",await checkConfiguration(),counts),runCheckSection("Tmux",await checkTmux(),counts),runCheckSection("Tmux Configs",checkTmuxConfigs(),counts),runCheckSection("Worker Profiles",await checkWorkerProfiles(),counts),runCheckSection("Omni Bridge",await checkBridge(),counts),runCheckSection("Agent Config",checkLegacyAgentFrontmatter(),counts),runCheckSection("Genie Specialist",checkGenieAgentTemplate(),counts),printDoctorSummary(counts),counts.errors)process.exit(1)}function printObservabilityReport(report){if(console.log(),console.log("\x1B[1mObservability Health\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(` partition_health: ${report.partition_health}`),console.log(` partition_count: ${report.partition_count}`),console.log(` next_rotation_at: ${report.next_rotation_at??"n/a"}`),console.log(` oldest_partition: ${report.oldest_partition??"n/a"}`),console.log(` newest_partition: ${report.newest_partition??"n/a"}`),console.log(` GENIE_WIDE_EMIT: ${report.wide_emit_flag}`),report.message)console.log(` note: ${report.message}`);console.log()}async function runObservabilityCheck(json2){let report=await collectObservabilityHealth();if(json2)console.log(JSON.stringify(report,null,2));else printObservabilityReport(report);if(report.partition_health==="fail")process.exit(1)}async function runFixTeamOrphans(opts){let{archiveOrphanTeamConfigs:archiveOrphanTeamConfigs2}=await Promise.resolve().then(() => (init_archive_orphan_team_configs(),exports_archive_orphan_team_configs)),decisions=archiveOrphanTeamConfigs2({dryRun:opts.dryRun});if(opts.json){console.log(JSON.stringify({dryRun:opts.dryRun,decisions},null,2));return}if(decisions.length===0){console.log(" no team config dirs found \u2014 nothing to do");return}for(let d of decisions){let tag=d.classification==="stale"?opts.dryRun?"WOULD ARCHIVE":"ARCHIVED":d.classification.toUpperCase(),tail=d.archivedTo?` \u2192 ${d.archivedTo}`:"";console.log(` [${tag}] ${d.team} \u2014 ${d.reason}${tail}`)}let stale=decisions.filter((d)=>d.classification==="stale").length,active=decisions.filter((d)=>d.classification==="active").length;console.log(`
934
+ `;if(rows.length===0)return{driftPct:null,detail:"no prior backfill row \u2014 first run will seed"};let row=rows[0];if(row.total_bytes<=0)return{driftPct:0,detail:"no JSONL bytes discovered yet"};let pct=Math.max(0,row.total_bytes-row.processed_bytes)/row.total_bytes*100,display=pct.toFixed(1);return{driftPct:pct,detail:`processed ${row.processed_bytes}/${row.total_bytes} bytes (drift ${display}%)`}}catch(err){return{driftPct:null,detail:`drift probe failed: ${err.message}`}}}async function defaultRunBackfillSync(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};return(async()=>{try{let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql)}catch{}})(),{ranSync:!0,driftPct:null,detail:"background convergence kicked \u2014 `genie doctor --fix` to wait"}}async function defaultRunBackfillBlocking(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql);let after=await defaultMeasureBackfillDrift();return{ranSync:!0,driftPct:after.driftPct,detail:after.detail}}async function defaultListOrphanedZombies(){try{let{listExhaustedZombies:listExhaustedZombies2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return await listExhaustedZombies2()}catch{return[]}}function safeIsDirectory(path2){try{return statSync5(path2).isDirectory()}catch{return!1}}function summarizeInboxes(inboxesDir2){let inboxFiles=[];try{inboxFiles=readdirSync6(inboxesDir2).filter((f)=>f.endsWith(".json"))}catch{return{newestMs:null,hasContent:!1}}let newestMs=null,hasContent=!1;for(let f of inboxFiles)try{let st=statSync5(join24(inboxesDir2,f));if(newestMs===null||st.mtimeMs>newestMs)newestMs=st.mtimeMs;if(st.size>2)hasContent=!0}catch{}return{newestMs,hasContent}}function classifyTeamDir2(name,base,now){let dir=join24(base,name);if(!safeIsDirectory(dir))return null;if(existsSync19(join24(dir,"config.json")))return null;let inboxesDir2=join24(dir,"inboxes");if(!safeIsDirectory(inboxesDir2))return null;let{newestMs,hasContent}=summarizeInboxes(inboxesDir2),orphan={teamName:name,path:dir,newestInboxMs:newestMs,hasContent},active=hasContent&&newestMs!==null&&now-newestMs<ORPHAN_FRESH_WINDOW_MS;return{orphan,active}}function defaultScanTeamConfigOrphans(){let base=teamsBaseDir3(),result2={active:[],stale:[]};if(!existsSync19(base))return result2;let now=Date.now();for(let name of readdirSync6(base)){if(name.startsWith(".")||name==="_archive")continue;let classified=classifyTeamDir2(name,base,now);if(!classified)continue;(classified.active?result2.active:result2.stale).push(classified.orphan)}return result2}function defaultArchiveStaleTeamConfigs(orphans){if(orphans.length===0)return[];let archiveRoot=join24(teamsBaseDir3(),"_archive");mkdirSync9(archiveRoot,{recursive:!0});let ts3=new Date().toISOString().replace(/[:.]/g,"-"),archived=[];for(let o of orphans){let dest=join24(archiveRoot,`${o.teamName}-${ts3}`);try{renameSync4(o.path,dest),archived.push(dest)}catch{}}return archived}async function defaultRecordAudit(eventType,name,details){await recordAuditEvent("command","serve_start",eventType,"serve",{precondition:name,...details})}async function checkPartition(health,autoFix,deps){if(health.partition_health==="ok"||health.partition_health==="warn")return{name:"partition",status:"ok",detail:health.next_rotation_at?`next rotation: ${health.next_rotation_at}`:void 0};if(health.partition_health==="unknown")return{name:"partition",status:"skipped",detail:"pg unavailable \u2014 skipping partition probe"};if(!autoFix)return{name:"partition",status:"refused",detail:"today's partition is missing or rotation is overdue",fixCommand:"genie doctor --observability # then re-run; or `genie serve start` (without --no-fix)"};let result2=await deps.runPartitionMaintenance();return{name:"partition",status:"fixed",detail:`created/present ${result2.createdOrPresent}, dropped ${result2.dropped}; next rotation ${result2.nextRotationAt??"unknown"}`}}async function checkWatchdog(health,autoFix,deps){if(deps.platform!=="linux")return{name:"watchdog",status:"skipped",detail:"watchdog install is Linux/systemd only"};if(health.watchdog==="ok")return{name:"watchdog",status:"ok"};if(!autoFix)return{name:"watchdog",status:"refused",detail:health.watchdog_detail??"watchdog units missing",fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"};try{let result2=await deps.installWatchdog();return{name:"watchdog",status:"fixed",detail:`wrote ${result2.filesWritten.length}, skipped ${result2.filesSkipped.length}`}}catch(err){return{name:"watchdog",status:"refused",detail:`auto-install failed: ${err.message}`,fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"}}}async function checkBackfill(autoFix,deps){let drift=await deps.measureBackfillDrift();if(drift.driftPct===null)return{name:"backfill",status:"skipped",detail:drift.detail};if(drift.driftPct<BACKFILL_DRIFT_THRESHOLD_PCT)return{name:"backfill",status:"ok",detail:drift.detail};if(!autoFix)return{name:"backfill",status:"refused",detail:drift.detail,fixCommand:`genie sessions sync # drift ${drift.driftPct.toFixed(1)}% > ${BACKFILL_DRIFT_THRESHOLD_PCT}%`};return{name:"backfill",status:"fixed",detail:(await deps.runBackfillSync()).detail}}async function checkDeadPaneZombies(deps){let orphans=await deps.listOrphanedZombies();if(orphans.length===0)return{name:"dead_pane_zombies",status:"ok"};return{name:"dead_pane_zombies",status:"refused",detail:`${orphans.length} exhausted zombie(s) past TTL; visible in \`genie status\``,fixCommand:"genie prune --zombies # archive eligible rows"}}function checkTeamConfigOrphans(autoFix,deps){let scan=deps.scanTeamConfigOrphans();if(scan.active.length===0&&scan.stale.length===0)return{name:"team_config_orphans",status:"ok"};if(!autoFix){let summary=`active=${scan.active.length} stale=${scan.stale.length}`,firstActive=scan.active[0]?.teamName;return{name:"team_config_orphans",status:"refused",detail:summary,fixCommand:firstActive?`genie team repair ${firstActive} # active orphan; stale dirs archive on auto-fix`:"genie serve start # auto-fix archives stale orphans"}}let archivedPaths=deps.archiveStaleTeamConfigs(scan.stale);if(scan.active.length>0)return{name:"team_config_orphans",status:"refused",detail:`archived ${archivedPaths.length} stale; ${scan.active.length} active orphan(s) need repair`,fixCommand:`genie team repair ${scan.active[0].teamName}`};return{name:"team_config_orphans",status:"fixed",detail:`archived ${archivedPaths.length} stale orphan(s)`}}function bindDefaults(deps){return{collectHealth:deps?.collectHealth??collectObservabilityHealth,runPartitionMaintenance:deps?.runPartitionMaintenance??defaultRunPartitionMaintenance,installWatchdog:deps?.installWatchdog??defaultInstallWatchdog,runBackfillSync:deps?.runBackfillSync??defaultRunBackfillSync,measureBackfillDrift:deps?.measureBackfillDrift??defaultMeasureBackfillDrift,listOrphanedZombies:deps?.listOrphanedZombies??defaultListOrphanedZombies,scanTeamConfigOrphans:deps?.scanTeamConfigOrphans??defaultScanTeamConfigOrphans,archiveStaleTeamConfigs:deps?.archiveStaleTeamConfigs??defaultArchiveStaleTeamConfigs,platform:deps?.platform??process.platform,recordAudit:deps?.recordAudit??defaultRecordAudit,log:deps?.log??((line)=>console.log(line))}}async function ensureServeReady(opts){let deps=bindDefaults(opts.deps),health=await deps.collectHealth(),results=[];results.push(await checkPartition(health,opts.autoFix,deps)),results.push(await checkBackfill(opts.autoFix,deps)),results.push(await checkDeadPaneZombies(deps)),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}function formatDuration(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function logMaintenanceStepStart(log,silent,label){if(!silent)log(` ${label}...`);return Date.now()}function logMaintenanceStepDone(log,silent,label,startedAt){if(!silent)log(` done ${label} (${formatDuration(Date.now()-startedAt)})`)}async function runDoctorMaintenance(opts={}){let rawRunBackfillSync=opts.deps?.runBackfillSync??defaultRunBackfillBlocking,deps=bindDefaults({...opts.deps,runBackfillSync:defaultRunBackfillBlocking,log:opts.silent?()=>{}:opts.deps?.log});deps.runBackfillSync=async()=>{let startedAt2=logMaintenanceStepStart(deps.log,opts.silent,"Running session backfill convergence (can take minutes on large transcript history)");try{return await rawRunBackfillSync()}finally{logMaintenanceStepDone(deps.log,opts.silent,"session backfill convergence",startedAt2)}};let startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Collecting observability health"),health=await deps.collectHealth();logMaintenanceStepDone(deps.log,opts.silent,"observability health",startedAt);let results=[];startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking runtime event partitions"),results.push(await checkPartition(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"runtime event partitions",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking watchdog install"),results.push(await checkWatchdog(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"watchdog install",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking session backfill drift"),results.push(await checkBackfill(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"session backfill drift",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking exhausted zombie rows"),results.push(await checkDeadPaneZombies(deps)),logMaintenanceStepDone(deps.log,opts.silent,"exhausted zombie rows",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking Claude team config orphans"),results.push(checkTeamConfigOrphans(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"Claude team config orphans",startedAt),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}async function emitAuditEvents(results,deps){for(let result2 of results)if(result2.status==="fixed")await deps.recordAudit("serve.precondition.fixed",result2.name,{detail:result2.detail??null}).catch(()=>{});else if(result2.status==="refused")await deps.recordAudit("serve.precondition.refused",result2.name,{detail:result2.detail??null,fix_command:result2.fixCommand??null}).catch(()=>{})}function printReport(results,log){log(" Preconditions:");for(let r of results){let tag=statusTag(r.status),suffix=r.detail?` \u2014 ${r.detail}`:"";if(log(` ${tag} ${r.name}${suffix}`),r.status==="refused"&&r.fixCommand)log(` \u2192 ${r.fixCommand}`)}}function statusTag(status){switch(status){case"ok":return"[ok]";case"fixed":return"[fix]";case"refused":return"[!!]";case"skipped":return"[--]"}}var BACKFILL_DRIFT_THRESHOLD_PCT=5,ORPHAN_FRESH_WINDOW_MS=86400000;var init_ensure_ready=__esm(()=>{init_observability_health();init_audit();init_db()});var exports_doctor={};__export(exports_doctor,{runPostUpdateMaintenance:()=>runPostUpdateMaintenance,findStaleGenieCandidates:()=>findStaleGenieCandidates,findBundledTmuxConfigDir:()=>findBundledTmuxConfigDir,doctorCommand:()=>doctorCommand,checkTmuxConfigs:()=>checkTmuxConfigs,checkLegacyAgentFrontmatter:()=>checkLegacyAgentFrontmatter,checkGenieAgentTemplate:()=>checkGenieAgentTemplate});import{execFileSync as execFileSync2}from"child_process";import{copyFileSync as copyFileSync2,existsSync as existsSync20,mkdirSync as mkdirSync10,readFileSync as readFileSync13,readdirSync as readdirSync7,statSync as statSync6,unlinkSync as unlinkSync6,writeFileSync as writeFileSync7}from"fs";import{homedir as homedir20}from"os";import{dirname as dirname8,join as join25,resolve as resolve3}from"path";import{fileURLToPath as fileURLToPath3}from"url";var{$:$3}=globalThis.Bun;function printSectionHeader(title){console.log(),console.log(`\x1B[1m${title}:\x1B[0m`)}function printCheckResult(result2){let icon={pass:"\x1B[32m\u2713\x1B[0m",fail:"\x1B[31m\u2717\x1B[0m",warn:"\x1B[33m!\x1B[0m"}[result2.status],message=result2.message?` ${result2.message}`:"";if(console.log(` ${icon} ${result2.name}${message}`),result2.suggestion)console.log(` \x1B[2m${result2.suggestion}\x1B[0m`)}async function checkPrerequisites(){let results=[],tmuxCheck=await checkCommand("tmux");if(tmuxCheck.exists)results.push({name:"tmux",status:"pass",message:tmuxCheck.version||""});else results.push({name:"tmux",status:"fail",suggestion:"Install with: brew install tmux (or apt install tmux)"});let jqCheck=await checkCommand("jq");if(jqCheck.exists)results.push({name:"jq",status:"pass",message:jqCheck.version||""});else results.push({name:"jq",status:"fail",suggestion:"Install with: brew install jq (or apt install jq)"});let bunCheck=await checkCommand("bun");if(bunCheck.exists)results.push({name:"bun",status:"pass",message:bunCheck.version||""});else results.push({name:"bun",status:"fail",suggestion:"Install with: curl -fsSL https://bun.sh/install | bash"});let claudeCheck=await checkCommand("claude");if(claudeCheck.exists)results.push({name:"Claude Code",status:"pass",message:claudeCheck.version||""});else results.push({name:"Claude Code",status:"warn",suggestion:"Install with: npm install -g @anthropic-ai/claude-code"});let codexCheck=await checkCommand("codex");if(codexCheck.exists)results.push({name:"Codex CLI",status:"pass",message:codexCheck.version||""});else results.push({name:"Codex CLI",status:"warn",suggestion:"Install via OpenAI account; codex is optional unless using --provider codex"});let requiredForSpawn=["genie","bun","node","npm","git","claude","codex"];for(let bin of requiredForSpawn){let interactivePath=await resolveBinaryInteractive(bin),nonInteractivePath=await resolveBinaryNonInteractive(bin);if(interactivePath&&nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"pass",message:nonInteractivePath});else if(interactivePath&&!nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"interactive-only",suggestion:`Move PATH export from ~/.bashrc to ~/.profile so spawn-scripts can resolve ${bin}. (Or use a stable symlink in ~/.local/bin.)`});else if(!interactivePath&&bin!=="codex"){if(bin==="genie"||bin==="node"||bin==="npm"||bin==="git")results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"not in non-interactive PATH",suggestion:`Add ${bin} to ~/.profile (not just ~/.bashrc). Some flows (e.g. genie update) shell out to bare '${bin}' from non-interactive subprocesses.`})}}return results}async function resolveBinaryInteractive(bin){try{return(await $3`command -v ${bin}`.quiet().text()).trim()||null}catch{return null}}async function resolveBinaryNonInteractive(bin){try{return(await $3`sh -c "command -v ${bin}"`.quiet().text()).trim()||null}catch{return null}}async function checkConfiguration(){let results=[];if(genieConfigExists())results.push({name:"Genie config exists",status:"pass",message:contractClaudePath(getGenieConfigPath())});else results.push({name:"Genie config exists",status:"warn",message:"not found",suggestion:"Run: genie setup"});if(isSetupComplete())results.push({name:"Setup complete",status:"pass"});else results.push({name:"Setup complete",status:"warn",message:"not completed",suggestion:"Run: genie setup"});let claudeSettingsPath=getClaudeSettingsPath();if(existsSync20(claudeSettingsPath))results.push({name:"Claude settings exists",status:"pass",message:contractClaudePath(claudeSettingsPath)});else results.push({name:"Claude settings exists",status:"warn",message:"not found",suggestion:"Claude Code creates this on first run"});return results}async function checkTmux(){let results=[];try{if((await $3`${tmuxBin()} -L genie list-sessions 2>/dev/null`.quiet()).exitCode===0)results.push({name:"Server running",status:"pass"});else return results.push({name:"Server running",status:"warn",message:"no sessions",suggestion:"Start with: tmux new-session -d -s genie"}),results}catch{return results.push({name:"Server running",status:"warn",message:"could not check"}),results}let sessionName=(await loadGenieConfig()).session.name;try{if((await $3`${tmuxBin()} -L genie has-session -t ${`=${sessionName}`} 2>/dev/null`.quiet()).exitCode===0)results.push({name:`Session '${sessionName}' exists`,status:"pass"});else results.push({name:`Session '${sessionName}' exists`,status:"warn",suggestion:`Start with: tmux new-session -d -s ${sessionName}`})}catch{results.push({name:`Session '${sessionName}' exists`,status:"warn",message:"could not check"})}return results}async function checkWorkerProfiles(){let results=[];if(!genieConfigExists())return results.push({name:"Worker profiles",status:"warn",message:"no genie config",suggestion:"Run: genie setup"}),results;let config=await loadGenieConfig(),profiles=config.workerProfiles;if(!profiles||Object.keys(profiles).length===0)return results.push({name:"Worker profiles",status:"pass",message:"none configured (using defaults)"}),results;let totalProfiles=Object.keys(profiles).length;results.push({name:"Profiles configured",status:"pass",message:`${totalProfiles} profile${totalProfiles===1?"":"s"}`});for(let name of Object.keys(profiles))results.push({name:`Profile '${name}'`,status:"pass",message:"claude (direct)"});if(config.defaultWorkerProfile)if(profiles[config.defaultWorkerProfile])results.push({name:"Default profile",status:"pass",message:config.defaultWorkerProfile});else results.push({name:"Default profile",status:"warn",message:`'${config.defaultWorkerProfile}' not found`,suggestion:"Run: genie profiles default <profile>"});return results}async function checkBridge(){let results=[];try{let{getBridgeStatus:getBridgeStatus2,removeBridgePidfile:removeBridgePidfile2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2(void 0,{});if(res.state==="stopped")return results.push({name:"Bridge",status:"warn",message:"stopped (no pidfile)",suggestion:"Start the bridge with: genie serve"}),results;if(res.state==="stale"){if(res.pidfile)removeBridgePidfile2();return results.push({name:"Bridge",status:"fail",message:`stale: ${res.detail}`,suggestion:"Restart the bridge with: genie serve restart"}),results}let{pong,pidfile}=res;if(!pong||!pidfile)return results.push({name:"Bridge",status:"warn",message:"running state missing pong/pidfile metadata"}),results;let uptimeSec=Math.round(pong.uptimeMs/1000);results.push({name:"Bridge running",status:"pass",message:`running (pid ${pong.pid}, uptime ${uptimeSec}s)`}),results.push({name:"NATS ping",status:"pass",message:`pong in ${res.latencyMs??0}ms (${pidfile.natsUrl})`}),results.push({name:"Subjects",status:"pass",message:pong.subjects.join(", ")})}catch(err){let detail=err instanceof Error?err.message:String(err);results.push({name:"Bridge module",status:"warn",message:`could not probe bridge: ${detail}`})}return results}function checkGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir))return[];let agentsMd=join25(genieDir,"AGENTS.md"),agentYaml=join25(genieDir,"agent.yaml"),issues=[];if(existsSync20(agentsMd))try{if(readFileSync13(agentsMd,"utf-8").includes(STALE_GENIE_AGENTS_MD_MARKER))issues.push("AGENTS.md uses generic placeholder template")}catch{}if(existsSync20(agentYaml))try{let text=readFileSync13(agentYaml,"utf-8");if(!STALE_GENIE_AGENT_YAML_MISSING_MODEL_REGEX.test(text))issues.push("agent.yaml missing model field (TUI falls back to gray)")}catch{}if(issues.length===0)return[{name:"agents/genie scaffold up to date",status:"pass"}];return[{name:"agents/genie stale scaffold",status:"warn",message:issues.join("; "),suggestion:"Run: genie doctor --fix (re-emits genie specialist templates, preserves user edits)"}]}function findBundledTmuxConfigDir(){try{let moduleDir=dirname8(fileURLToPath3(import.meta.url));for(let i2=0;i2<6;i2+=1){let candidate=resolve3(moduleDir,"../".repeat(i2+1),"scripts","tmux");if(existsSync20(join25(candidate,"tui-tmux.conf"))&&existsSync20(join25(candidate,"genie.tmux.conf")))return candidate}}catch{}return null}function checkTmuxConfigs(){if(!findBundledTmuxConfigDir())return[{name:"tmux configs",status:"pass",message:"bundled configs unavailable (skipped)"}];let home=join25(homedir20(),".genie"),stale=[],expectedSnippet="unbind -n MouseDown3Pane";for(let file of["tui-tmux.conf","tmux.conf"]){let installedPath=join25(home,file);if(!existsSync20(installedPath))continue;try{if(!readFileSync13(installedPath,"utf-8").includes(expectedSnippet))stale.push(file)}catch{stale.push(file)}}if(stale.length===0)return[{name:"~/.genie tmux configs up to date",status:"pass"}];return[{name:"~/.genie tmux configs stale",status:"warn",message:`missing right-click unbind in: ${stale.join(", ")}`,suggestion:"Run: genie doctor --fix (refreshes ~/.genie tmux configs from bundled scripts/tmux/)"}]}function isAgentDirectory(path2){try{return statSync6(path2).isDirectory()}catch{return!1}}function hasLegacyFrontmatter(agentDir){let yamlPath=join25(agentDir,"agent.yaml"),agentsMdPath=join25(agentDir,"AGENTS.md");if(!existsSync20(yamlPath)||!existsSync20(agentsMdPath))return!1;try{return readFileSync13(agentsMdPath,"utf-8").slice(0,4).startsWith("---")}catch{return!1}}function checkLegacyAgentFrontmatter(workspaceRoot){let results=[],root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let agentsDir=join25(root,"agents");if(!existsSync20(agentsDir))return[];let entries;try{entries=readdirSync7(agentsDir)}catch{return[]}for(let name of entries){let agentDir=join25(agentsDir,name);if(!isAgentDirectory(agentDir))continue;if(!hasLegacyFrontmatter(agentDir))continue;results.push({name:`agents/${name}/AGENTS.md`,status:"warn",message:"legacy frontmatter detected (ignored by sync)",suggestion:`Move config into agents/${name}/agent.yaml \u2014 AGENTS.md is prompt-only post-migration.`})}if(results.length===0)results.push({name:"No legacy frontmatter in agents/*/AGENTS.md",status:"pass"});return results}async function checkPgserveCanonical(){let results=[],canonicalPort=null;try{let out=execFileSync2("pgserve",["port"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=Number.parseInt(out.trim(),10);if(Number.isFinite(parsed)&&parsed>0&&parsed<=65535)canonicalPort=parsed}catch{}if(canonicalPort===null)return results.push({name:"pgserve binary",status:"warn",message:"not on PATH (or `pgserve port` failed)",suggestion:"Install canonical pgserve: bun add -g pgserve@^2.1.0 (then run `pgserve install` to register under pm2)"}),results;results.push({name:"pgserve binary",status:"pass",message:`canonical port ${canonicalPort}`});try{let status=execFileSync2("pgserve",["status","--json"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=JSON.parse(status);if(parsed.installed===!0&&parsed.status==="online")results.push({name:"pgserve under pm2",status:"pass",message:`online \u2014 shared backbone for genie-serve + omni-api on :${canonicalPort}`});else if(parsed.installed===!0)results.push({name:"pgserve under pm2",status:"warn",message:`registered but status=${parsed.status??"unknown"}`,suggestion:"Recover with: pm2 restart pgserve (logs: ~/.pgserve/logs/)"});else results.push({name:"pgserve under pm2",status:"warn",message:"binary present but not registered under pm2",suggestion:"Register canonical pgserve: pgserve install"})}catch{results.push({name:"pgserve under pm2",status:"warn",message:"`pgserve status` failed (pm2 unreachable?)",suggestion:"Verify pm2: pm2 list | Re-register: pgserve install"})}return results}function runCheckSection(label,results,counts){printSectionHeader(label);for(let result2 of results){if(printCheckResult(result2),result2.status==="fail")counts.errors=!0;if(result2.status==="warn")counts.warnings=!0}}async function doctorCommand(options){if(options?.fix){await doctorFix();return}if(options?.fixTeamOrphans){await runFixTeamOrphans({dryRun:Boolean(options.dryRun),json:Boolean(options.json)});return}if(options?.observability){await runObservabilityCheck(Boolean(options.json));return}if(options?.perf){let{runPerfCheck:runPerfCheck2}=await Promise.resolve().then(() => (init_perf_check(),exports_perf_check));await runPerfCheck2(Boolean(options.json));return}console.log(),console.log("\x1B[1mGenie Doctor\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`);let counts={errors:!1,warnings:!1};if(runCheckSection("Prerequisites",await checkPrerequisites(),counts),runCheckSection("Installer Resolution",await collectInstallerResolution(),counts),runCheckSection("Configuration",await checkConfiguration(),counts),runCheckSection("Tmux",await checkTmux(),counts),runCheckSection("Tmux Configs",checkTmuxConfigs(),counts),runCheckSection("Worker Profiles",await checkWorkerProfiles(),counts),runCheckSection("Pgserve (canonical backbone)",await checkPgserveCanonical(),counts),runCheckSection("Omni Bridge",await checkBridge(),counts),runCheckSection("Agent Config",checkLegacyAgentFrontmatter(),counts),runCheckSection("Genie Specialist",checkGenieAgentTemplate(),counts),printDoctorSummary(counts),counts.errors)process.exit(1)}function printObservabilityReport(report){if(console.log(),console.log("\x1B[1mObservability Health\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(` partition_health: ${report.partition_health}`),console.log(` partition_count: ${report.partition_count}`),console.log(` next_rotation_at: ${report.next_rotation_at??"n/a"}`),console.log(` oldest_partition: ${report.oldest_partition??"n/a"}`),console.log(` newest_partition: ${report.newest_partition??"n/a"}`),console.log(` GENIE_WIDE_EMIT: ${report.wide_emit_flag}`),report.message)console.log(` note: ${report.message}`);console.log()}async function runObservabilityCheck(json2){let report=await collectObservabilityHealth();if(json2)console.log(JSON.stringify(report,null,2));else printObservabilityReport(report);if(report.partition_health==="fail")process.exit(1)}async function runFixTeamOrphans(opts){let{archiveOrphanTeamConfigs:archiveOrphanTeamConfigs2}=await Promise.resolve().then(() => (init_archive_orphan_team_configs(),exports_archive_orphan_team_configs)),decisions=archiveOrphanTeamConfigs2({dryRun:opts.dryRun});if(opts.json){console.log(JSON.stringify({dryRun:opts.dryRun,decisions},null,2));return}if(decisions.length===0){console.log(" no team config dirs found \u2014 nothing to do");return}for(let d of decisions){let tag=d.classification==="stale"?opts.dryRun?"WOULD ARCHIVE":"ARCHIVED":d.classification.toUpperCase(),tail=d.archivedTo?` \u2192 ${d.archivedTo}`:"";console.log(` [${tag}] ${d.team} \u2014 ${d.reason}${tail}`)}let stale=decisions.filter((d)=>d.classification==="stale").length,active=decisions.filter((d)=>d.classification==="active").length;console.log(`
935
935
  ${decisions.length} dirs inspected, ${stale} archived, ${active} flagged active.`)}function printDoctorSummary(counts){if(console.log(),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),counts.errors)console.log("\x1B[31mSome checks failed.\x1B[0m Run \x1B[36mgenie setup\x1B[0m to fix.");else if(counts.warnings)console.log("\x1B[33mSome warnings detected.\x1B[0m Everything should still work.");else console.log("\x1B[32mAll checks passed!\x1B[0m");console.log()}function legacyPgserveRepairEnabled(){return process.env.GENIE_PG_FORCE_TCP==="1"||process.env.GENIE_DOCTOR_FIX_LEGACY_PGSERVE==="1"}async function killStalePostgres(genieHome3){if(!legacyPgserveRepairEnabled()){console.log(" Skipping legacy pgserve v1 process cleanup (v1/v2 coexistence)");return}console.log(" Killing stale Genie legacy pgserve processes...");try{let{execSync:execSync4}=await import("child_process"),escaped=join25(genieHome3,"data","pgserve").replace(/\//g,"\\/");execSync4(`pkill -9 -f "postgres.*${escaped}" 2>/dev/null || true`,{stdio:"ignore",timeout:5000}),execSync4(`pkill -9 -f "pgserve.*${escaped}" 2>/dev/null || true`,{stdio:"ignore",timeout:5000}),console.log(" \x1B[32m\u2713\x1B[0m Stale Genie legacy pgserve processes killed")}catch{console.log(" \x1B[33m!\x1B[0m Could not kill stale Genie legacy pgserve processes")}}async function cleanSharedMemory(){console.log(" Cleaning shared memory...");try{let{execSync:execSync4}=await import("child_process");if(process.platform==="darwin")execSync4("ipcs -m 2>/dev/null | awk '/^m/ {print $2}' | xargs -I{} ipcrm -m {} 2>/dev/null || true",{stdio:"ignore",timeout:5000});else execSync4("ipcs -m 2>/dev/null | awk '$6 == 0 {print $2}' | xargs -I{} ipcrm -m {} 2>/dev/null || true",{stdio:"ignore",timeout:5000});console.log(" \x1B[32m\u2713\x1B[0m Shared memory cleaned")}catch{console.log(" \x1B[32m\u2713\x1B[0m No stale shared memory")}}async function stopExistingDaemon(pidFile){try{let{readFileSync:readFileSync14}=await import("fs"),pid=Number.parseInt(readFileSync14(pidFile,"utf-8").trim(),10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,"SIGTERM"),console.log(` \x1B[32m\u2713\x1B[0m Stopped existing daemon (PID ${pid})`),await new Promise((r)=>setTimeout(r,1000))}catch{}}catch{}}function removeStaleFiles(genieHome3,pidFile){let files=[pidFile];if(legacyPgserveRepairEnabled())files.push(join25(genieHome3,"pgserve.port"),join25(genieHome3,"data","pgserve","postmaster.pid"));else console.log(" Leaving legacy pgserve v1 port/data files untouched (v1/v2 coexistence)");for(let file of files)if(existsSync20(file))try{unlinkSync6(file),console.log(` \x1B[32m\u2713\x1B[0m Removed ${file}`)}catch{console.log(` \x1B[33m!\x1B[0m Could not remove ${file}`)}}async function restartDaemon(){console.log(" Restarting daemon...");try{let{spawn:spawn2}=await import("child_process"),bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn2(bunPath,[genieBin,"daemon","start"],{detached:!0,stdio:"ignore"}).unref(),await new Promise((resolve4)=>setTimeout(resolve4,2000)),console.log(" \x1B[32m\u2713\x1B[0m Daemon restart initiated")}catch{console.log(" \x1B[33m!\x1B[0m Could not restart daemon \u2014 run: genie daemon start")}}function isAgentsMdUserEdited(target,fresh){if(!existsSync20(target))return!1;try{let current=readFileSync13(target,"utf-8");if(current.trim().length===0)return!1;if(current.includes(STALE_GENIE_AGENTS_MD_MARKER))return!1;return current!==fresh}catch{return!1}}function writeGenieTemplate(targetDir,name,content){let target=join25(targetDir,name),userEdited=name==="AGENTS.md"&&isAgentsMdUserEdited(target,content),writeTo=userEdited?`${target}.new`:target;try{writeFileSync7(writeTo,content);let marker=userEdited?`wrote ${name}.new (user edits preserved \u2014 merge manually)`:`wrote ${name}`;console.log(` \x1B[32m\u2713\x1B[0m ${marker}`)}catch(err){let detail=err instanceof Error?err.message:String(err);console.log(` \x1B[31m\u2717\x1B[0m ${name}: ${detail}`)}}function fixGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root){console.log(" \x1B[33m!\x1B[0m No workspace detected \u2014 skipping genie-template repair");return}let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir)){console.log(" \x1B[2m\xB7\x1B[0m No agents/genie directory \u2014 skipping");return}console.log(" Refreshing agents/genie scaffold..."),writeGenieTemplate(genieDir,"AGENTS.md",GENIE_AGENTS_TEMPLATE),writeGenieTemplate(genieDir,"SOUL.md",GENIE_SOUL_TEMPLATE),writeGenieTemplate(genieDir,"HEARTBEAT.md",GENIE_HEARTBEAT_TEMPLATE)}function ensureGenieHomeDir(home){if(existsSync20(home))return!0;try{return mkdirSync10(home,{recursive:!0}),!0}catch{return console.log(` \x1B[31m\u2717\x1B[0m Could not create ${home}`),!1}}function refreshTmuxConfFile(bundledDir,home,srcFile,dstFile){let src=join25(bundledDir,srcFile),dst=join25(home,dstFile);if(!existsSync20(src))return;if(existsSync20(dst))try{copyFileSync2(dst,`${dst}.bak`)}catch{}try{copyFileSync2(src,dst),console.log(` \x1B[32m\u2713\x1B[0m wrote ${dstFile} (previous saved as ${dstFile}.bak)`)}catch(err){let detail=err instanceof Error?err.message:String(err);console.log(` \x1B[31m\u2717\x1B[0m ${dstFile}: ${detail}`)}}function fixTmuxConfigs(){let bundledDir=findBundledTmuxConfigDir();if(!bundledDir){console.log(" \x1B[33m!\x1B[0m Bundled tmux configs not found \u2014 skipping");return}let home=join25(homedir20(),".genie");if(!ensureGenieHomeDir(home))return;console.log(" Refreshing ~/.genie tmux configs..."),refreshTmuxConfFile(bundledDir,home,"tui-tmux.conf","tui-tmux.conf"),refreshTmuxConfFile(bundledDir,home,"genie.tmux.conf","tmux.conf")}async function runMaintenancePreconditions(silent=!1,log){try{let{runDoctorMaintenance:runDoctorMaintenance2}=await Promise.resolve().then(() => (init_ensure_ready(),exports_ensure_ready));await runDoctorMaintenance2({silent,deps:log?{log}:void 0})}catch(err){let msg=err instanceof Error?err.message:String(err);if(!silent)console.warn(` Maintenance preconditions skipped: ${msg}`)}}async function doctorFix(){console.log(`
936
936
  \x1B[1mGenie Doctor \u2014 Auto Fix\x1B[0m`),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m
937
937
  `);let genieHome3=process.env.GENIE_HOME??join25(homedir20(),".genie");await killStalePostgres(genieHome3),await cleanSharedMemory();let pidFile=join25(genieHome3,"scheduler.pid");await stopExistingDaemon(pidFile),removeStaleFiles(genieHome3,pidFile),fixGenieAgentTemplate(),fixTmuxConfigs(),await runMaintenancePreconditions(),await restartDaemon(),console.log(`
@@ -2382,7 +2382,7 @@ ${body}`;writeFileSync16(filePath,output,"utf-8")}function serializeSdkConfig(sd
2382
2382
  `;return new Set(rows.map((r)=>r.id))}function makeAgentToolBypassDetector(deps){let windowMs=deps.activeWindowMs??DEFAULT_ACTIVE_WINDOW_MS,maxOrphans=deps.maxOrphansPerEvent??DEFAULT_MAX_ORPHANS_PER_EVENT;return{id:"rot.agent-tool-bypass",version:"1.0.0",riskClass:"medium",async query(){let nowMs=deps.now(),active=(await deps.listTranscripts()).filter((t)=>nowMs-t.mtime_ms<=windowMs),ids=active.map((t)=>t.agent_id),known=ids.length===0?new Set:await deps.checkAgentIds(ids),orphans=[];for(let t of active){if(known.has(t.agent_id))continue;orphans.push({agent_id:t.agent_id,transcript_path:t.transcript_path,size_bytes:t.size_bytes,mtime_iso:new Date(t.mtime_ms).toISOString()})}return{orphans,scanned_count:active.length}},shouldFire(state){return state.orphans.length>0},render(state){let head=state.orphans.slice(0,maxOrphans),first=head[0];return{type:"rot.detected",subject:first?.agent_id??"unknown",payload:{pattern_id:"pattern-10-agent-tool-bypass",entity_id:first?.agent_id??"unknown",observed_state_json:{agent_id:first?.agent_id??"unknown",transcript_path:first?.transcript_path??"",size_bytes:first?.size_bytes??0,mtime_iso:first?.mtime_iso??"",detected_at:new Date(deps.now()).toISOString(),orphan_count:state.orphans.length,scanned_count:state.scanned_count,agent_ids:head.map((o)=>o.agent_id),transcript_paths:head.map((o)=>o.transcript_path),sizes_bytes:head.map((o)=>o.size_bytes)}}}}}}var DEFAULT_ACTIVE_WINDOW_MS=600000,DEFAULT_MAX_ORPHANS_PER_EVENT=32,DEFAULT_SEARCH_ROOT="/tmp",DEFAULT_SEARCH_PREFIX="claude-",DEFAULT_WALK_DEPTH=4,agentToolBypassDetector;var init_pattern_10_agent_tool_bypass=__esm(()=>{init_db();init_detectors();agentToolBypassDetector=makeAgentToolBypassDetector({listTranscripts:defaultListTranscripts,checkAgentIds:defaultCheckAgentIds,now:()=>Date.now()});registerDetector(agentToolBypassDetector)});var exports_built_in={};var init_built_in=__esm(()=>{init_pattern_1_backfill_no_worktree();init_pattern_4_duplicate_agents();init_pattern_5_zombie_team_lead();init_pattern_2_team_ls_drift();init_pattern_3_anchor_orphan();init_pattern_6_subagent_cascade();init_pattern_7_dispatch_silent_drop();init_pattern_8_session_reuse_ghost();init_pattern_9_team_unpushed_orphaned_worktree();init_pattern_10_agent_tool_bypass()});var exports_detector_scheduler={};__export(exports_detector_scheduler,{start:()=>start,DEFAULT_TICK_INTERVAL_MS:()=>DEFAULT_TICK_INTERVAL_MS,DEFAULT_JITTER_MS:()=>DEFAULT_JITTER_MS,DEFAULT_FIRE_BUDGET:()=>DEFAULT_FIRE_BUDGET,DEFAULT_DETECTOR_BUDGETS:()=>DEFAULT_DETECTOR_BUDGETS});function start(options={}){let tickIntervalMs=options.tickIntervalMs??DEFAULT_TICK_INTERVAL_MS,jitterMs=options.jitterMs??DEFAULT_JITTER_MS,defaultBudget=options.defaultFireBudget??DEFAULT_FIRE_BUDGET,budgets={...DEFAULT_DETECTOR_BUDGETS,...options.fireBudgets??{}},now=options.now??(()=>Date.now()),setTimeoutFn=options.setTimeoutFn??((fn,ms)=>setTimeout(fn,ms)),clearTimeoutFn=options.clearTimeoutFn??((handle)=>{clearTimeout(handle)}),resolveDetectors=options.detectorSource??listDetectors,emit2=options.emitFn??emitEvent,state={ticks:0,fires:0,disables:0,budgetBuckets:{}},disabledBuckets=new Set,stopped=!1,currentTimer=null,tickInFlight=null;async function runTick(){if(stopped)return;state.ticks++;let detectors=resolveDetectors();for(let detector of detectors)await runOneDetector(detector)}async function safeCall(fn){try{return await fn()}catch{return null}}function emitFire(detector,event){emit2(event.type,event.payload,{detector_version:detector.version,source_subsystem:"detector-scheduler",entity_id:event.subject??detector.id,agent:process.env.GENIE_AGENT_NAME??"detector-scheduler"}),state.fires++}function emitDisable(detector,budget,current,bucketStart){state.disables++,emit2("detector.disabled",{detector_id:detector.id,cause:"fire_budget_exceeded",budget,fire_count:current,bucket_end_ts:new Date(bucketStart+HOUR_MS).toISOString()},{detector_version:detector.version,source_subsystem:"detector-scheduler",entity_id:detector.id,severity:"warn",agent:process.env.GENIE_AGENT_NAME??"detector-scheduler"})}async function runOneDetector(detector){let bucketStart=Math.floor(now()/HOUR_MS)*HOUR_MS,bucketKey=`${detector.id}:${bucketStart}`,budget=budgets[detector.id]??defaultBudget;if(disabledBuckets.has(bucketKey))return;let result2=await safeCall(()=>detector.query());if(result2===null)return;if(!await safeCall(()=>detector.shouldFire(result2)))return;let current=(state.budgetBuckets[bucketKey]??0)+1;state.budgetBuckets[bucketKey]=current;let event=await safeCall(()=>detector.render(result2));if(event===null)return;if(emitFire(detector,event),current>=budget&&!disabledBuckets.has(bucketKey))disabledBuckets.add(bucketKey),emitDisable(detector,budget,current,bucketStart)}function scheduleNext(){if(stopped)return;let jitter=jitterMs>0?Math.floor((Math.random()*2-1)*jitterMs):0,delay=Math.max(0,tickIntervalMs+jitter);currentTimer=setTimeoutFn(()=>{tickInFlight=runTick().finally(()=>{tickInFlight=null,scheduleNext()})},delay)}return scheduleNext(),{stop(){if(stopped)return;if(stopped=!0,currentTimer)clearTimeoutFn(currentTimer),currentTimer=null},async tickNow(){if(tickInFlight)await tickInFlight;await runTick()},stats(){return{...state,budgetBuckets:{...state.budgetBuckets}}}}}var DEFAULT_TICK_INTERVAL_MS=60000,DEFAULT_JITTER_MS=5000,DEFAULT_FIRE_BUDGET=100,DEFAULT_DETECTOR_BUDGETS,HOUR_MS=3600000;var init_detector_scheduler=__esm(()=>{init_detectors();init_emit();init_pattern_2_team_ls_drift();DEFAULT_DETECTOR_BUDGETS=Object.freeze({"rot.team-ls-drift":20,"rot.backfill-no-worktree":40})});var exports_executor_read={};__export(exports_executor_read,{stopExecutorReadEndpoint:()=>stopExecutorReadEndpoint,startExecutorReadEndpoint:()=>startExecutorReadEndpoint,readExecutorState:()=>readExecutorState,isExecutorReadEndpointRunning:()=>isExecutorReadEndpointRunning,getExecutorReadPort:()=>getExecutorReadPort});async function readExecutorState(id,sql){let rows=await(sql??await getConnection())`SELECT state, outcome, closed_at FROM executors WHERE id = ${id} LIMIT 1`;if(rows.length===0)return null;let row=rows[0];return{state:row.state,outcome:row.outcome??null,closed_at:row.closed_at==null?null:row.closed_at instanceof Date?row.closed_at.toISOString():row.closed_at}}function getExecutorReadPort(){let envPort=process.env.GENIE_EXECUTOR_READ_PORT;if(envPort){let parsed=Number.parseInt(envPort,10);if(!Number.isNaN(parsed)&&parsed>0&&parsed<65536)return parsed}return getAuxiliaryPortBase()+2}async function handleStateRoute(id){if(!UUID_RE.test(id))return Response.json({error:"invalid executor id"},{status:400});try{let reply=await readExecutorState(id);if(!reply)return Response.json({error:"not found"},{status:404});return Response.json(reply,{headers:{"Cache-Control":"no-store"}})}catch(err){let msg=err instanceof Error?err.message:String(err);return Response.json({error:msg},{status:500})}}async function routeRequest(req,port){let url=new URL(req.url);if(req.method==="GET"&&url.pathname==="/health")return Response.json({status:"ok",port});if(req.method!=="GET")return new Response("Method Not Allowed",{status:405});let match=ROUTE_RE.exec(url.pathname);if(!match)return new Response("Not Found",{status:404});return handleStateRoute(match[1])}async function startExecutorReadEndpoint(){if(server2)return!0;let port=getExecutorReadPort();try{return server2=Bun.serve({port,hostname:"127.0.0.1",fetch:(req)=>routeRequest(req,port)}),!0}catch(err){let message=err instanceof Error?err.message:String(err);if(message.includes("EADDRINUSE")||message.includes("address already in use"))console.warn(`Executor read endpoint: port ${port} already in use \u2014 skipping`);else console.warn(`Executor read endpoint: failed to start on port ${port}: ${message}`);return!1}}async function stopExecutorReadEndpoint(){if(server2)await server2.stop(!0),server2=null}function isExecutorReadEndpointRunning(){return server2!==null}var server2=null,UUID_RE,ROUTE_RE;var init_executor_read=__esm(()=>{init_db();UUID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,ROUTE_RE=/^\/executors\/([^/]+)\/state\/?$/});var exports_omni_approval_handler={};__export(exports_omni_approval_handler,{startOmniApprovalHandler:()=>startOmniApprovalHandler});class OmniApprovalHandler{nc=null;subs=[];sc=import_nats2.StringCodec();permissions;natsUrl;approveTokens;denyTokens;constructor(config){this.permissions=config.permissions,this.natsUrl=config.natsUrl??"localhost:4222",this.approveTokens=(config.permissions.approveTokens??DEFAULT_APPROVE_TOKENS).map((t)=>t.toLowerCase()),this.denyTokens=(config.permissions.denyTokens??DEFAULT_DENY_TOKENS).map((t)=>t.toLowerCase())}async start(){let{omniChat,omniInstance}=this.permissions;if(!omniChat||!omniInstance)return;this.nc=await import_nats2.connect({servers:this.natsUrl});let messageTopic=`omni.message.${omniInstance}.>`,msgSub=this.nc.subscribe(messageTopic);this.subs.push(msgSub),this.processMessages(msgSub);let eventSub=this.nc.subscribe("omni.event.>");this.subs.push(eventSub),this.processEvents(eventSub),handlerInstance=this,console.log(`[omni-approval] Listening for approval replies on ${messageTopic}`)}async stop(){for(let sub of this.subs)sub.unsubscribe();if(this.subs=[],this.nc)await this.nc.close(),this.nc=null;if(handlerInstance===this)handlerInstance=null}async processMessages(sub){for await(let msg of sub)try{let data=JSON.parse(this.sc.decode(msg.data));if((data.chatId??this.extractChatIdFromSubject(msg.subject))!==this.permissions.omniChat)continue;if(data.content)await this.handleTextReply(data.content,data.sender??"whatsapp-user")}catch{}}async processEvents(sub){for await(let msg of sub)try{let data=JSON.parse(this.sc.decode(msg.data));if(data.type!=="reaction")continue;if(data.chatId!==this.permissions.omniChat||data.instanceId!==this.permissions.omniInstance)continue;if(data.emoji&&data.messageId)await this.handleReaction(data.emoji,data.messageId,data.sender??"whatsapp-user")}catch{}}extractChatIdFromSubject(subject){let parts=subject.split(".");if(parts.length>=4)return parts.slice(3).join(".");return}async handleTextReply(content,sender){let normalized=content.trim().toLowerCase();if(!normalized)return!1;let decision=null;if(this.approveTokens.includes(normalized))decision="allow";else if(this.denyTokens.includes(normalized))decision="deny";if(!decision)return!1;let pending=await listPendingApprovals();if(pending.length===0)return!1;let oldest=pending[0],resolved=await resolveApproval(oldest.id,decision,sender);if(resolved)console.log(`[omni-approval] Resolved ${oldest.id} as ${decision} by ${sender} (text: "${normalized}")`);return resolved}async handleReaction(emoji,messageId,sender){let decision=null;if(DEFAULT_APPROVE_REACTIONS.includes(emoji))decision="allow";else if(DEFAULT_DENY_REACTIONS.includes(emoji))decision="deny";if(!decision)return!1;try{let sql=await getConnection(),[approval]=await sql`
2383
2383
  SELECT id FROM approvals
2384
2384
  WHERE omni_message_id = ${messageId} AND decision = 'pending'
2385
- `;if(approval){let resolved2=await resolveApproval(approval.id,decision,sender);if(resolved2)console.log(`[omni-approval] Resolved ${approval.id} via reaction ${emoji} by ${sender}`);return resolved2}}catch{}let pending=await listPendingApprovals();if(pending.length===0)return!1;let resolved=await resolveApproval(pending[0].id,decision,sender);if(resolved)console.log(`[omni-approval] Resolved ${pending[0].id} via reaction ${emoji} by ${sender} (fallback)`);return resolved}}async function startOmniApprovalHandler(natsUrl){let ws=findWorkspace();if(!ws)return null;let config=getWorkspaceConfig(ws.root);if(!config.permissions?.omniChat||!config.permissions?.omniInstance)return null;let handler=new OmniApprovalHandler({natsUrl,permissions:config.permissions});return await handler.start(),handler}var import_nats2,DEFAULT_APPROVE_TOKENS,DEFAULT_DENY_TOKENS,DEFAULT_APPROVE_REACTIONS,DEFAULT_DENY_REACTIONS,handlerInstance=null;var init_omni_approval_handler=__esm(()=>{init_db();init_claude_sdk_remote_approval();init_workspace();import_nats2=__toESM(require_mod4(),1),DEFAULT_APPROVE_TOKENS=["y","yes","approve","sim"],DEFAULT_DENY_TOKENS=["n","no","deny","nao"],DEFAULT_APPROVE_REACTIONS=["\uD83D\uDC4D","\u2705","\uD83D\uDC4C"],DEFAULT_DENY_REACTIONS=["\uD83D\uDC4E","\u274C","\uD83D\uDEAB"]});function isValid2(value){return typeof value==="string"&&VALID.has(value)}function resolveExecutorType(override){if(isValid2(override))return override;let env=process.env.GENIE_EXECUTOR;if(isValid2(env))return env;try{let persisted=loadGenieConfigSync().omni?.executor;if(isValid2(persisted))return persisted}catch{}return"tmux"}var VALID;var init_executor_config=__esm(()=>{init_genie_config2();VALID=new Set(["tmux","sdk"])});class BridgeSessionStore{sql;constructor(sql){this.sql=sql}async create(opts){let[row]=await this.sql`
2385
+ `;if(approval){let resolved2=await resolveApproval(approval.id,decision,sender);if(resolved2)console.log(`[omni-approval] Resolved ${approval.id} via reaction ${emoji} by ${sender}`);return resolved2}}catch{}let pending=await listPendingApprovals();if(pending.length===0)return!1;let resolved=await resolveApproval(pending[0].id,decision,sender);if(resolved)console.log(`[omni-approval] Resolved ${pending[0].id} via reaction ${emoji} by ${sender} (fallback)`);return resolved}}async function startOmniApprovalHandler(natsUrl){let ws=findWorkspace();if(!ws)return null;let config=getWorkspaceConfig(ws.root);if(!config.permissions?.omniChat||!config.permissions?.omniInstance)return null;let handler=new OmniApprovalHandler({natsUrl,permissions:config.permissions});return await handler.start(),handler}var import_nats2,DEFAULT_APPROVE_TOKENS,DEFAULT_DENY_TOKENS,DEFAULT_APPROVE_REACTIONS,DEFAULT_DENY_REACTIONS,handlerInstance=null;var init_omni_approval_handler=__esm(()=>{init_db();init_claude_sdk_remote_approval();init_workspace();import_nats2=__toESM(require_mod4(),1),DEFAULT_APPROVE_TOKENS=["y","yes","approve","sim"],DEFAULT_DENY_TOKENS=["n","no","deny","nao"],DEFAULT_APPROVE_REACTIONS=["\uD83D\uDC4D","\u2705","\uD83D\uDC4C"],DEFAULT_DENY_REACTIONS=["\uD83D\uDC4E","\u274C","\uD83D\uDEAB"]});function isValid2(value){return typeof value==="string"&&VALID.has(value)}function resolveExecutorType(override){if(isValid2(override))return override;let env=process.env.GENIE_EXECUTOR;if(isValid2(env))return env;try{let persisted=loadGenieConfigSync().omni?.executor;if(isValid2(persisted))return persisted}catch{}return"tmux"}var VALID;var init_executor_config=__esm(()=>{init_genie_config2();VALID=new Set(["tmux","sdk"])});function resolveHeartbeatIntervalMs(explicit){let envRaw=process.env.OMNI_HEARTBEAT_INTERVAL_MS,envParsed=envRaw!==void 0?Number(envRaw):Number.NaN,candidate=explicit??(Number.isFinite(envParsed)&&envParsed>0?envParsed:DEFAULT_INTERVAL_MS);if(!Number.isFinite(candidate)||candidate<=0)return DEFAULT_INTERVAL_MS;return Math.min(Math.max(candidate,MIN_INTERVAL_MS),MAX_INTERVAL_MS)}function heartbeatSubject(instanceId,chatId){return`omni.agent.heartbeat.${instanceId}.${chatId}`}class HeartbeatPublisher{intervalMs;publishFn;setIntervalFn;clearIntervalFn;nowFn;active=new Map;constructor(options={}){if(this.intervalMs=resolveHeartbeatIntervalMs(options.intervalMs),this.nowFn=options.now??Date.now,this.setIntervalFn=options.setInterval??globalThis.setInterval,this.clearIntervalFn=options.clearInterval??globalThis.clearInterval,options.publish)this.publishFn=options.publish;else if(options.natsConnection){let sc=import_nats3.StringCodec(),nc=options.natsConnection;this.publishFn=(subject,payload)=>nc.publish(subject,sc.encode(payload))}else this.publishFn=()=>{}}getIntervalMs(){return this.intervalMs}size(){return this.active.size}start(sessionKey2,ctx){let existing=this.active.get(sessionKey2);if(existing)this.clearIntervalFn(existing.handle);let handle=this.setIntervalFn(()=>{this.tick(sessionKey2)},this.intervalMs);if(typeof handle.unref==="function")handle.unref?.();this.active.set(sessionKey2,{handle,ctx})}stop(sessionKey2){let entry2=this.active.get(sessionKey2);if(!entry2)return;this.clearIntervalFn(entry2.handle),this.active.delete(sessionKey2)}stopAll(){for(let[,entry2]of this.active)this.clearIntervalFn(entry2.handle);this.active.clear()}async tick(sessionKey2){let entry2=this.active.get(sessionKey2);if(!entry2)return;let{ctx}=entry2,busy;try{busy=await ctx.isBusy()}catch(err){console.warn(`[agent-heartbeat] isBusy threw for ${sessionKey2} \u2014 treating as idle (skip): ${err instanceof Error?err.message:String(err)}`);return}if(!busy)return;if(!this.active.has(sessionKey2))return;let event={turnId:ctx.turnId,instanceId:ctx.instanceId,chatId:ctx.chatId,timestamp:new Date(this.nowFn()).toISOString()};try{this.publishFn(heartbeatSubject(ctx.instanceId,ctx.chatId),JSON.stringify(event))}catch(err){console.warn(`[agent-heartbeat] publish failed for ${sessionKey2}: ${err instanceof Error?err.message:String(err)}`)}}}var import_nats3,DEFAULT_INTERVAL_MS=30000,MIN_INTERVAL_MS=5000,MAX_INTERVAL_MS=60000;var init_agent_heartbeat=__esm(()=>{import_nats3=__toESM(require_mod4(),1)});class BridgeSessionStore{sql;constructor(sql){this.sql=sql}async create(opts){let[row]=await this.sql`
2386
2386
  INSERT INTO genie_bridge_sessions (
2387
2387
  instance_id, chat_id, agent_name, executor_id,
2388
2388
  tmux_pane_id, claude_session_id, metadata
@@ -2451,7 +2451,7 @@ The user's message:
2451
2451
 
2452
2452
  ---
2453
2453
 
2454
- ${initialMessage}`:turnContext,permissions=entry2.permissions?.allow?.length||entry2.permissions?.deny?.length?{allow:entry2.permissions.allow,deny:entry2.permissions.deny}:void 0;return{provider:entry2.provider??"claude",team:agentName,role:agentName,sessionId:resumeClaudeSessionId?void 0:randomUUID9(),resume:resumeClaudeSessionId,model:entry2.model,promptMode:entry2.promptMode,systemPromptFile:join58(entry2.dir,"AGENTS.md"),initialPrompt:fullInitialPrompt,skipHooks:!0,permissions,disallowedTools:entry2.disallowedTools,nativeTeam:{enabled:!0,agentName,color:entry2.color??void 0}}}function resolveOmniPaneProcessName(provider){return provider==="codex"?"codex":"claude"}class ClaudeCodeOmniExecutor{sessions=new Map;safePgCall=null;setSafePgCall(fn){this.safePgCall=fn}setNatsPublish(_fn){}async injectNudge(session,text){let paneId=session.tmux?.paneId;if(!paneId)return;let nudgeText=`[system] ${text}`;await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(nudgeText)} Enter`)}async launchOmniProcessInPane(agentName,chatId,entry2,env,paneId,initialMessage,resumeClaudeSessionId){let omniEnv={...env,GENIE_OMNI_CHAT_ID:chatId,GENIE_OMNI_AGENT:agentName},params=buildOmniSpawnParams(agentName,chatId,entry2,omniEnv,initialMessage,resumeClaudeSessionId),claudeSessionId=resumeClaudeSessionId??params.sessionId??void 0,launch=buildLaunchCommand(params),allEnv={...omniEnv,...launch.env},envPrefix=Object.entries(allEnv).map(([k,v])=>`${k}=${shellQuote(v)}`).join(" "),cmd=envPrefix?`${envPrefix} ${launch.command}`:launch.command;return await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(cmd)} Enter`),claudeSessionId}async spawn(agentName,chatId,env,initialMessage){let resolved=await resolve6(agentName);if(!resolved)throw Error(`Agent "${agentName}" not found in genie directory`);let entry2=resolved.entry,tmuxSession=resolveBridgeTmuxSession(agentName,entry2.bridgeTmuxSession,env.GENIE_TMUX_SESSION),chatName=await lookupChatName(chatId,env.OMNI_INSTANCE??""),windowName=sanitizeWindowName2(chatId,chatName??void 0),{paneId,created}=await ensureTeamWindow(tmuxSession,windowName,entry2.dir),instanceId=env.OMNI_INSTANCE??"",agent=this.safePgCall?await this.safePgCall("tmux-find-or-create-agent",()=>findOrCreateAgent(`${agentName}:${chatId}`,"omni","omni"),null,{chatId}):null,existingExecutor=agent?await this.safePgCall?.("tmux-find-existing-executor",()=>findLatestByMetadata({agentId:agent.id,source:"omni",chatId}),null,{chatId})??null:null,resumeClaudeSessionId=existingExecutor?.claudeSessionId??void 0,processName=resolveOmniPaneProcessName(entry2.provider),processRunning=!created&&await isPaneProcessRunning(paneId,processName),claudeSessionId=created||!processRunning?await this.launchOmniProcessInPane(agentName,chatId,entry2,env,paneId,initialMessage,resumeClaudeSessionId):resumeClaudeSessionId,sessionKey2=`${agentName}:${chatId}`,registration=await this.registerOrRelinkExecutor(agent,existingExecutor,chatId,instanceId,tmuxSession,windowName,paneId,entry2.dir,claudeSessionId);if(this.sessions.set(sessionKey2,{executorId:registration?.executorId??null,agentId:registration?.agentId??null,repoPath:entry2.dir}),registration?.executorId)await this.updateState(registration.executorId,"running",chatId);let now=Date.now();return{id:sessionKey2,agentName,chatId,executorType:"tmux",createdAt:now,lastActivityAt:now,tmux:{session:tmuxSession,window:windowName,paneId,claudeSessionId,processName}}}async registerOrRelinkExecutor(agent,existingExecutor,chatId,instanceId,tmuxSession,tmuxWindow,tmuxPaneId,repoPath,claudeSessionId){if(!this.safePgCall||!agent)return null;if(await this.safePgCall("tmux-update-agent-pane",async()=>{await(await Promise.resolve().then(() => (init_db(),exports_db)).then((m)=>m.getConnection()))`
2454
+ ${initialMessage}`:turnContext,permissions=entry2.permissions?.allow?.length||entry2.permissions?.deny?.length?{allow:entry2.permissions.allow,deny:entry2.permissions.deny}:void 0;return{provider:entry2.provider??"claude",team:agentName,role:agentName,sessionId:resumeClaudeSessionId?void 0:randomUUID9(),resume:resumeClaudeSessionId,model:entry2.model,promptMode:entry2.promptMode,systemPromptFile:join58(entry2.dir,"AGENTS.md"),initialPrompt:fullInitialPrompt,skipHooks:!0,permissions,disallowedTools:entry2.disallowedTools,nativeTeam:{enabled:!0,agentName,color:entry2.color??void 0}}}function resolveOmniPaneProcessName(provider){return provider==="codex"?"codex":"claude"}class ClaudeCodeOmniExecutor{sessions=new Map;safePgCall=null;setSafePgCall(fn){this.safePgCall=fn}setNatsPublish(_fn){}async injectNudge(session,text){let paneId=session.tmux?.paneId;if(!paneId)return;let nudgeText=`[system] ${text}`;await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(nudgeText)} Enter`)}async launchOmniProcessInPane(agentName,chatId,entry2,env,paneId,initialMessage,resumeClaudeSessionId){let omniEnv={...env,GENIE_OMNI_CHAT_ID:chatId,GENIE_OMNI_AGENT:agentName},params=buildOmniSpawnParams(agentName,chatId,entry2,omniEnv,initialMessage,resumeClaudeSessionId),claudeSessionId=resumeClaudeSessionId??params.sessionId??void 0,launch=buildLaunchCommand(params),allEnv={...omniEnv,...launch.env},envPrefix=Object.entries(allEnv).map(([k,v])=>`${k}=${shellQuote(v)}`).join(" "),cmd=envPrefix?`${envPrefix} ${launch.command}`:launch.command;return await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(cmd)} Enter`),claudeSessionId}async spawn(agentName,chatId,env,initialMessage){let resolved=await resolve6(agentName);if(!resolved)throw Error(`Agent "${agentName}" not found in genie directory`);let entry2=resolved.entry,tmuxSession=resolveBridgeTmuxSession(agentName,entry2.bridgeTmuxSession,env.GENIE_TMUX_SESSION),chatName=await lookupChatName(chatId,env.OMNI_INSTANCE??""),windowName=sanitizeWindowName2(chatId,chatName??void 0),{paneId,created}=await ensureTeamWindow(tmuxSession,windowName,entry2.dir),instanceId=env.OMNI_INSTANCE??"",agent=this.safePgCall?await this.safePgCall("tmux-find-or-create-agent",()=>findOrCreateAgent(`${agentName}:${chatId}`,"omni","omni"),null,{chatId}):null,existingExecutor=agent?await this.safePgCall?.("tmux-find-existing-executor",()=>findLatestByMetadata({agentId:agent.id,source:"omni",chatId}),null,{chatId})??null:null,resumeClaudeSessionId=existingExecutor?.claudeSessionId??void 0,processName=resolveOmniPaneProcessName(entry2.provider),processRunning=!created&&await isPaneProcessRunning(paneId,processName),claudeSessionId=created||!processRunning?await this.launchOmniProcessInPane(agentName,chatId,entry2,env,paneId,initialMessage,resumeClaudeSessionId):resumeClaudeSessionId,sessionKey2=`${agentName}:${chatId}`,registration=await this.registerOrRelinkExecutor(agent,existingExecutor,chatId,instanceId,tmuxSession,windowName,paneId,entry2.dir,claudeSessionId);if(this.sessions.set(sessionKey2,{executorId:registration?.executorId??null,agentId:registration?.agentId??null,repoPath:entry2.dir,lastPaneFingerprint:null}),registration?.executorId)await this.updateState(registration.executorId,"running",chatId);let now=Date.now();return{id:sessionKey2,agentName,chatId,executorType:"tmux",createdAt:now,lastActivityAt:now,tmux:{session:tmuxSession,window:windowName,paneId,claudeSessionId,processName}}}async registerOrRelinkExecutor(agent,existingExecutor,chatId,instanceId,tmuxSession,tmuxWindow,tmuxPaneId,repoPath,claudeSessionId){if(!this.safePgCall||!agent)return null;if(await this.safePgCall("tmux-update-agent-pane",async()=>{await(await Promise.resolve().then(() => (init_db(),exports_db)).then((m)=>m.getConnection()))`
2455
2455
  UPDATE agents
2456
2456
  SET pane_id = ${tmuxPaneId},
2457
2457
  session = ${tmuxSession},
@@ -2473,9 +2473,9 @@ ${initialMessage}`:turnContext,permissions=entry2.permissions?.allow?.length||en
2473
2473
 
2474
2474
  ---
2475
2475
 
2476
- [${senderName}]: ${message.content}`,paneId=session.tmux?.paneId;if(paneId&&/^%\d+$/.test(paneId)&&await isPaneAlive(paneId))try{await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(body)}`),await new Promise((resolve11)=>setTimeout(resolve11,200)),await executeTmux2(`send-keys -t '${paneId}' Enter`)}catch(err){console.error(`[claude-code] deliver: send-keys failed for ${session.id} (pane ${paneId}):`,err instanceof Error?err.message:err)}else console.error(`[claude-code] deliver: pane unavailable for ${session.id} (paneId=${paneId??"null"}), message lost`);if(session.lastActivityAt=Date.now(),state?.executorId)await this.updateState(state.executorId,"idle",session.chatId)}async shutdown(session){let state=this.sessions.get(session.id);try{if(session.tmux)await killWindow(session.tmux.session,session.tmux.window)}finally{if(state?.executorId&&this.safePgCall)await this.safePgCall("tmux-terminate-executor",()=>terminateExecutor(state.executorId),void 0,{executorId:state.executorId,chatId:session.chatId});if(state?.agentId&&this.safePgCall)await this.safePgCall("tmux-unregister-agent",()=>unregister(state.agentId),void 0,{chatId:session.chatId});this.sessions.delete(session.id)}}async isAlive(session){let paneId=session.tmux?.paneId;if(!paneId)return!1;try{if(!await isPaneAlive(paneId))return!1;return await isPaneProcessRunning(paneId,session.tmux?.processName??"claude")}catch{return!1}}}var init_claude_code2=__esm(()=>{init_agent_directory();init_agent_registry();init_executor_registry();init_omni_signature();init_provider_adapters();init_team_lead_command();init_tmux()});async function recordAuditEvent2(safePgCall,type2,attrs){let entityType=attrs.entity_type??"executor",entityId=attrs.entity_id??attrs.executor_id??"",actor=attrs.actor??attrs.agent_id??null,{entity_type:_et,entity_id:_eid,actor:_a,...details}=attrs;await safePgCall(`audit:${type2}`,(sql)=>sql`INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
2476
+ [${senderName}]: ${message.content}`,paneId=session.tmux?.paneId;if(paneId&&/^%\d+$/.test(paneId)&&await isPaneAlive(paneId))try{await executeTmux2(`send-keys -t '${paneId}' ${shellQuote(body)}`),await new Promise((resolve11)=>setTimeout(resolve11,200)),await executeTmux2(`send-keys -t '${paneId}' Enter`)}catch(err){console.error(`[claude-code] deliver: send-keys failed for ${session.id} (pane ${paneId}):`,err instanceof Error?err.message:err)}else console.error(`[claude-code] deliver: pane unavailable for ${session.id} (paneId=${paneId??"null"}), message lost`);if(session.lastActivityAt=Date.now(),state?.executorId)await this.updateState(state.executorId,"idle",session.chatId)}async shutdown(session){let state=this.sessions.get(session.id);try{if(session.tmux)await killWindow(session.tmux.session,session.tmux.window)}finally{if(state?.executorId&&this.safePgCall)await this.safePgCall("tmux-terminate-executor",()=>terminateExecutor(state.executorId),void 0,{executorId:state.executorId,chatId:session.chatId});if(state?.agentId&&this.safePgCall)await this.safePgCall("tmux-unregister-agent",()=>unregister(state.agentId),void 0,{chatId:session.chatId});this.sessions.delete(session.id)}}async isAlive(session){let paneId=session.tmux?.paneId;if(!paneId)return!1;try{if(!await isPaneAlive(paneId))return!1;return await isPaneProcessRunning(paneId,session.tmux?.processName??"claude")}catch{return!1}}async isBusy(session){let state=this.sessions.get(session.id),paneId=session.tmux?.paneId;if(!state||!paneId)return!1;let content;try{content=await capturePaneContent(paneId,200,!1)}catch{return!1}let fingerprint=`${content.length}:${content.slice(-128)}`,previous=state.lastPaneFingerprint;if(state.lastPaneFingerprint=fingerprint,previous===null)return!0;return previous!==fingerprint}}var init_claude_code2=__esm(()=>{init_agent_directory();init_agent_registry();init_executor_registry();init_omni_signature();init_provider_adapters();init_team_lead_command();init_tmux()});async function recordAuditEvent2(safePgCall,type2,attrs){let entityType=attrs.entity_type??"executor",entityId=attrs.entity_id??attrs.executor_id??"",actor=attrs.actor??attrs.agent_id??null,{entity_type:_et,entity_id:_eid,actor:_a,...details}=attrs;await safePgCall(`audit:${type2}`,(sql)=>sql`INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
2477
2477
  VALUES (${entityType}, ${entityId}, ${type2}, ${actor}, ${JSON.stringify(details)})`,void 0,{executorId:entityId,chatId:attrs.chat_id??""})}async function loadSystemPrompt(entry2){let identityPath=loadIdentity(entry2);if(!identityPath)return;let{readFileSync:readFileSync28}=await import("fs");try{return readFileSync28(identityPath,"utf-8")}catch{return}}async function resolveSystemPrompt(entry2,state){let prompt2=await loadSystemPrompt(entry2),isTurnBased=Boolean(state.env.OMNI_INSTANCE);return{prompt:prompt2,isTurnBased}}function buildTurnContextPrefix(state,message,chatId){if(!state.env.OMNI_INSTANCE)return"";return buildTurnBasedPrompt(message.sender,state.env.OMNI_INSTANCE,state.env.OMNI_CHAT??chatId)}function extractTextFromAssistant(msg){if(!msg.message)return[];return msg.message.content.filter((b2)=>b2.type==="text"&&b2.text).map((b2)=>b2.text)}async function collectQueryResult(queryMessages){let textParts=[],sessionId;try{for await(let msg of queryMessages){if(msg.type==="assistant")textParts.push(...extractTextFromAssistant(msg));if(msg.type==="result"&&msg.subtype==="success")sessionId=msg.session_id}}catch(err){if(err.name==="AbortError")return{text:""};throw err}return{text:textParts.join(`
2478
- `).trim(),sessionId}}function buildReplyPayload(agent,chatId,instanceId,extra={}){return JSON.stringify({content:"",agent,chat_id:chatId,instance_id:instanceId,timestamp:new Date().toISOString(),...extra})}function resolveAction(params,env){if(params.skip)return{type:"skip",extra:{reason:params.reason},label:"Turn closed (skip)."};if(params.react)return{type:"react",extra:{react:String(params.react),message_id:env.OMNI_MESSAGE??""},label:`Reacted ${params.react} + turn closed.`};if(params.media)return{type:"media",extra:{content:params.caption?String(params.caption):params.text?String(params.text):"",media:String(params.media)},label:"Media sent + turn closed."};if(params.text)return{type:"text",extra:{content:String(params.text)},label:"Turn closed. Message delivered."};return{type:"skip",extra:{},label:"Turn closed (skip)."}}function handleDoneTool(params,env,natsPublish){let instanceId=env.OMNI_INSTANCE??"",chatId=env.OMNI_CHAT??"",agent=env.OMNI_AGENT??"",action=resolveAction(params,env);if(action.type==="skip"){if(natsPublish&&instanceId&&chatId)natsPublish(`omni.turn.done.${instanceId}.${chatId}`,JSON.stringify({action:"skip",...action.extra}));return action.label}if(!natsPublish||!instanceId||!chatId)return console.warn("[claude-sdk] No NATS publish available \u2014 reply dropped"),"Turn close attempted but NATS publish not available.";return natsPublish(`omni.reply.${instanceId}.${chatId}`,buildReplyPayload(agent,chatId,instanceId,action.extra)),action.label}function parseSendMessageInput(input){if(!input||typeof input!=="object")return{};let obj=input,recipient=typeof obj.recipient==="string"?obj.recipient:typeof obj.to==="string"?obj.to:void 0,body=typeof obj.message==="string"?obj.message:typeof obj.content==="string"?obj.content:void 0;return{recipient,body}}function createSendMessageOmniHook(env,natsPublish){return async(input)=>{let hookInput=input;if(hookInput.tool_name!=="SendMessage")return{};let{recipient,body}=parseSendMessageInput(hookInput.tool_input);if(recipient!=="omni")return{};let instanceId=env.OMNI_INSTANCE??"",chatId=env.OMNI_CHAT??"",agent=env.OMNI_AGENT??"";if(!instanceId||!chatId)return{};if(!body||body.trim()==="")return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:'SendMessage(recipient: "omni") requires a non-empty `message` field. Retry with the reply text.'}};if(!natsPublish)return console.warn("[claude-sdk] SendMessage(to: omni) intercepted but NATS publish unavailable \u2014 message dropped"),{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"Omni bridge unavailable \u2014 message could not be delivered."}};return natsPublish(`omni.reply.${instanceId}.${chatId}`,buildReplyPayload(agent,chatId,instanceId,{content:body})),{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"Message delivered to user via omni bridge."}}}}async function createDoneMcpServer(env,natsPublish){let{createSdkMcpServer,tool}=await import("@anthropic-ai/claude-agent-sdk");return createSdkMcpServer({name:"genie-omni-tools",tools:[tool("done","Close this turn. REQUIRED after processing the user message. Sends a final response, reacts, or skips. Call exactly once per turn.",{text:exports_external.string().optional().describe("Final message to the user"),media:exports_external.string().optional().describe("File path for media attachment"),caption:exports_external.string().optional().describe("Caption for media"),react:exports_external.string().optional().describe("Emoji reaction (instead of text)"),skip:exports_external.boolean().optional().describe("Close turn without sending anything"),reason:exports_external.string().optional().describe("Internal reason for skipping")},async(args)=>{return{content:[{type:"text",text:handleDoneTool(args,env,natsPublish)}]}})]})}class ClaudeSdkOmniExecutor{sessions=new Map;safePgCall=null;natsPublish=null;deliveryQueues=new Map;pendingNudges=new Map;setSafePgCall(fn){this.safePgCall=fn}setNatsPublish(fn){this.natsPublish=fn}async injectNudge(session,text){if(!this.sessions.has(session.id))return;this.pendingNudges.set(session.id,text)}async spawn(agentName,chatId,env,_initialMessage){if(!await resolve6(agentName))throw Error(`Agent "${agentName}" not found in genie directory`);let provider=new ClaudeSdkProvider,abortController=new AbortController,sessionId=`${agentName}:${chatId}`,registration=await this.registerInWorldA(agentName,chatId,env.OMNI_INSTANCE??"");if(this.sessions.set(sessionId,{abortController,running:!0,provider,executorId:registration?.executorId??null,claudeSessionId:registration?.claudeSessionId,dbSessionId:null,turnIndex:0,env}),registration?.executorId)await this.updateState(registration.executorId,"running",chatId);let now=Date.now();return{id:sessionId,agentName,chatId,executorType:"sdk",createdAt:now,lastActivityAt:now,sdk:{claudeSessionId:registration?.claudeSessionId,executorId:registration?.executorId}}}async registerInWorldA(agentName,chatId,instanceId){if(!this.safePgCall)return null;let agent=await this.safePgCall("sdk-find-or-create-agent",()=>findOrCreateAgent(agentName,"omni","omni"),null,{chatId});if(!agent)return null;let existing=await this.safePgCall("sdk-find-existing-executor",()=>findLatestByMetadata({agentId:agent.id,source:"omni",chatId}),null,{chatId});if(existing)return await this.safePgCall("sdk-relink-executor",()=>relinkExecutorToAgent(existing.id,agent.id),void 0,{executorId:existing.id,chatId}),await recordAuditEvent2(this.safePgCall,"session.resumed",{executor_id:existing.id,agent_id:agentName,chat_id:chatId,claude_session_id:existing.claudeSessionId}),{executorId:existing.id,claudeSessionId:existing.claudeSessionId??void 0};let executor=await this.safePgCall("sdk-create-executor",()=>createAndLinkExecutor(agent.id,"claude","api",{claudeSessionId:void 0,metadata:{source:"omni",chat_id:chatId,instance_id:instanceId}}),null,{chatId});if(executor)await recordAuditEvent2(this.safePgCall,"session.created_fresh",{executor_id:executor.id,agent_id:agentName,chat_id:chatId});return executor?{executorId:executor.id}:null}async updateState(executorId,state,chatId){if(!this.safePgCall)return;await this.safePgCall("sdk-update-executor-state",()=>updateExecutorState(executorId,state),void 0,{executorId,chatId})}async deliver(session,message){let state=this.sessions.get(session.id);if(!state)throw Error(`No SDK session found for ${session.id}`);let current=(this.deliveryQueues.get(session.id)??Promise.resolve()).then(()=>this._processDelivery(session,state,message));this.deliveryQueues.set(session.id,current.catch(()=>{}))}async _processDelivery(session,state,message){let resolved=await resolve6(session.agentName);if(!resolved)throw Error(`Agent "${session.agentName}" not found in genie directory`);let entry2=resolved.entry,permissionConfig=resolvePermissionConfig(entry2.permissions),{prompt:systemPrompt,isTurnBased}=await resolveSystemPrompt(entry2,state);if(state.executorId)await this.updateState(state.executorId,"working",session.chatId);if(this.safePgCall)await recordAuditEvent2(this.safePgCall,"deliver.start",{executor_id:state.executorId??session.id,agent_id:session.agentName,chat_id:message.chatId,instance_id:message.instanceId});let doneMcp=await createDoneMcpServer(state.env,this.natsPublish),sendMessageHooks=isTurnBased?{PreToolUse:[{matcher:"SendMessage",hooks:[createSendMessageOmniHook(state.env,this.natsPublish)]}]}:void 0,extraOptions={abortController:state.abortController,mcpServers:{"genie-omni-tools":doneMcp},...sendMessageHooks&&{hooks:sendMessageHooks}};if(state.claudeSessionId)extraOptions.resume=state.claudeSessionId;let turnPrefix=buildTurnContextPrefix(state,message,session.chatId),queryContent=message.content,pendingNudge=this.pendingNudges.get(session.id);if(pendingNudge)queryContent=`[system] ${pendingNudge}
2478
+ `).trim(),sessionId}}function buildReplyPayload(agent,chatId,instanceId,extra={}){return JSON.stringify({content:"",agent,chat_id:chatId,instance_id:instanceId,timestamp:new Date().toISOString(),...extra})}function resolveAction(params,env){if(params.skip)return{type:"skip",extra:{reason:params.reason},label:"Turn closed (skip)."};if(params.react)return{type:"react",extra:{react:String(params.react),message_id:env.OMNI_MESSAGE??""},label:`Reacted ${params.react} + turn closed.`};if(params.media)return{type:"media",extra:{content:params.caption?String(params.caption):params.text?String(params.text):"",media:String(params.media)},label:"Media sent + turn closed."};if(params.text)return{type:"text",extra:{content:String(params.text)},label:"Turn closed. Message delivered."};return{type:"skip",extra:{},label:"Turn closed (skip)."}}function handleDoneTool(params,env,natsPublish){let instanceId=env.OMNI_INSTANCE??"",chatId=env.OMNI_CHAT??"",agent=env.OMNI_AGENT??"",action=resolveAction(params,env);if(action.type==="skip"){if(natsPublish&&instanceId&&chatId)natsPublish(`omni.turn.done.${instanceId}.${chatId}`,JSON.stringify({action:"skip",...action.extra}));return action.label}if(!natsPublish||!instanceId||!chatId)return console.warn("[claude-sdk] No NATS publish available \u2014 reply dropped"),"Turn close attempted but NATS publish not available.";return natsPublish(`omni.reply.${instanceId}.${chatId}`,buildReplyPayload(agent,chatId,instanceId,action.extra)),action.label}function parseSendMessageInput(input){if(!input||typeof input!=="object")return{};let obj=input,recipient=typeof obj.recipient==="string"?obj.recipient:typeof obj.to==="string"?obj.to:void 0,body=typeof obj.message==="string"?obj.message:typeof obj.content==="string"?obj.content:void 0;return{recipient,body}}function createSendMessageOmniHook(env,natsPublish){return async(input)=>{let hookInput=input;if(hookInput.tool_name!=="SendMessage")return{};let{recipient,body}=parseSendMessageInput(hookInput.tool_input);if(recipient!=="omni")return{};let instanceId=env.OMNI_INSTANCE??"",chatId=env.OMNI_CHAT??"",agent=env.OMNI_AGENT??"";if(!instanceId||!chatId)return{};if(!body||body.trim()==="")return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:'SendMessage(recipient: "omni") requires a non-empty `message` field. Retry with the reply text.'}};if(!natsPublish)return console.warn("[claude-sdk] SendMessage(to: omni) intercepted but NATS publish unavailable \u2014 message dropped"),{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"Omni bridge unavailable \u2014 message could not be delivered."}};return natsPublish(`omni.reply.${instanceId}.${chatId}`,buildReplyPayload(agent,chatId,instanceId,{content:body})),{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"Message delivered to user via omni bridge."}}}}async function createDoneMcpServer(env,natsPublish){let{createSdkMcpServer,tool}=await import("@anthropic-ai/claude-agent-sdk");return createSdkMcpServer({name:"genie-omni-tools",tools:[tool("done","Close this turn. REQUIRED after processing the user message. Sends a final response, reacts, or skips. Call exactly once per turn.",{text:exports_external.string().optional().describe("Final message to the user"),media:exports_external.string().optional().describe("File path for media attachment"),caption:exports_external.string().optional().describe("Caption for media"),react:exports_external.string().optional().describe("Emoji reaction (instead of text)"),skip:exports_external.boolean().optional().describe("Close turn without sending anything"),reason:exports_external.string().optional().describe("Internal reason for skipping")},async(args)=>{return{content:[{type:"text",text:handleDoneTool(args,env,natsPublish)}]}})]})}class ClaudeSdkOmniExecutor{sessions=new Map;safePgCall=null;natsPublish=null;deliveryQueues=new Map;pendingNudges=new Map;setSafePgCall(fn){this.safePgCall=fn}setNatsPublish(fn){this.natsPublish=fn}async injectNudge(session,text){if(!this.sessions.has(session.id))return;this.pendingNudges.set(session.id,text)}async spawn(agentName,chatId,env,_initialMessage){if(!await resolve6(agentName))throw Error(`Agent "${agentName}" not found in genie directory`);let provider=new ClaudeSdkProvider,abortController=new AbortController,sessionId=`${agentName}:${chatId}`,registration=await this.registerInWorldA(agentName,chatId,env.OMNI_INSTANCE??"");if(this.sessions.set(sessionId,{abortController,running:!0,provider,executorId:registration?.executorId??null,claudeSessionId:registration?.claudeSessionId,dbSessionId:null,turnIndex:0,env,inFlightDeliveries:0}),registration?.executorId)await this.updateState(registration.executorId,"running",chatId);let now=Date.now();return{id:sessionId,agentName,chatId,executorType:"sdk",createdAt:now,lastActivityAt:now,sdk:{claudeSessionId:registration?.claudeSessionId,executorId:registration?.executorId}}}async registerInWorldA(agentName,chatId,instanceId){if(!this.safePgCall)return null;let agent=await this.safePgCall("sdk-find-or-create-agent",()=>findOrCreateAgent(agentName,"omni","omni"),null,{chatId});if(!agent)return null;let existing=await this.safePgCall("sdk-find-existing-executor",()=>findLatestByMetadata({agentId:agent.id,source:"omni",chatId}),null,{chatId});if(existing)return await this.safePgCall("sdk-relink-executor",()=>relinkExecutorToAgent(existing.id,agent.id),void 0,{executorId:existing.id,chatId}),await recordAuditEvent2(this.safePgCall,"session.resumed",{executor_id:existing.id,agent_id:agentName,chat_id:chatId,claude_session_id:existing.claudeSessionId}),{executorId:existing.id,claudeSessionId:existing.claudeSessionId??void 0};let executor=await this.safePgCall("sdk-create-executor",()=>createAndLinkExecutor(agent.id,"claude","api",{claudeSessionId:void 0,metadata:{source:"omni",chat_id:chatId,instance_id:instanceId}}),null,{chatId});if(executor)await recordAuditEvent2(this.safePgCall,"session.created_fresh",{executor_id:executor.id,agent_id:agentName,chat_id:chatId});return executor?{executorId:executor.id}:null}async updateState(executorId,state,chatId){if(!this.safePgCall)return;await this.safePgCall("sdk-update-executor-state",()=>updateExecutorState(executorId,state),void 0,{executorId,chatId})}async deliver(session,message){let state=this.sessions.get(session.id);if(!state)throw Error(`No SDK session found for ${session.id}`);let current=(this.deliveryQueues.get(session.id)??Promise.resolve()).then(async()=>{state.inFlightDeliveries+=1;try{await this._processDelivery(session,state,message)}finally{state.inFlightDeliveries=Math.max(0,state.inFlightDeliveries-1)}});this.deliveryQueues.set(session.id,current.catch(()=>{}))}async isBusy(session){let state=this.sessions.get(session.id);if(!state)return!1;return state.running&&state.inFlightDeliveries>0}async _processDelivery(session,state,message){let resolved=await resolve6(session.agentName);if(!resolved)throw Error(`Agent "${session.agentName}" not found in genie directory`);let entry2=resolved.entry,permissionConfig=resolvePermissionConfig(entry2.permissions),{prompt:systemPrompt,isTurnBased}=await resolveSystemPrompt(entry2,state);if(state.executorId)await this.updateState(state.executorId,"working",session.chatId);if(this.safePgCall)await recordAuditEvent2(this.safePgCall,"deliver.start",{executor_id:state.executorId??session.id,agent_id:session.agentName,chat_id:message.chatId,instance_id:message.instanceId});let doneMcp=await createDoneMcpServer(state.env,this.natsPublish),sendMessageHooks=isTurnBased?{PreToolUse:[{matcher:"SendMessage",hooks:[createSendMessageOmniHook(state.env,this.natsPublish)]}]}:void 0,extraOptions={abortController:state.abortController,mcpServers:{"genie-omni-tools":doneMcp},...sendMessageHooks&&{hooks:sendMessageHooks}};if(state.claudeSessionId)extraOptions.resume=state.claudeSessionId;let turnPrefix=buildTurnContextPrefix(state,message,session.chatId),queryContent=message.content,pendingNudge=this.pendingNudges.get(session.id);if(pendingNudge)queryContent=`[system] ${pendingNudge}
2479
2479
 
2480
2480
  ${message.content}`,this.pendingNudges.delete(session.id);if(turnPrefix)queryContent=`${turnPrefix}
2481
2481
 
@@ -2524,10 +2524,10 @@ ${queryContent}`;let{messages:queryMessages}=state.provider.runQuery({agentId:se
2524
2524
  UPDATE omni_requests
2525
2525
  SET status = 'pending', started_at = NULL, next_retry_at = ${nextRetry}
2526
2526
  WHERE id = ${id}
2527
- `}}class TurnTracker{turns=new Map;open(sessionKey2,turnId,messageId){this.turns.set(sessionKey2,{turnId,sessionKey:sessionKey2,messageId,startedAt:Date.now(),closed:!1})}close(sessionKey2,action){let turn=this.turns.get(sessionKey2);if(turn&&!turn.closed)turn.closed=!0,turn.closedAction=action}isOpen(sessionKey2){let turn=this.turns.get(sessionKey2);return turn!==void 0&&!turn.closed}getTurnId(sessionKey2){return this.turns.get(sessionKey2)?.turnId}getByTurnId(turnId){for(let turn of this.turns.values())if(turn.turnId===turnId)return turn;return}delete(sessionKey2){this.turns.delete(sessionKey2)}}var exports_omni_bridge={};__export(exports_omni_bridge,{OmniBridge:()=>OmniBridge});import{closeSync as closeSync4,existsSync as existsSync46,mkdirSync as mkdirSync20,openSync as openSync4,readFileSync as readFileSync28,unlinkSync as unlinkSync9,writeSync}from"fs";import{dirname as dirname15}from"path";function withTimeout2(p,ms,label){return new Promise((resolve11,reject)=>{let timer2=setTimeout(()=>reject(Error(`${label} timed out after ${ms}ms`)),ms);timer2.unref?.(),p.then((v)=>{clearTimeout(timer2),resolve11(v)},(err)=>{clearTimeout(timer2),reject(err)})})}function isPgConnectionError(err){if(!err||typeof err!=="object")return!1;let e=err,code=e.code??"";if(["ECONNREFUSED","ECONNRESET","ETIMEDOUT","ENOTFOUND","EPIPE","EHOSTUNREACH"].includes(code))return!0;let msg=e.message??String(err);return/ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EPIPE|connection terminated|connection closed|server closed the connection|the database system is shutting down/i.test(msg)}class OmniBridge{nc=null;sub=null;executor;turnTracker=new TurnTracker;sessions=new Map;messageQueue=[];recentMessageIds=new Map;idleCheckTimer=null;sc=import_nats3.StringCodec();sql=null;pgAvailable=!1;pgProvider;natsConnectFn;queueConfig;queue=null;sessionStore=null;natsUrl;idleTimeoutMs;maxConcurrent;executorType;pidfilePath=null;startedAtMs=0;pingSub=null;signalCleanup=null;constructor(config={}){if(this.natsUrl=config.natsUrl??process.env.GENIE_NATS_URL??DEFAULT_NATS_URL2,this.idleTimeoutMs=config.idleTimeoutMs??(process.env.GENIE_IDLE_TIMEOUT_MS?Number(process.env.GENIE_IDLE_TIMEOUT_MS):DEFAULT_IDLE_TIMEOUT_MS2),this.maxConcurrent=config.maxConcurrent??(process.env.GENIE_MAX_CONCURRENT?Number(process.env.GENIE_MAX_CONCURRENT):DEFAULT_MAX_CONCURRENT),this.pgProvider=config.pgProvider??(async()=>{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return await getConnection2()}),this.natsConnectFn=config.natsConnectFn??import_nats3.connect,this.queueConfig=config.queue??{},this.executorType=resolveExecutorType(config.executorType),this.executorType==="sdk")this.executor=new ClaudeSdkOmniExecutor;else this.executor=new ClaudeCodeOmniExecutor}async start(){if(this.nc){console.log("[omni-bridge] Already running");return}console.log(`[omni-bridge] Connecting to NATS at ${this.natsUrl}...`),this.nc=await this.natsConnectFn({servers:this.natsUrl,name:"genie-omni-bridge",reconnect:!0,maxReconnectAttempts:-1,reconnectTimeWait:2000}),console.log("[omni-bridge] Connected to NATS"),await this.setupPg(),this.wireExecutorHooks(),this.subscribeOmniChannels(),this.startedAtMs=Date.now(),this.subscribePingChannel(),await this.claimPidfile(),this.armSignalCleanup(),this.idleCheckTimer=setInterval(()=>this.checkIdleSessions(),IDLE_CHECK_INTERVAL_MS),console.log(`[omni-bridge] Listening on omni.message.> (max_concurrent=${this.maxConcurrent}, idle_timeout=${this.idleTimeoutMs}ms)`)}async setupPg(){if(await this.probePg(),this.pgAvailable&&this.sql)this.sessionStore=new BridgeSessionStore(this.sql),await this.recoverSessions();if(this.executorType==="sdk"&&this.pgAvailable&&this.sql)this.queue=new OmniQueue(this.sql,(_req,msg)=>this.routeMessage(msg),this.queueConfig),await this.queue.recoverStale(),this.queue.start()}wireExecutorHooks(){this.executor.setSafePgCall(this.safePgCall.bind(this));let sc=this.sc,nc=this.nc;if(!nc)return;this.executor.setNatsPublish((topic,payload)=>{nc.publish(topic,sc.encode(payload))})}subscribeOmniChannels(){if(!this.nc)return;this.sub=this.nc.subscribe("omni.message.>",{queue:"genie-bridge"}),this.processSubscription();let turnSubs=["omni.turn.open.>","omni.turn.done.>","omni.turn.nudge.>","omni.turn.timeout.>"];for(let topic of turnSubs){let sub=this.nc.subscribe(topic,{queue:"genie-bridge"});this.processTurnEvents(sub)}let sessionResetSub=this.nc.subscribe("omni.session.reset.>",{queue:"genie-bridge"});this.processSessionResetEvents(sessionResetSub)}subscribePingChannel(){if(!this.nc)return;this.pingSub=this.nc.subscribe(BRIDGE_PING_SUBJECT);let pingSub=this.pingSub,pingSc=this.sc,pingStart=this.startedAtMs;(async()=>{for await(let m of pingSub){let pong={ok:!0,pid:process.pid,uptimeMs:Date.now()-pingStart,subjects:["omni.message.>","omni.turn.open.>","omni.session.reset.>",BRIDGE_PING_SUBJECT]};try{m.respond(pingSc.encode(JSON.stringify(pong)))}catch{}}})().catch(()=>{})}async claimPidfile(){this.pidfilePath=getBridgePidfilePath();try{mkdirSync20(dirname15(this.pidfilePath),{recursive:!0}),this.evictStalePidfile(this.pidfilePath);let fd=openSync4(this.pidfilePath,"wx"),payload=JSON.stringify({pid:process.pid,startedAt:this.startedAtMs,subjects:["omni.message.>","omni.turn.open.>","omni.session.reset.>",BRIDGE_PING_SUBJECT],natsUrl:this.natsUrl});writeSync(fd,payload),closeSync4(fd)}catch(err){await this.rollbackStartOnPidfileError(err)}}evictStalePidfile(path3){if(!existsSync46(path3))return;let stalePid=null;try{let raw=readFileSync28(path3,"utf8"),parsed=JSON.parse(raw);if(typeof parsed.pid==="number"&&Number.isFinite(parsed.pid))stalePid=parsed.pid}catch{}if(stalePid!==null&&this.isPidAlive(stalePid))throw Error(`pidfile locked by PID ${stalePid}`);try{unlinkSync9(path3)}catch{}}isPidAlive(pid){try{return process.kill(pid,0),!0}catch(probeErr){return probeErr.code!=="ESRCH"}}async rollbackStartOnPidfileError(err){this.pidfilePath=null;let detail=err instanceof Error?err.message:String(err);try{if(this.pingSub)this.pingSub.unsubscribe()}catch{}this.pingSub=null;try{await this.nc?.drain()}catch{}throw this.nc=null,Error(`[omni-bridge] pidfile locked at ${getBridgePidfilePath()}: ${detail}`)}armSignalCleanup(){let onSignal=()=>{if(this.pidfilePath){try{unlinkSync9(this.pidfilePath)}catch{}this.pidfilePath=null}};process.once("SIGTERM",onSignal),process.once("SIGINT",onSignal),this.signalCleanup=()=>{process.removeListener("SIGTERM",onSignal),process.removeListener("SIGINT",onSignal)}}async stop(){if(!this.nc){console.log("[omni-bridge] Not running");return}if(console.log("[omni-bridge] Shutting down..."),this.queue)this.queue.stop(),this.queue=null;if(this.idleCheckTimer)clearInterval(this.idleCheckTimer),this.idleCheckTimer=null;if(await this.shutdownActiveSessions(),this.unsubscribeChannels(),this.clearPidfileOnStop(),this.signalCleanup)this.signalCleanup(),this.signalCleanup=null;try{await this.nc.drain()}catch{}this.nc=null,this.sql=null,this.pgAvailable=!1,this.sessionStore=null,console.log("[omni-bridge] Stopped")}async shutdownActiveSessions(){for(let[key,entry2]of this.sessions){if(entry2.idleTimer)clearTimeout(entry2.idleTimer);if(entry2.spawning||!entry2.session)continue;if(entry2.session.executorType==="tmux"){console.log(`[omni-bridge] Detaching from tmux session ${key} (pane stays alive)`);continue}try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down session ${key}:`,err)}let closeId=entry2.pgBridgeSessionId;if(closeId&&this.sessionStore)await this.safePgCall("session_close_sdk",(sql)=>new BridgeSessionStore(sql).close(closeId),void 0)}this.sessions.clear()}unsubscribeChannels(){if(this.sub)this.sub.unsubscribe(),this.sub=null;if(this.pingSub){try{this.pingSub.unsubscribe()}catch{}this.pingSub=null}}clearPidfileOnStop(){if(!this.pidfilePath)return;try{unlinkSync9(this.pidfilePath)}catch{}this.pidfilePath=null}async status(){let now=Date.now(),activeFromPg=null,executorIds=[];if(this.pgAvailable&&this.sql){let rows=await this.safePgCall("status_active_count",async(sql)=>sql`
2527
+ `}}class TurnTracker{turns=new Map;open(sessionKey2,turnId,messageId){this.turns.set(sessionKey2,{turnId,sessionKey:sessionKey2,messageId,startedAt:Date.now(),closed:!1})}close(sessionKey2,action){let turn=this.turns.get(sessionKey2);if(turn&&!turn.closed)turn.closed=!0,turn.closedAction=action}isOpen(sessionKey2){let turn=this.turns.get(sessionKey2);return turn!==void 0&&!turn.closed}getTurnId(sessionKey2){return this.turns.get(sessionKey2)?.turnId}getByTurnId(turnId){for(let turn of this.turns.values())if(turn.turnId===turnId)return turn;return}delete(sessionKey2){this.turns.delete(sessionKey2)}}var exports_omni_bridge={};__export(exports_omni_bridge,{OmniBridge:()=>OmniBridge});import{closeSync as closeSync4,existsSync as existsSync46,mkdirSync as mkdirSync20,openSync as openSync4,readFileSync as readFileSync28,unlinkSync as unlinkSync9,writeSync}from"fs";import{dirname as dirname15}from"path";function withTimeout2(p,ms,label){return new Promise((resolve11,reject)=>{let timer2=setTimeout(()=>reject(Error(`${label} timed out after ${ms}ms`)),ms);timer2.unref?.(),p.then((v)=>{clearTimeout(timer2),resolve11(v)},(err)=>{clearTimeout(timer2),reject(err)})})}function isPgConnectionError(err){if(!err||typeof err!=="object")return!1;let e=err,code=e.code??"";if(["ECONNREFUSED","ECONNRESET","ETIMEDOUT","ENOTFOUND","EPIPE","EHOSTUNREACH"].includes(code))return!0;let msg=e.message??String(err);return/ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EPIPE|connection terminated|connection closed|server closed the connection|the database system is shutting down/i.test(msg)}class OmniBridge{nc=null;sub=null;executor;turnTracker=new TurnTracker;heartbeatPublisher=null;sessions=new Map;messageQueue=[];recentMessageIds=new Map;idleCheckTimer=null;sc=import_nats4.StringCodec();sql=null;pgAvailable=!1;pgProvider;natsConnectFn;queueConfig;queue=null;sessionStore=null;natsUrl;idleTimeoutMs;maxConcurrent;executorType;pidfilePath=null;startedAtMs=0;pingSub=null;signalCleanup=null;constructor(config={}){if(this.natsUrl=config.natsUrl??process.env.GENIE_NATS_URL??DEFAULT_NATS_URL2,this.idleTimeoutMs=config.idleTimeoutMs??(process.env.GENIE_IDLE_TIMEOUT_MS?Number(process.env.GENIE_IDLE_TIMEOUT_MS):DEFAULT_IDLE_TIMEOUT_MS2),this.maxConcurrent=config.maxConcurrent??(process.env.GENIE_MAX_CONCURRENT?Number(process.env.GENIE_MAX_CONCURRENT):DEFAULT_MAX_CONCURRENT),this.pgProvider=config.pgProvider??(async()=>{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return await getConnection2()}),this.natsConnectFn=config.natsConnectFn??import_nats4.connect,this.queueConfig=config.queue??{},this.executorType=resolveExecutorType(config.executorType),this.executorType==="sdk")this.executor=new ClaudeSdkOmniExecutor;else this.executor=new ClaudeCodeOmniExecutor}async start(){if(this.nc){console.log("[omni-bridge] Already running");return}console.log(`[omni-bridge] Connecting to NATS at ${this.natsUrl}...`),this.nc=await this.natsConnectFn({servers:this.natsUrl,name:"genie-omni-bridge",reconnect:!0,maxReconnectAttempts:-1,reconnectTimeWait:2000}),console.log("[omni-bridge] Connected to NATS"),await this.setupPg(),this.wireExecutorHooks(),this.subscribeOmniChannels(),this.startedAtMs=Date.now(),this.subscribePingChannel(),await this.claimPidfile(),this.armSignalCleanup(),this.idleCheckTimer=setInterval(()=>this.checkIdleSessions(),IDLE_CHECK_INTERVAL_MS),console.log(`[omni-bridge] Listening on omni.message.> (max_concurrent=${this.maxConcurrent}, idle_timeout=${this.idleTimeoutMs}ms)`)}async setupPg(){if(await this.probePg(),this.pgAvailable&&this.sql)this.sessionStore=new BridgeSessionStore(this.sql),await this.recoverSessions();if(this.executorType==="sdk"&&this.pgAvailable&&this.sql)this.queue=new OmniQueue(this.sql,(_req,msg)=>this.routeMessage(msg),this.queueConfig),await this.queue.recoverStale(),this.queue.start()}wireExecutorHooks(){this.executor.setSafePgCall(this.safePgCall.bind(this));let sc=this.sc,nc=this.nc;if(!nc)return;let publish=(topic,payload)=>{nc.publish(topic,sc.encode(payload))};this.executor.setNatsPublish(publish),this.heartbeatPublisher=new HeartbeatPublisher({publish})}subscribeOmniChannels(){if(!this.nc)return;this.sub=this.nc.subscribe("omni.message.>",{queue:"genie-bridge"}),this.processSubscription();let turnSubs=["omni.turn.open.>","omni.turn.done.>","omni.turn.nudge.>","omni.turn.timeout.>"];for(let topic of turnSubs){let sub=this.nc.subscribe(topic,{queue:"genie-bridge"});this.processTurnEvents(sub)}let sessionResetSub=this.nc.subscribe("omni.session.reset.>",{queue:"genie-bridge"});this.processSessionResetEvents(sessionResetSub)}subscribePingChannel(){if(!this.nc)return;this.pingSub=this.nc.subscribe(BRIDGE_PING_SUBJECT);let pingSub=this.pingSub,pingSc=this.sc,pingStart=this.startedAtMs;(async()=>{for await(let m of pingSub){let pong={ok:!0,pid:process.pid,uptimeMs:Date.now()-pingStart,subjects:["omni.message.>","omni.turn.open.>","omni.session.reset.>",BRIDGE_PING_SUBJECT]};try{m.respond(pingSc.encode(JSON.stringify(pong)))}catch{}}})().catch(()=>{})}async claimPidfile(){this.pidfilePath=getBridgePidfilePath();try{mkdirSync20(dirname15(this.pidfilePath),{recursive:!0}),this.evictStalePidfile(this.pidfilePath);let fd=openSync4(this.pidfilePath,"wx"),payload=JSON.stringify({pid:process.pid,startedAt:this.startedAtMs,subjects:["omni.message.>","omni.turn.open.>","omni.session.reset.>",BRIDGE_PING_SUBJECT],natsUrl:this.natsUrl});writeSync(fd,payload),closeSync4(fd)}catch(err){await this.rollbackStartOnPidfileError(err)}}evictStalePidfile(path3){if(!existsSync46(path3))return;let stalePid=null;try{let raw=readFileSync28(path3,"utf8"),parsed=JSON.parse(raw);if(typeof parsed.pid==="number"&&Number.isFinite(parsed.pid))stalePid=parsed.pid}catch{}if(stalePid!==null&&this.isPidAlive(stalePid))throw Error(`pidfile locked by PID ${stalePid}`);try{unlinkSync9(path3)}catch{}}isPidAlive(pid){try{return process.kill(pid,0),!0}catch(probeErr){return probeErr.code!=="ESRCH"}}async rollbackStartOnPidfileError(err){this.pidfilePath=null;let detail=err instanceof Error?err.message:String(err);try{if(this.pingSub)this.pingSub.unsubscribe()}catch{}this.pingSub=null;try{await this.nc?.drain()}catch{}throw this.nc=null,Error(`[omni-bridge] pidfile locked at ${getBridgePidfilePath()}: ${detail}`)}armSignalCleanup(){let onSignal=()=>{if(this.pidfilePath){try{unlinkSync9(this.pidfilePath)}catch{}this.pidfilePath=null}};process.once("SIGTERM",onSignal),process.once("SIGINT",onSignal),this.signalCleanup=()=>{process.removeListener("SIGTERM",onSignal),process.removeListener("SIGINT",onSignal)}}async stop(){if(!this.nc){console.log("[omni-bridge] Not running");return}if(console.log("[omni-bridge] Shutting down..."),this.queue)this.queue.stop(),this.queue=null;if(this.idleCheckTimer)clearInterval(this.idleCheckTimer),this.idleCheckTimer=null;if(this.heartbeatPublisher?.stopAll(),this.heartbeatPublisher=null,await this.shutdownActiveSessions(),this.unsubscribeChannels(),this.clearPidfileOnStop(),this.signalCleanup)this.signalCleanup(),this.signalCleanup=null;try{await this.nc.drain()}catch{}this.nc=null,this.sql=null,this.pgAvailable=!1,this.sessionStore=null,console.log("[omni-bridge] Stopped")}async shutdownActiveSessions(){for(let[key,entry2]of this.sessions){if(entry2.idleTimer)clearTimeout(entry2.idleTimer);if(entry2.spawning||!entry2.session)continue;if(entry2.session.executorType==="tmux"){console.log(`[omni-bridge] Detaching from tmux session ${key} (pane stays alive)`);continue}try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down session ${key}:`,err)}let closeId=entry2.pgBridgeSessionId;if(closeId&&this.sessionStore)await this.safePgCall("session_close_sdk",(sql)=>new BridgeSessionStore(sql).close(closeId),void 0)}this.sessions.clear()}unsubscribeChannels(){if(this.sub)this.sub.unsubscribe(),this.sub=null;if(this.pingSub){try{this.pingSub.unsubscribe()}catch{}this.pingSub=null}}clearPidfileOnStop(){if(!this.pidfilePath)return;try{unlinkSync9(this.pidfilePath)}catch{}this.pidfilePath=null}async status(){let now=Date.now(),activeFromPg=null,executorIds=[];if(this.pgAvailable&&this.sql){let rows=await this.safePgCall("status_active_count",async(sql)=>sql`
2528
2528
  SELECT id FROM executors
2529
2529
  WHERE ended_at IS NULL AND metadata->>'source' = 'omni'
2530
- `,null);if(rows)activeFromPg=rows.length,executorIds=rows.map((r)=>r.id)}let pgQueue=null;if(this.queue)pgQueue=await this.safePgCall("status_queue_stats",(_sql)=>this.queue?.stats()??Promise.resolve(null),null);return{connected:this.nc!==null,natsUrl:this.natsUrl,pgAvailable:this.pgAvailable,activeSessions:activeFromPg??this.sessions.size,maxConcurrent:this.maxConcurrent,idleTimeoutMs:this.idleTimeoutMs,queueDepth:pgQueue?pgQueue.pending+pgQueue.processing:this.messageQueue.length,executorType:this.executorType,executorIds,pgQueue,sessions:Array.from(this.sessions.entries()).map(([key,entry2])=>({id:key,agentName:entry2.session.agentName,chatId:entry2.session.chatId,instanceId:entry2.instanceId,executorType:entry2.session.executorType,spawning:entry2.spawning,idleMs:now-entry2.session.lastActivityAt,bufferSize:entry2.buffer.length}))}}async probePg(){try{let sql=await withTimeout2(this.pgProvider(),PG_STARTUP_PROBE_TIMEOUT_MS,"PG provider startup");await withTimeout2(Promise.resolve(sql`SELECT 1`),PG_STARTUP_PROBE_TIMEOUT_MS,"PG SELECT 1 probe"),this.sql=sql,this.pgAvailable=!0,console.log("[omni-bridge] PG reachable \u2014 session recovery enabled")}catch(err){this.sql=null,this.pgAvailable=!1;let msg=err instanceof Error?err.message:String(err);if(isPgConnectionError(err)){console.warn(`[omni-bridge] PG unavailable \u2014 session recovery disabled (${msg})`);return}throw Error(`[omni-bridge] PG schema mismatch or setup error: ${msg}. ${"Run `bun run migrate` (or the equivalent migration command) and retry."}`)}}async recoverSessions(){if(!this.sessionStore)return;let orphanedCount=await this.safePgCall("recover_orphan_all",(sql)=>new BridgeSessionStore(sql).markAllOrphaned(),0);if(orphanedCount>0)console.log(`[omni-bridge] Startup cleanup: orphaned ${orphanedCount} stale session(s) from previous run`)}async safePgCall(op,fn,fallback,ctx){if(!this.pgAvailable||!this.sql)return fallback;let sql=this.sql;try{return await withTimeout2(fn(sql),PG_RUNTIME_QUERY_TIMEOUT_MS,`safePgCall(${op})`)}catch(err){let msg=err instanceof Error?err.message:String(err),execPart=ctx?.executorId?` executor_id=${ctx.executorId}`:"",chatPart=ctx?.chatId?` chat_id=${ctx.chatId}`:"";if(console.warn(`[omni-bridge] safePgCall(${op}) failed${execPart}${chatPart}: ${msg}`),isPgConnectionError(err))this.pgAvailable=!1,this.sql=null,console.warn("[omni-bridge] PG connection lost \u2014 switching to degraded mode");return fallback}}fillSubjectMetadata(parsed,subject){let parts=subject.split(".");if(parts.length<4)return;parsed.instanceId=parsed.instanceId||parts[2],parsed.chatId=parsed.chatId||parts[3]}isDuplicateMessage(messageId){if(!messageId)return!1;if(this.recentMessageIds.has(messageId))return!0;if(this.recentMessageIds.set(messageId,Date.now()),this.recentMessageIds.size>1000){let cutoff=Date.now()-60000;for(let[id,ts3]of this.recentMessageIds)if(ts3<cutoff)this.recentMessageIds.delete(id)}return!1}async dispatchMessage(parsed){if(this.queue){let env=parsed.env??{};await this.queue.enqueue(parsed,env);return}let key=`${parsed.agent}:${parsed.chatId}`,hasSession=this.sessions.has(key);console.log(`[omni-bridge] Routing message for ${key} (hasSession=${hasSession}, queue=${!!this.queue})`),await this.routeMessage(parsed),console.log(`[omni-bridge] routeMessage done for ${key}`)}async processSubscription(){if(!this.sub)return;for await(let msg of this.sub)try{let data=this.sc.decode(msg.data),parsed=JSON.parse(data);this.fillSubjectMetadata(parsed,msg.subject),console.log(`[omni-bridge] NATS message received: ${msg.subject} agent=${parsed.agent} chat=${parsed.chatId}`);let messageId=parsed.messageId;if(this.isDuplicateMessage(messageId)){console.log(`[omni-bridge] Dedup: skipping duplicate messageId=${messageId}`);continue}if(!parsed.chatId||!parsed.agent){console.warn("[omni-bridge] Dropping message: missing chatId or agent",msg.subject);continue}await this.dispatchMessage(parsed)}catch(err){console.error("[omni-bridge] Error processing message:",err)}}async processTurnEvents(sub){for await(let msg of sub)try{let payload=JSON.parse(this.sc.decode(msg.data)),parts=msg.subject.split("."),eventType=parts[2],instanceId=parts[3],chatId=parts.slice(4).join(".");console.log(`[omni-bridge] Turn event: ${eventType} instance=${instanceId} chat=${chatId}`);let sessionKey2=this.findSessionKey(instanceId,chatId);if(!sessionKey2&&payload.turnId){if(sessionKey2=this.findSessionKeyByTurnId(payload.turnId),sessionKey2)console.log(`[omni-bridge] Matched session via turnId fallback: ${sessionKey2}`)}if(sessionKey2)await this.routeTurnEvent(eventType,sessionKey2,payload);else console.log(`[omni-bridge] No session found for turn.${eventType} (instance=${instanceId}, chat=${chatId})`)}catch(err){console.warn("[omni-bridge] Error processing turn event:",err)}}async routeTurnEvent(eventType,sessionKey2,payload){switch(eventType){case"open":this.turnTracker.open(sessionKey2,payload.turnId,payload.messageId);break;case"done":this.turnTracker.close(sessionKey2,payload.action),await this.handleTurnDone(sessionKey2);break;case"nudge":await this.handleTurnNudge(sessionKey2,payload.message);break;case"timeout":await this.handleTurnTimeout(sessionKey2);break}}findSessionKeyByTurnId(turnId){for(let[key]of this.sessions)if(this.turnTracker.getTurnId(key)===turnId)return key;return}chatIdMap=new Map;findSessionKey(instanceId,chatId){let resolvedChatId=this.chatIdMap.get(chatId);for(let[key,entry2]of this.sessions){if(entry2.instanceId!==instanceId)continue;if(entry2.session?.chatId===chatId)return key;if(resolvedChatId&&entry2.session?.chatId===resolvedChatId)return key;if(entry2.spawning&&key.endsWith(`:${chatId}`))return key;if(resolvedChatId&&entry2.spawning&&key.endsWith(`:${resolvedChatId}`))return key}return}async handleTurnNudge(sessionKey2,nudgeText){let entry2=this.sessions.get(sessionKey2);if(!entry2?.session)return;try{await this.executor.injectNudge(entry2.session,nudgeText)}catch(err){console.warn(`[omni-bridge] Failed to inject nudge for ${sessionKey2}:`,err)}}async processSessionResetEvents(sub){for await(let msg of sub)try{let parts=msg.subject.split(".");if(parts.length<5){console.warn(`[omni-bridge] Malformed session-reset subject: ${msg.subject}`);continue}let instanceId=parts[3],chatId=parts.slice(4).join("."),action;try{action=JSON.parse(this.sc.decode(msg.data)).action}catch{}await this.handleSessionReset(instanceId,chatId,action)}catch(err){console.warn("[omni-bridge] Error processing session reset event:",err)}}async handleSessionReset(instanceId,chatId,action){let sessionKey2=this.findSessionKey(instanceId,chatId);if(!sessionKey2){console.log(`[omni-bridge] Session reset for cold chat ${instanceId}/${chatId} \u2014 no-op`);return}let entry2=this.sessions.get(sessionKey2);if(!entry2)return;let actionTag=action?` (action=${action})`:"";if(entry2.spawning){console.log(`[omni-bridge] Session reset for spawning ${sessionKey2}${actionTag}, marking cancelled`),entry2.cancelled=!0,entry2.buffer=[],this.turnTracker.close(sessionKey2,"reset"),await this.removeSession(sessionKey2),await this.drainQueue();return}if(!entry2.session)return;console.log(`[omni-bridge] Session reset for ${sessionKey2}${actionTag}, evicting`),this.turnTracker.close(sessionKey2,"reset");try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down reset session ${sessionKey2}:`,err)}await this.removeSession(sessionKey2),await this.drainQueue()}async handleTurnDone(sessionKey2){if(!this.sessions.get(sessionKey2)?.session)return;console.log(`[omni-bridge] Turn done for ${sessionKey2}, session stays alive for next message`),this.resetIdleTimer(sessionKey2)}async handleTurnTimeout(sessionKey2){let entry2=this.sessions.get(sessionKey2);if(!entry2?.session)return;console.warn(`[omni-bridge] Turn timed out for ${sessionKey2}, evicting session`),this.turnTracker.close(sessionKey2,"timeout");try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down timed-out session ${sessionKey2}:`,err)}this.sessions.delete(sessionKey2)}async routeMessage(message){let key=`${message.agent}:${message.chatId}`,entry2=this.sessions.get(key);if(entry2){if(entry2.spawning){if(entry2.buffer.length<MAX_BUFFER_PER_CHAT)entry2.buffer.push(message);else console.warn(`[omni-bridge] Buffer full (${MAX_BUFFER_PER_CHAT}) for ${key}, dropping message from ${message.sender}`),await this.publishBufferFullReply(message);return}if(await this.executor.isAlive(entry2.session)){await this.executor.deliver(entry2.session,message),this.resetIdleTimer(key);let bsId=entry2.pgBridgeSessionId;if(bsId&&this.sessionStore)this.safePgCall("session_activity",(sql)=>new BridgeSessionStore(sql).recordActivity(bsId),void 0,{chatId:message.chatId});return}await this.removeSession(key)}await this.spawnSession(message)}async spawnSession(message){let key=`${message.agent}:${message.chatId}`,existing=this.sessions.get(key);if(existing){if(existing.buffer.length<MAX_BUFFER_PER_CHAT)existing.buffer.push(message),console.log(`[omni-bridge] Buffered message for existing session ${key} (buffer=${existing.buffer.length})`);return}if(this.sessions.size>=this.maxConcurrent){this.messageQueue.push(message),await this.publishAutoReply(message),console.log(`[omni-bridge] Max concurrent (${this.maxConcurrent}) reached, queued message for ${key}`);return}let placeholder={session:null,instanceId:message.instanceId,spawning:!0,buffer:[message],idleTimer:null};this.sessions.set(key,placeholder);try{let raw=message,payloadEnv=raw.env,spawnEnv={OMNI_API_KEY:payloadEnv?.OMNI_API_KEY??process.env.OMNI_API_KEY??"",OMNI_INSTANCE:payloadEnv?.OMNI_INSTANCE??message.instanceId,OMNI_CHAT:payloadEnv?.OMNI_CHAT??message.chatId,OMNI_MESSAGE:payloadEnv?.OMNI_MESSAGE??raw.messageId??"",OMNI_TURN_ID:payloadEnv?.OMNI_TURN_ID||"",OMNI_SENDER_NAME:payloadEnv?.OMNI_SENDER_NAME??message.sender??""};console.log(`[omni-bridge] Spawning session for ${key}...`);let session=await this.executor.spawn(message.agent,message.chatId,spawnEnv,message.content);if(placeholder.cancelled){console.log(`[omni-bridge] Spawn for ${key} completed but was cancelled by reset, shutting down`);try{await this.executor.shutdown(session)}catch(err){console.warn(`[omni-bridge] Error shutting down cancelled spawn for ${key}:`,err)}return}if(placeholder.session=session,placeholder.spawning=!1,this.sessionStore){let claudeSessionId=session.sdk?.claudeSessionId??session.tmux?.claudeSessionId,pgId=await this.safePgCall("session_create",(sql)=>new BridgeSessionStore(sql).create({instanceId:message.instanceId,chatId:message.chatId,agentName:message.agent,executorId:session.sdk?.executorId,tmuxPaneId:session.tmux?.paneId,claudeSessionId}),void 0,{chatId:message.chatId});if(pgId)placeholder.pgBridgeSessionId=pgId}for(let buffered of placeholder.buffer)await this.executor.deliver(session,buffered);placeholder.buffer=[],this.resetIdleTimer(key);let sessionTag=session.executorType==="tmux"?`(tmux pane=${session.tmux?.paneId})`:"(executor=sdk)";console.log(`[omni-bridge] Session active: ${key} ${sessionTag}`)}catch(err){console.error(`[omni-bridge] Failed to spawn session for ${key}:`,err);let lostMessages=placeholder.buffer;if(lostMessages.length>0)console.warn(`[omni-bridge] Re-queuing ${lostMessages.length} buffered message(s) from failed spawn for ${key}`),this.messageQueue.push(...lostMessages);this.sessions.delete(key)}}resetIdleTimer(key){let entry2=this.sessions.get(key);if(!entry2)return;if(entry2.idleTimer)clearTimeout(entry2.idleTimer);entry2.idleTimer=setTimeout(async()=>{console.log(`[omni-bridge] Idle timeout for ${key}, shutting down...`);try{await this.executor.shutdown(entry2.session)}catch{}await this.removeSession(key),await this.drainQueue()},this.idleTimeoutMs)}async checkIdleSessions(){let now=Date.now();for(let[key,entry2]of this.sessions){if(entry2.spawning)continue;if(!await this.executor.isAlive(entry2.session)){console.log(`[omni-bridge] Dead session detected: ${key}`),await this.removeSession(key);continue}let idleMs=now-entry2.session.lastActivityAt;if(idleMs>this.idleTimeoutMs){console.log(`[omni-bridge] Forcing idle shutdown: ${key} (idle ${Math.round(idleMs/1000)}s)`);try{await this.executor.shutdown(entry2.session)}catch{}await this.removeSession(key)}}}async removeSession(key){let entry2=this.sessions.get(key);if(entry2?.idleTimer)clearTimeout(entry2.idleTimer);let closeId=entry2?.pgBridgeSessionId;if(closeId&&this.sessionStore)await this.safePgCall("session_close",(sql)=>new BridgeSessionStore(sql).close(closeId),void 0);this.sessions.delete(key)}async drainQueue(){while(this.messageQueue.length>0){if(this.sessions.size>=this.maxConcurrent)break;let message=this.messageQueue.shift();if(message)await this.spawnSession(message)}}async publishBufferFullReply(message){if(!this.nc)return;let topic=`omni.reply.${message.instanceId}.${message.chatId}`,reply={content:"Fila de mensagens cheia, por favor aguarde e tente novamente.",agent:message.agent,chat_id:message.chatId,instance_id:message.instanceId,timestamp:new Date().toISOString(),auto_reply:!0};this.nc.publish(topic,this.sc.encode(JSON.stringify(reply)))}async publishAutoReply(message){if(!this.nc)return;let topic=`omni.reply.${message.instanceId}.${message.chatId}`,reply={content:"Aguarde um momento, estou atendendo outros clientes.",agent:message.agent,chat_id:message.chatId,instance_id:message.instanceId,timestamp:new Date().toISOString(),auto_reply:!0};this.nc.publish(topic,this.sc.encode(JSON.stringify(reply)))}}var import_nats3,DEFAULT_NATS_URL2="localhost:4222",DEFAULT_IDLE_TIMEOUT_MS2=900000,DEFAULT_MAX_CONCURRENT=20,MAX_BUFFER_PER_CHAT=50,IDLE_CHECK_INTERVAL_MS=30000,PG_STARTUP_PROBE_TIMEOUT_MS=5000,PG_RUNTIME_QUERY_TIMEOUT_MS=2000;var init_omni_bridge=__esm(()=>{init_bridge_status();init_executor_config();init_claude_code2();init_claude_sdk2();import_nats3=__toESM(require_mod4(),1)});import{existsSync as existsSync47,mkdirSync as mkdirSync21,readFileSync as readFileSync29,readdirSync as readdirSync10,renameSync as renameSync7,statSync as statSync12,writeFileSync as writeFileSync18}from"fs";import{homedir as homedir38}from"os";import{dirname as dirname16,join as join59,resolve as resolvePath4}from"path";import{pathToFileURL as pathToFileURL2}from"url";function scanDir(dirAbs){if(!existsSync47(dirAbs))return[];return readdirSync10(dirAbs).filter((name)=>name.endsWith(".ts")&&!name.startsWith("_")&&!name.endsWith(".test.ts")).map((name)=>join59(dirAbs,name)).filter((p)=>{try{return statSync12(p).isFile()}catch{return!1}})}function discoverHooks(opts){let home=homedir38(),discovered=[],teamsRoot=join59(home,".claude","teams");if(existsSync47(teamsRoot))for(let teamName of readdirSync10(teamsRoot)){if(teamName.startsWith("_"))continue;let teamHooksDir=join59(teamsRoot,teamName,"hooks");for(let path3 of scanDir(teamHooksDir))discovered.push({path:path3,scope:"team",teamName})}if(opts.repoRoot){let repoHooksDir=join59(opts.repoRoot,".genie","hooks"),repoRemoteUrl=opts.resolveRepoRemoteUrl?.(opts.repoRoot)??void 0;for(let path3 of scanDir(repoHooksDir))discovered.push({path:path3,scope:"repo",repoRemoteUrl})}let globalHooksDir=join59(home,".genie","hooks");for(let path3 of scanDir(globalHooksDir))discovered.push({path:path3,scope:"global"});return discovered}function quarantine(filePath,errorMessage){let dir=dirname16(filePath),quarantineDir=join59(dir,"_quarantine");if(!existsSync47(quarantineDir))mkdirSync21(quarantineDir,{recursive:!0});let basename11=filePath.split("/").pop()??"unknown.ts",target=join59(quarantineDir,basename11);try{renameSync7(filePath,target)}catch{}try{writeFileSync18(`${target}.error`,errorMessage,"utf-8")}catch{}return target}function validateHandler(imported){if(typeof imported!=="object"||imported===null)return"expected default export to be an object";let handler=imported;if(handler.version!=="1")return`unknown handler version: ${String(handler.version)} (expected '1')`;if(typeof handler.name!=="string"||handler.name.length===0)return"handler.name must be a non-empty string";if(typeof handler.event!=="string")return"handler.event must be a HookEventName string";if(typeof handler.priority!=="number"||Number.isNaN(handler.priority))return"handler.priority must be a finite number";if(typeof handler.fn!=="function")return"handler.fn must be a function";return null}async function loadExternalHooks(opts={}){let trustPath=opts.trustPath??defaultTrustPath(),trustFile;try{trustFile=readTrustFile(trustPath)}catch(err){let msg=err instanceof Error?err.message:String(err);return console.warn(`[hook-loader] trust file unreadable at ${trustPath}: ${msg} \u2014 refusing to load any external hooks`),[]}let importer=opts.importer??((url)=>import(url.href)),discovered=discoverHooks(opts),loaded=[],seenNames=new Map,outcomes=[];for(let source of discovered){let verify=verifyTrust(source.path,trustFile,{currentRepoRemoteUrl:source.repoRemoteUrl});if(!verify.trusted){outcomes.push({kind:"untrusted",reason:verify.reason,source});continue}let imported;try{imported=await importer(pathToFileURL2(resolvePath4(source.path)))}catch(err){let msg=err instanceof Error?`${err.name}: ${err.message}`:String(err),quarantined=quarantine(source.path,msg);outcomes.push({kind:"broken",error:msg,source,quarantined});continue}let handlerCandidate=imported.default,validationError=validateHandler(handlerCandidate);if(validationError!==null){let quarantined=quarantine(source.path,validationError);outcomes.push({kind:"broken",error:validationError,source,quarantined});continue}let stamped={...handlerCandidate,source:source.scope,manifest_path:source.path},existing=seenNames.get(stamped.name);if(existing){if(console.warn(`[hook-loader] handler name collision: "${stamped.name}" registered from ${existing.path} (${existing.scope}) shadows ${source.path} (${source.scope})`),opts.strict)throw Error(`--strict-hooks: handler name collision on "${stamped.name}" between ${existing.path} and ${source.path}`);outcomes.push({kind:"shadowed",handler:stamped,source,shadowedBy:existing});continue}parseCapabilities(readFileSync29(source.path,"utf-8")),seenNames.set(stamped.name,source),loaded.push(stamped),outcomes.push({kind:"loaded",handler:stamped,source})}let builtins=getRegistry().filter((h)=>h.source==="builtin");return setRegistry([...builtins,...loaded]),outcomes}var init_loader=__esm(()=>{init_hooks();init_trust()});var exports_hook_socket={};__export(exports_hook_socket,{startHookSocket:()=>startHookSocket,defaultHookSocketPath:()=>defaultHookSocketPath});import{existsSync as existsSync48,unlinkSync as unlinkSync10}from"fs";import{mkdir as mkdir7}from"fs/promises";import{createServer,connect as netConnect}from"net";import{homedir as homedir39}from"os";import{dirname as dirname17,join as join60}from"path";function defaultHookSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join60(homedir39(),".genie");return join60(home,"hook.sock")}async function detectStaleAndCleanup(socketPath){if(!existsSync48(socketPath))return"clean";if(await new Promise((resolve11)=>{let probe=netConnect(socketPath),finish=(alive)=>{try{probe.destroy()}catch{}resolve11(alive)};probe.once("connect",()=>finish(!0)),probe.once("error",()=>finish(!1)),setTimeout(()=>finish(!1),200).unref()}))return"live";try{return unlinkSync10(socketPath),"stale-removed"}catch(err){throw Error(`Failed to remove stale hook socket at ${socketPath}: ${err.message}`)}}function parseFrame(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES2)return{kind:"error",reason:`frame length ${declared} exceeds MAX_FRAME_BYTES`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}function readOneFrame(socket){return new Promise((resolve11,reject)=>{let acc=Buffer.alloc(0),length=-1,cleanup=()=>{socket.off("data",onData),socket.off("end",onEnd),socket.off("error",onError)},onData=(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let result2=parseFrame(acc,length);if(result2.kind==="incomplete"){length=result2.length;return}if(cleanup(),result2.kind==="done")resolve11(result2.body);else reject(Error(result2.reason))},onEnd=()=>{cleanup(),reject(Error(`socket closed after ${acc.length} bytes (expected ${length===-1?"4+":4+length})`))},onError=(err)=>{cleanup(),reject(err)};socket.on("data",onData),socket.on("end",onEnd),socket.on("error",onError)})}function writeFrame(socket,payload){let body=Buffer.from(payload,"utf-8");if(body.length>MAX_FRAME_BYTES2){let empty=Buffer.alloc(4);socket.end(empty);return}let header=Buffer.alloc(4);header.writeUInt32BE(body.length,0),socket.end(Buffer.concat([header,body]))}async function handleConnection(socket){socket.setNoDelay(!0);try{let body=await readOneFrame(socket),stdin=body.length===0?"":body.toString("utf-8"),reply=await dispatch(stdin);writeFrame(socket,reply??"")}catch(err){try{writeFrame(socket,"")}catch{}if(process.env.GENIE_HOOK_SOCK_DEBUG==="1")console.warn(`[hook-socket] connection error: ${err.message}`)}}async function startHookSocket(options={}){if(process.env.GENIE_WIDE_EMIT===void 0)process.env.GENIE_WIDE_EMIT="1";let loaderOutcomes=[];if(options.loadExternal!==!1)loaderOutcomes=await loadExternalHooks({repoRoot:options.repoRoot,strict:options.strict}),summarizeLoaderOutcomes(loaderOutcomes);let socketPath=defaultHookSocketPath();await mkdir7(dirname17(socketPath),{recursive:!0});let state=await detectStaleAndCleanup(socketPath);if(state==="live")throw Error(`hook socket at ${socketPath} is already live \u2014 another genie serve daemon is running. Refusing to start.`);if(state==="stale-removed")console.log(`hook-socket: removed stale socket at ${socketPath}`);let liveSockets=new Set,server3=createServer((socket)=>{liveSockets.add(socket),socket.once("close",()=>liveSockets.delete(socket)),handleConnection(socket)});await new Promise((resolve11,reject)=>{let onError=(err)=>{server3.off("listening",onListening),reject(err)},onListening=()=>{server3.off("error",onError),resolve11()};server3.once("error",onError),server3.once("listening",onListening),server3.listen(socketPath)}),console.log(`hook-socket: listening at ${socketPath}`);let stopped=!1;return{path:socketPath,stop:async()=>{if(stopped)return;stopped=!0;for(let sock of liveSockets)try{sock.destroy()}catch{}if(liveSockets.clear(),await new Promise((resolve11)=>{server3.close(()=>resolve11())}),existsSync48(socketPath))try{unlinkSync10(socketPath)}catch{}},loaderOutcomes}}function summarizeLoaderOutcomes(outcomes){if(outcomes.length===0)return;let loaded=0,untrusted=0,broken=0,shadowed=0;for(let outcome of outcomes)if(outcome.kind==="loaded")loaded+=1;else if(outcome.kind==="untrusted")untrusted+=1;else if(outcome.kind==="broken")broken+=1;else shadowed+=1;let parts=[`hook-loader: ${loaded} loaded`];if(untrusted>0)parts.push(`${untrusted} untrusted`);if(broken>0)parts.push(`${broken} broken (quarantined)`);if(shadowed>0)parts.push(`${shadowed} shadowed`);console.log(parts.join(", "))}var MAX_FRAME_BYTES2=1048576;var init_hook_socket=__esm(()=>{init_hooks();init_loader()});var exports_serve={};__export(exports_serve,{writeStoppingLockSync:()=>writeStoppingLockSync,startBrainServerIfEnabled:()=>startBrainServerIfEnabled,registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isStoppingLockActive:()=>isStoppingLockActive,isServeRunning:()=>isServeRunning,getTuiQuitBindingArgs:()=>getTuiQuitBindingArgs,getTuiKeybindings:()=>getTuiKeybindings,ensureTuiSession:()=>ensureTuiSession,clearStoppingLock:()=>clearStoppingLock,autoStartServe:()=>autoStartServe});import{execSync as execSync11,spawn as spawn4,spawnSync as spawnSync6}from"child_process";import{closeSync as closeSync5,existsSync as existsSync49,mkdirSync as mkdirSync22,openSync as openSync5,readFileSync as readFileSync30,unlinkSync as unlinkSync11,writeFileSync as writeFileSync19,writeSync as writeSync2}from"fs";import{homedir as homedir40}from"os";import{dirname as dirname18,join as join61}from"path";function genieHome3(){return process.env.GENIE_HOME??join61(homedir40(),".genie")}function servePidPath(){return join61(genieHome3(),"serve.pid")}function serveStartupStatusPath(){return join61(genieHome3(),"state",`serve-startup-${process.pid}-${Date.now()}.json`)}function readServePid(){let path3=servePidPath();if(!existsSync49(path3))return null;let raw=readFileSync30(path3,"utf-8").trim();if(raw==="")return null;let sepIdx=raw.indexOf(":");if(sepIdx<0){let pid2=Number.parseInt(raw,10);if(Number.isNaN(pid2)||pid2<=0)return null;return{pid:pid2,startTime:null}}let pidPart=raw.slice(0,sepIdx),startTimePart=raw.slice(sepIdx+1).trim(),pid=Number.parseInt(pidPart,10);if(Number.isNaN(pid)||pid<=0)return null;return{pid,startTime:startTimePart===""||startTimePart==="unknown"?null:startTimePart}}function removeServePid(){let path3=servePidPath();if(!existsSync49(path3))return;try{let current=readServePid();if(current&&current.pid!==process.pid)return;unlinkSync11(path3)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function stoppingLockPath(){return join61(genieHome3(),"serve.stopping.lock")}function writeStoppingLockSync(ttlMs=STOPPING_LOCK_TTL_MS){mkdirSync22(genieHome3(),{recursive:!0}),writeFileSync19(stoppingLockPath(),String(Date.now()+ttlMs),"utf-8")}function clearStoppingLock(){try{unlinkSync11(stoppingLockPath())}catch{}}function isStoppingLockActive(){let path3=stoppingLockPath();if(!existsSync49(path3))return!1;let raw;try{raw=readFileSync30(path3,"utf-8").trim()}catch{return!1}let expiresAt=Number.parseInt(raw,10);if(raw===""||Number.isNaN(expiresAt)||expiresAt<=Date.now())return clearStoppingLock(),!1;return!0}function tuiTmuxConf(){return[join61(genieHome3(),"tui-tmux.conf")].find((p)=>existsSync49(p))??"/dev/null"}function tuiTmux(subcmd){return`${tmuxBin()} -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync11(genieTmuxCmd("list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function getTuiKeybindings(sessionName=TUI_SESSION){return[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${sessionName}:0.1" "select-pane -t ${sessionName}:0.0"`,`bind-key -T root C-1 select-pane -t ${sessionName}:0.0`,`bind-key -T root C-2 select-pane -t ${sessionName}:0.1`,`bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${sessionName}:0.0) -gt 5 ]" "resize-pane -t ${sessionName}:0.0 -x 0" "resize-pane -t ${sessionName}:0.0 -x ${NAV_WIDTH}"`,`bind-key -T root C-t select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-t`,"bind-key -T root C-d detach-client",`bind-key -T root C-q select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-q`]}function getTuiQuitBindingArgs(sessionName=TUI_SESSION){return["bind-key","-T","root","C-q","select-pane","-t",`${sessionName}:0.0`,"\\;","send-keys","-t",`${sessionName}:0.0`,"C-q"]}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}'`,...process.env.GENIE_TMUX_MOUSE!=="off"?[`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{execSync11(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){for(let cmd of getTuiKeybindings())try{if(cmd.startsWith("bind-key -T root C-q "))spawnSync6(tmuxBin(),["-L","genie-tui","-f",tuiTmuxConf(),...getTuiQuitBindingArgs()],{stdio:"ignore"});else execSync11(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync11(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync11(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
2530
+ `,null);if(rows)activeFromPg=rows.length,executorIds=rows.map((r)=>r.id)}let pgQueue=null;if(this.queue)pgQueue=await this.safePgCall("status_queue_stats",(_sql)=>this.queue?.stats()??Promise.resolve(null),null);return{connected:this.nc!==null,natsUrl:this.natsUrl,pgAvailable:this.pgAvailable,activeSessions:activeFromPg??this.sessions.size,maxConcurrent:this.maxConcurrent,idleTimeoutMs:this.idleTimeoutMs,queueDepth:pgQueue?pgQueue.pending+pgQueue.processing:this.messageQueue.length,executorType:this.executorType,executorIds,pgQueue,sessions:Array.from(this.sessions.entries()).map(([key,entry2])=>({id:key,agentName:entry2.session.agentName,chatId:entry2.session.chatId,instanceId:entry2.instanceId,executorType:entry2.session.executorType,spawning:entry2.spawning,idleMs:now-entry2.session.lastActivityAt,bufferSize:entry2.buffer.length}))}}async probePg(){try{let sql=await withTimeout2(this.pgProvider(),PG_STARTUP_PROBE_TIMEOUT_MS,"PG provider startup");await withTimeout2(Promise.resolve(sql`SELECT 1`),PG_STARTUP_PROBE_TIMEOUT_MS,"PG SELECT 1 probe"),this.sql=sql,this.pgAvailable=!0,console.log("[omni-bridge] PG reachable \u2014 session recovery enabled")}catch(err){this.sql=null,this.pgAvailable=!1;let msg=err instanceof Error?err.message:String(err);if(isPgConnectionError(err)){console.warn(`[omni-bridge] PG unavailable \u2014 session recovery disabled (${msg})`);return}throw Error(`[omni-bridge] PG schema mismatch or setup error: ${msg}. ${"Run `bun run migrate` (or the equivalent migration command) and retry."}`)}}async recoverSessions(){if(!this.sessionStore)return;let orphanedCount=await this.safePgCall("recover_orphan_all",(sql)=>new BridgeSessionStore(sql).markAllOrphaned(),0);if(orphanedCount>0)console.log(`[omni-bridge] Startup cleanup: orphaned ${orphanedCount} stale session(s) from previous run`)}async safePgCall(op,fn,fallback,ctx){if(!this.pgAvailable||!this.sql)return fallback;let sql=this.sql;try{return await withTimeout2(fn(sql),PG_RUNTIME_QUERY_TIMEOUT_MS,`safePgCall(${op})`)}catch(err){let msg=err instanceof Error?err.message:String(err),execPart=ctx?.executorId?` executor_id=${ctx.executorId}`:"",chatPart=ctx?.chatId?` chat_id=${ctx.chatId}`:"";if(console.warn(`[omni-bridge] safePgCall(${op}) failed${execPart}${chatPart}: ${msg}`),isPgConnectionError(err))this.pgAvailable=!1,this.sql=null,console.warn("[omni-bridge] PG connection lost \u2014 switching to degraded mode");return fallback}}fillSubjectMetadata(parsed,subject){let parts=subject.split(".");if(parts.length<4)return;parsed.instanceId=parsed.instanceId||parts[2],parsed.chatId=parsed.chatId||parts[3]}isDuplicateMessage(messageId){if(!messageId)return!1;if(this.recentMessageIds.has(messageId))return!0;if(this.recentMessageIds.set(messageId,Date.now()),this.recentMessageIds.size>1000){let cutoff=Date.now()-60000;for(let[id,ts3]of this.recentMessageIds)if(ts3<cutoff)this.recentMessageIds.delete(id)}return!1}async dispatchMessage(parsed){if(this.queue){let env=parsed.env??{};await this.queue.enqueue(parsed,env);return}let key=`${parsed.agent}:${parsed.chatId}`,hasSession=this.sessions.has(key);console.log(`[omni-bridge] Routing message for ${key} (hasSession=${hasSession}, queue=${!!this.queue})`),await this.routeMessage(parsed),console.log(`[omni-bridge] routeMessage done for ${key}`)}async processSubscription(){if(!this.sub)return;for await(let msg of this.sub)try{let data=this.sc.decode(msg.data),parsed=JSON.parse(data);this.fillSubjectMetadata(parsed,msg.subject),console.log(`[omni-bridge] NATS message received: ${msg.subject} agent=${parsed.agent} chat=${parsed.chatId}`);let messageId=parsed.messageId;if(this.isDuplicateMessage(messageId)){console.log(`[omni-bridge] Dedup: skipping duplicate messageId=${messageId}`);continue}if(!parsed.chatId||!parsed.agent){console.warn("[omni-bridge] Dropping message: missing chatId or agent",msg.subject);continue}await this.dispatchMessage(parsed)}catch(err){console.error("[omni-bridge] Error processing message:",err)}}async processTurnEvents(sub){for await(let msg of sub)try{let payload=JSON.parse(this.sc.decode(msg.data)),parts=msg.subject.split("."),eventType=parts[2],instanceId=parts[3],chatId=parts.slice(4).join(".");console.log(`[omni-bridge] Turn event: ${eventType} instance=${instanceId} chat=${chatId}`);let sessionKey2=this.findSessionKey(instanceId,chatId);if(!sessionKey2&&payload.turnId){if(sessionKey2=this.findSessionKeyByTurnId(payload.turnId),sessionKey2)console.log(`[omni-bridge] Matched session via turnId fallback: ${sessionKey2}`)}if(sessionKey2)await this.routeTurnEvent(eventType,sessionKey2,payload);else console.log(`[omni-bridge] No session found for turn.${eventType} (instance=${instanceId}, chat=${chatId})`)}catch(err){console.warn("[omni-bridge] Error processing turn event:",err)}}async routeTurnEvent(eventType,sessionKey2,payload){switch(eventType){case"open":this.turnTracker.open(sessionKey2,payload.turnId,payload.messageId),this.startHeartbeat(sessionKey2,payload);break;case"done":this.turnTracker.close(sessionKey2,payload.action),this.heartbeatPublisher?.stop(sessionKey2),await this.handleTurnDone(sessionKey2);break;case"nudge":await this.handleTurnNudge(sessionKey2,payload.message);break;case"timeout":this.heartbeatPublisher?.stop(sessionKey2),await this.handleTurnTimeout(sessionKey2);break}}startHeartbeat(sessionKey2,payload){let publisher=this.heartbeatPublisher;if(!publisher)return;let entry2=this.sessions.get(sessionKey2);if(!entry2)return;let turnId=payload.turnId;if(!turnId)return;publisher.start(sessionKey2,{instanceId:entry2.instanceId,chatId:entry2.session?.chatId??"",turnId,isBusy:async()=>{let live=this.sessions.get(sessionKey2);if(!live?.session)return!1;try{return await this.executor.isBusy(live.session)}catch{return!1}}})}findSessionKeyByTurnId(turnId){for(let[key]of this.sessions)if(this.turnTracker.getTurnId(key)===turnId)return key;return}chatIdMap=new Map;findSessionKey(instanceId,chatId){let resolvedChatId=this.chatIdMap.get(chatId);for(let[key,entry2]of this.sessions){if(entry2.instanceId!==instanceId)continue;if(entry2.session?.chatId===chatId)return key;if(resolvedChatId&&entry2.session?.chatId===resolvedChatId)return key;if(entry2.spawning&&key.endsWith(`:${chatId}`))return key;if(resolvedChatId&&entry2.spawning&&key.endsWith(`:${resolvedChatId}`))return key}return}async handleTurnNudge(sessionKey2,nudgeText){let entry2=this.sessions.get(sessionKey2);if(!entry2?.session)return;try{await this.executor.injectNudge(entry2.session,nudgeText)}catch(err){console.warn(`[omni-bridge] Failed to inject nudge for ${sessionKey2}:`,err)}}async processSessionResetEvents(sub){for await(let msg of sub)try{let parts=msg.subject.split(".");if(parts.length<5){console.warn(`[omni-bridge] Malformed session-reset subject: ${msg.subject}`);continue}let instanceId=parts[3],chatId=parts.slice(4).join("."),action;try{action=JSON.parse(this.sc.decode(msg.data)).action}catch{}await this.handleSessionReset(instanceId,chatId,action)}catch(err){console.warn("[omni-bridge] Error processing session reset event:",err)}}async handleSessionReset(instanceId,chatId,action){let sessionKey2=this.findSessionKey(instanceId,chatId);if(!sessionKey2){console.log(`[omni-bridge] Session reset for cold chat ${instanceId}/${chatId} \u2014 no-op`);return}let entry2=this.sessions.get(sessionKey2);if(!entry2)return;let actionTag=action?` (action=${action})`:"";if(entry2.spawning){console.log(`[omni-bridge] Session reset for spawning ${sessionKey2}${actionTag}, marking cancelled`),entry2.cancelled=!0,entry2.buffer=[],this.turnTracker.close(sessionKey2,"reset"),this.heartbeatPublisher?.stop(sessionKey2),await this.removeSession(sessionKey2),await this.drainQueue();return}if(!entry2.session)return;console.log(`[omni-bridge] Session reset for ${sessionKey2}${actionTag}, evicting`),this.turnTracker.close(sessionKey2,"reset"),this.heartbeatPublisher?.stop(sessionKey2);try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down reset session ${sessionKey2}:`,err)}await this.removeSession(sessionKey2),await this.drainQueue()}async handleTurnDone(sessionKey2){if(!this.sessions.get(sessionKey2)?.session)return;console.log(`[omni-bridge] Turn done for ${sessionKey2}, session stays alive for next message`),this.resetIdleTimer(sessionKey2)}async handleTurnTimeout(sessionKey2){let entry2=this.sessions.get(sessionKey2);if(!entry2?.session)return;console.warn(`[omni-bridge] Turn timed out for ${sessionKey2}, evicting session`),this.turnTracker.close(sessionKey2,"timeout"),this.heartbeatPublisher?.stop(sessionKey2);try{await this.executor.shutdown(entry2.session)}catch(err){console.warn(`[omni-bridge] Error shutting down timed-out session ${sessionKey2}:`,err)}this.sessions.delete(sessionKey2)}async routeMessage(message){let key=`${message.agent}:${message.chatId}`,entry2=this.sessions.get(key);if(entry2){if(entry2.spawning){if(entry2.buffer.length<MAX_BUFFER_PER_CHAT)entry2.buffer.push(message);else console.warn(`[omni-bridge] Buffer full (${MAX_BUFFER_PER_CHAT}) for ${key}, dropping message from ${message.sender}`),await this.publishBufferFullReply(message);return}if(await this.executor.isAlive(entry2.session)){await this.executor.deliver(entry2.session,message),this.resetIdleTimer(key);let bsId=entry2.pgBridgeSessionId;if(bsId&&this.sessionStore)this.safePgCall("session_activity",(sql)=>new BridgeSessionStore(sql).recordActivity(bsId),void 0,{chatId:message.chatId});return}await this.removeSession(key)}await this.spawnSession(message)}async spawnSession(message){let key=`${message.agent}:${message.chatId}`,existing=this.sessions.get(key);if(existing){if(existing.buffer.length<MAX_BUFFER_PER_CHAT)existing.buffer.push(message),console.log(`[omni-bridge] Buffered message for existing session ${key} (buffer=${existing.buffer.length})`);return}if(this.sessions.size>=this.maxConcurrent){this.messageQueue.push(message),await this.publishAutoReply(message),console.log(`[omni-bridge] Max concurrent (${this.maxConcurrent}) reached, queued message for ${key}`);return}let placeholder={session:null,instanceId:message.instanceId,spawning:!0,buffer:[message],idleTimer:null};this.sessions.set(key,placeholder);try{let raw=message,payloadEnv=raw.env,spawnEnv={OMNI_API_KEY:payloadEnv?.OMNI_API_KEY??process.env.OMNI_API_KEY??"",OMNI_INSTANCE:payloadEnv?.OMNI_INSTANCE??message.instanceId,OMNI_CHAT:payloadEnv?.OMNI_CHAT??message.chatId,OMNI_MESSAGE:payloadEnv?.OMNI_MESSAGE??raw.messageId??"",OMNI_TURN_ID:payloadEnv?.OMNI_TURN_ID||"",OMNI_SENDER_NAME:payloadEnv?.OMNI_SENDER_NAME??message.sender??""};console.log(`[omni-bridge] Spawning session for ${key}...`);let session=await this.executor.spawn(message.agent,message.chatId,spawnEnv,message.content);if(placeholder.cancelled){console.log(`[omni-bridge] Spawn for ${key} completed but was cancelled by reset, shutting down`);try{await this.executor.shutdown(session)}catch(err){console.warn(`[omni-bridge] Error shutting down cancelled spawn for ${key}:`,err)}return}if(placeholder.session=session,placeholder.spawning=!1,this.sessionStore){let claudeSessionId=session.sdk?.claudeSessionId??session.tmux?.claudeSessionId,pgId=await this.safePgCall("session_create",(sql)=>new BridgeSessionStore(sql).create({instanceId:message.instanceId,chatId:message.chatId,agentName:message.agent,executorId:session.sdk?.executorId,tmuxPaneId:session.tmux?.paneId,claudeSessionId}),void 0,{chatId:message.chatId});if(pgId)placeholder.pgBridgeSessionId=pgId}for(let buffered of placeholder.buffer)await this.executor.deliver(session,buffered);placeholder.buffer=[],this.resetIdleTimer(key);let sessionTag=session.executorType==="tmux"?`(tmux pane=${session.tmux?.paneId})`:"(executor=sdk)";console.log(`[omni-bridge] Session active: ${key} ${sessionTag}`)}catch(err){console.error(`[omni-bridge] Failed to spawn session for ${key}:`,err);let lostMessages=placeholder.buffer;if(lostMessages.length>0)console.warn(`[omni-bridge] Re-queuing ${lostMessages.length} buffered message(s) from failed spawn for ${key}`),this.messageQueue.push(...lostMessages);this.sessions.delete(key)}}resetIdleTimer(key){let entry2=this.sessions.get(key);if(!entry2)return;if(entry2.idleTimer)clearTimeout(entry2.idleTimer);entry2.idleTimer=setTimeout(async()=>{console.log(`[omni-bridge] Idle timeout for ${key}, shutting down...`);try{await this.executor.shutdown(entry2.session)}catch{}await this.removeSession(key),await this.drainQueue()},this.idleTimeoutMs)}async checkIdleSessions(){let now=Date.now();for(let[key,entry2]of this.sessions){if(entry2.spawning)continue;if(!await this.executor.isAlive(entry2.session)){console.log(`[omni-bridge] Dead session detected: ${key}`),await this.removeSession(key);continue}let idleMs=now-entry2.session.lastActivityAt;if(idleMs>this.idleTimeoutMs){console.log(`[omni-bridge] Forcing idle shutdown: ${key} (idle ${Math.round(idleMs/1000)}s)`);try{await this.executor.shutdown(entry2.session)}catch{}await this.removeSession(key)}}}async removeSession(key){let entry2=this.sessions.get(key);if(entry2?.idleTimer)clearTimeout(entry2.idleTimer);this.heartbeatPublisher?.stop(key);let closeId=entry2?.pgBridgeSessionId;if(closeId&&this.sessionStore)await this.safePgCall("session_close",(sql)=>new BridgeSessionStore(sql).close(closeId),void 0);this.sessions.delete(key)}async drainQueue(){while(this.messageQueue.length>0){if(this.sessions.size>=this.maxConcurrent)break;let message=this.messageQueue.shift();if(message)await this.spawnSession(message)}}async publishBufferFullReply(message){if(!this.nc)return;let topic=`omni.reply.${message.instanceId}.${message.chatId}`,reply={content:"Fila de mensagens cheia, por favor aguarde e tente novamente.",agent:message.agent,chat_id:message.chatId,instance_id:message.instanceId,timestamp:new Date().toISOString(),auto_reply:!0};this.nc.publish(topic,this.sc.encode(JSON.stringify(reply)))}async publishAutoReply(message){if(!this.nc)return;let topic=`omni.reply.${message.instanceId}.${message.chatId}`,reply={content:"Aguarde um momento, estou atendendo outros clientes.",agent:message.agent,chat_id:message.chatId,instance_id:message.instanceId,timestamp:new Date().toISOString(),auto_reply:!0};this.nc.publish(topic,this.sc.encode(JSON.stringify(reply)))}}var import_nats4,DEFAULT_NATS_URL2="localhost:4222",DEFAULT_IDLE_TIMEOUT_MS2=900000,DEFAULT_MAX_CONCURRENT=20,MAX_BUFFER_PER_CHAT=50,IDLE_CHECK_INTERVAL_MS=30000,PG_STARTUP_PROBE_TIMEOUT_MS=5000,PG_RUNTIME_QUERY_TIMEOUT_MS=2000;var init_omni_bridge=__esm(()=>{init_bridge_status();init_executor_config();init_agent_heartbeat();init_claude_code2();init_claude_sdk2();import_nats4=__toESM(require_mod4(),1)});import{existsSync as existsSync47,mkdirSync as mkdirSync21,readFileSync as readFileSync29,readdirSync as readdirSync10,renameSync as renameSync7,statSync as statSync12,writeFileSync as writeFileSync18}from"fs";import{homedir as homedir38}from"os";import{dirname as dirname16,join as join59,resolve as resolvePath4}from"path";import{pathToFileURL as pathToFileURL2}from"url";function scanDir(dirAbs){if(!existsSync47(dirAbs))return[];return readdirSync10(dirAbs).filter((name)=>name.endsWith(".ts")&&!name.startsWith("_")&&!name.endsWith(".test.ts")).map((name)=>join59(dirAbs,name)).filter((p)=>{try{return statSync12(p).isFile()}catch{return!1}})}function discoverHooks(opts){let home=homedir38(),discovered=[],teamsRoot=join59(home,".claude","teams");if(existsSync47(teamsRoot))for(let teamName of readdirSync10(teamsRoot)){if(teamName.startsWith("_"))continue;let teamHooksDir=join59(teamsRoot,teamName,"hooks");for(let path3 of scanDir(teamHooksDir))discovered.push({path:path3,scope:"team",teamName})}if(opts.repoRoot){let repoHooksDir=join59(opts.repoRoot,".genie","hooks"),repoRemoteUrl=opts.resolveRepoRemoteUrl?.(opts.repoRoot)??void 0;for(let path3 of scanDir(repoHooksDir))discovered.push({path:path3,scope:"repo",repoRemoteUrl})}let globalHooksDir=join59(home,".genie","hooks");for(let path3 of scanDir(globalHooksDir))discovered.push({path:path3,scope:"global"});return discovered}function quarantine(filePath,errorMessage){let dir=dirname16(filePath),quarantineDir=join59(dir,"_quarantine");if(!existsSync47(quarantineDir))mkdirSync21(quarantineDir,{recursive:!0});let basename11=filePath.split("/").pop()??"unknown.ts",target=join59(quarantineDir,basename11);try{renameSync7(filePath,target)}catch{}try{writeFileSync18(`${target}.error`,errorMessage,"utf-8")}catch{}return target}function validateHandler(imported){if(typeof imported!=="object"||imported===null)return"expected default export to be an object";let handler=imported;if(handler.version!=="1")return`unknown handler version: ${String(handler.version)} (expected '1')`;if(typeof handler.name!=="string"||handler.name.length===0)return"handler.name must be a non-empty string";if(typeof handler.event!=="string")return"handler.event must be a HookEventName string";if(typeof handler.priority!=="number"||Number.isNaN(handler.priority))return"handler.priority must be a finite number";if(typeof handler.fn!=="function")return"handler.fn must be a function";return null}async function loadExternalHooks(opts={}){let trustPath=opts.trustPath??defaultTrustPath(),trustFile;try{trustFile=readTrustFile(trustPath)}catch(err){let msg=err instanceof Error?err.message:String(err);return console.warn(`[hook-loader] trust file unreadable at ${trustPath}: ${msg} \u2014 refusing to load any external hooks`),[]}let importer=opts.importer??((url)=>import(url.href)),discovered=discoverHooks(opts),loaded=[],seenNames=new Map,outcomes=[];for(let source of discovered){let verify=verifyTrust(source.path,trustFile,{currentRepoRemoteUrl:source.repoRemoteUrl});if(!verify.trusted){outcomes.push({kind:"untrusted",reason:verify.reason,source});continue}let imported;try{imported=await importer(pathToFileURL2(resolvePath4(source.path)))}catch(err){let msg=err instanceof Error?`${err.name}: ${err.message}`:String(err),quarantined=quarantine(source.path,msg);outcomes.push({kind:"broken",error:msg,source,quarantined});continue}let handlerCandidate=imported.default,validationError=validateHandler(handlerCandidate);if(validationError!==null){let quarantined=quarantine(source.path,validationError);outcomes.push({kind:"broken",error:validationError,source,quarantined});continue}let stamped={...handlerCandidate,source:source.scope,manifest_path:source.path},existing=seenNames.get(stamped.name);if(existing){if(console.warn(`[hook-loader] handler name collision: "${stamped.name}" registered from ${existing.path} (${existing.scope}) shadows ${source.path} (${source.scope})`),opts.strict)throw Error(`--strict-hooks: handler name collision on "${stamped.name}" between ${existing.path} and ${source.path}`);outcomes.push({kind:"shadowed",handler:stamped,source,shadowedBy:existing});continue}parseCapabilities(readFileSync29(source.path,"utf-8")),seenNames.set(stamped.name,source),loaded.push(stamped),outcomes.push({kind:"loaded",handler:stamped,source})}let builtins=getRegistry().filter((h)=>h.source==="builtin");return setRegistry([...builtins,...loaded]),outcomes}var init_loader=__esm(()=>{init_hooks();init_trust()});var exports_hook_socket={};__export(exports_hook_socket,{startHookSocket:()=>startHookSocket,defaultHookSocketPath:()=>defaultHookSocketPath});import{existsSync as existsSync48,unlinkSync as unlinkSync10}from"fs";import{mkdir as mkdir7}from"fs/promises";import{createServer,connect as netConnect}from"net";import{homedir as homedir39}from"os";import{dirname as dirname17,join as join60}from"path";function defaultHookSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join60(homedir39(),".genie");return join60(home,"hook.sock")}async function detectStaleAndCleanup(socketPath){if(!existsSync48(socketPath))return"clean";if(await new Promise((resolve11)=>{let probe=netConnect(socketPath),finish=(alive)=>{try{probe.destroy()}catch{}resolve11(alive)};probe.once("connect",()=>finish(!0)),probe.once("error",()=>finish(!1)),setTimeout(()=>finish(!1),200).unref()}))return"live";try{return unlinkSync10(socketPath),"stale-removed"}catch(err){throw Error(`Failed to remove stale hook socket at ${socketPath}: ${err.message}`)}}function parseFrame(acc,length){if(length===-1){if(acc.length<4)return{kind:"incomplete",length:-1};let declared=acc.readUInt32BE(0);if(declared>MAX_FRAME_BYTES2)return{kind:"error",reason:`frame length ${declared} exceeds MAX_FRAME_BYTES`};if(declared===0)return{kind:"done",body:Buffer.alloc(0)};if(acc.length>=4+declared)return{kind:"done",body:acc.subarray(4,4+declared)};return{kind:"incomplete",length:declared}}if(acc.length>=4+length)return{kind:"done",body:acc.subarray(4,4+length)};return{kind:"incomplete",length}}function readOneFrame(socket){return new Promise((resolve11,reject)=>{let acc=Buffer.alloc(0),length=-1,cleanup=()=>{socket.off("data",onData),socket.off("end",onEnd),socket.off("error",onError)},onData=(chunk)=>{acc=acc.length===0?Buffer.from(chunk):Buffer.concat([acc,Buffer.from(chunk)],acc.length+chunk.length);let result2=parseFrame(acc,length);if(result2.kind==="incomplete"){length=result2.length;return}if(cleanup(),result2.kind==="done")resolve11(result2.body);else reject(Error(result2.reason))},onEnd=()=>{cleanup(),reject(Error(`socket closed after ${acc.length} bytes (expected ${length===-1?"4+":4+length})`))},onError=(err)=>{cleanup(),reject(err)};socket.on("data",onData),socket.on("end",onEnd),socket.on("error",onError)})}function writeFrame(socket,payload){let body=Buffer.from(payload,"utf-8");if(body.length>MAX_FRAME_BYTES2){let empty=Buffer.alloc(4);socket.end(empty);return}let header=Buffer.alloc(4);header.writeUInt32BE(body.length,0),socket.end(Buffer.concat([header,body]))}async function handleConnection(socket){socket.setNoDelay(!0);try{let body=await readOneFrame(socket),stdin=body.length===0?"":body.toString("utf-8"),reply=await dispatch(stdin);writeFrame(socket,reply??"")}catch(err){try{writeFrame(socket,"")}catch{}if(process.env.GENIE_HOOK_SOCK_DEBUG==="1")console.warn(`[hook-socket] connection error: ${err.message}`)}}async function startHookSocket(options={}){if(process.env.GENIE_WIDE_EMIT===void 0)process.env.GENIE_WIDE_EMIT="1";let loaderOutcomes=[];if(options.loadExternal!==!1)loaderOutcomes=await loadExternalHooks({repoRoot:options.repoRoot,strict:options.strict}),summarizeLoaderOutcomes(loaderOutcomes);let socketPath=defaultHookSocketPath();await mkdir7(dirname17(socketPath),{recursive:!0});let state=await detectStaleAndCleanup(socketPath);if(state==="live")throw Error(`hook socket at ${socketPath} is already live \u2014 another genie serve daemon is running. Refusing to start.`);if(state==="stale-removed")console.log(`hook-socket: removed stale socket at ${socketPath}`);let liveSockets=new Set,server3=createServer((socket)=>{liveSockets.add(socket),socket.once("close",()=>liveSockets.delete(socket)),handleConnection(socket)});await new Promise((resolve11,reject)=>{let onError=(err)=>{server3.off("listening",onListening),reject(err)},onListening=()=>{server3.off("error",onError),resolve11()};server3.once("error",onError),server3.once("listening",onListening),server3.listen(socketPath)}),console.log(`hook-socket: listening at ${socketPath}`);let stopped=!1;return{path:socketPath,stop:async()=>{if(stopped)return;stopped=!0;for(let sock of liveSockets)try{sock.destroy()}catch{}if(liveSockets.clear(),await new Promise((resolve11)=>{server3.close(()=>resolve11())}),existsSync48(socketPath))try{unlinkSync10(socketPath)}catch{}},loaderOutcomes}}function summarizeLoaderOutcomes(outcomes){if(outcomes.length===0)return;let loaded=0,untrusted=0,broken=0,shadowed=0;for(let outcome of outcomes)if(outcome.kind==="loaded")loaded+=1;else if(outcome.kind==="untrusted")untrusted+=1;else if(outcome.kind==="broken")broken+=1;else shadowed+=1;let parts=[`hook-loader: ${loaded} loaded`];if(untrusted>0)parts.push(`${untrusted} untrusted`);if(broken>0)parts.push(`${broken} broken (quarantined)`);if(shadowed>0)parts.push(`${shadowed} shadowed`);console.log(parts.join(", "))}var MAX_FRAME_BYTES2=1048576;var init_hook_socket=__esm(()=>{init_hooks();init_loader()});var exports_serve={};__export(exports_serve,{writeStoppingLockSync:()=>writeStoppingLockSync,startBrainServerIfEnabled:()=>startBrainServerIfEnabled,registerServeCommands:()=>registerServeCommands,isTuiSessionReady:()=>isTuiSessionReady,isStoppingLockActive:()=>isStoppingLockActive,isServeRunning:()=>isServeRunning,getTuiQuitBindingArgs:()=>getTuiQuitBindingArgs,getTuiKeybindings:()=>getTuiKeybindings,ensureTuiSession:()=>ensureTuiSession,clearStoppingLock:()=>clearStoppingLock,autoStartServe:()=>autoStartServe});import{execSync as execSync11,spawn as spawn4,spawnSync as spawnSync6}from"child_process";import{closeSync as closeSync5,existsSync as existsSync49,mkdirSync as mkdirSync22,openSync as openSync5,readFileSync as readFileSync30,unlinkSync as unlinkSync11,writeFileSync as writeFileSync19,writeSync as writeSync2}from"fs";import{homedir as homedir40}from"os";import{dirname as dirname18,join as join61}from"path";function genieHome3(){return process.env.GENIE_HOME??join61(homedir40(),".genie")}function servePidPath(){return join61(genieHome3(),"serve.pid")}function serveStartupStatusPath(){return join61(genieHome3(),"state",`serve-startup-${process.pid}-${Date.now()}.json`)}function readServePid(){let path3=servePidPath();if(!existsSync49(path3))return null;let raw=readFileSync30(path3,"utf-8").trim();if(raw==="")return null;let sepIdx=raw.indexOf(":");if(sepIdx<0){let pid2=Number.parseInt(raw,10);if(Number.isNaN(pid2)||pid2<=0)return null;return{pid:pid2,startTime:null}}let pidPart=raw.slice(0,sepIdx),startTimePart=raw.slice(sepIdx+1).trim(),pid=Number.parseInt(pidPart,10);if(Number.isNaN(pid)||pid<=0)return null;return{pid,startTime:startTimePart===""||startTimePart==="unknown"?null:startTimePart}}function removeServePid(){let path3=servePidPath();if(!existsSync49(path3))return;try{let current=readServePid();if(current&&current.pid!==process.pid)return;unlinkSync11(path3)}catch{}}function isProcessAlive(pid){try{return process.kill(pid,0),!0}catch{return!1}}function stoppingLockPath(){return join61(genieHome3(),"serve.stopping.lock")}function writeStoppingLockSync(ttlMs=STOPPING_LOCK_TTL_MS){mkdirSync22(genieHome3(),{recursive:!0}),writeFileSync19(stoppingLockPath(),String(Date.now()+ttlMs),"utf-8")}function clearStoppingLock(){try{unlinkSync11(stoppingLockPath())}catch{}}function isStoppingLockActive(){let path3=stoppingLockPath();if(!existsSync49(path3))return!1;let raw;try{raw=readFileSync30(path3,"utf-8").trim()}catch{return!1}let expiresAt=Number.parseInt(raw,10);if(raw===""||Number.isNaN(expiresAt)||expiresAt<=Date.now())return clearStoppingLock(),!1;return!0}function tuiTmuxConf(){return[join61(genieHome3(),"tui-tmux.conf")].find((p)=>existsSync49(p))??"/dev/null"}function tuiTmux(subcmd){return`${tmuxBin()} -L genie-tui -f ${tuiTmuxConf()} ${subcmd}`}function isGenieTmuxRunning(){try{return execSync11(genieTmuxCmd("list-sessions"),{stdio:"ignore"}),!0}catch{return!1}}function getTuiKeybindings(sessionName=TUI_SESSION){return[`bind-key -T root Tab if-shell "[ '#{pane_index}' = '0' ]" "select-pane -t ${sessionName}:0.1" "select-pane -t ${sessionName}:0.0"`,`bind-key -T root C-1 select-pane -t ${sessionName}:0.0`,`bind-key -T root C-2 select-pane -t ${sessionName}:0.1`,`bind-key -T root C-b if-shell "[ $(tmux display-message -p '#\\{pane_width\\}' -t ${sessionName}:0.0) -gt 5 ]" "resize-pane -t ${sessionName}:0.0 -x 0" "resize-pane -t ${sessionName}:0.0 -x ${NAV_WIDTH}"`,`bind-key -T root C-t select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-t`,"bind-key -T root C-d detach-client",`bind-key -T root C-q select-pane -t ${sessionName}:0.0 \\; send-keys -t ${sessionName}:0.0 C-q`]}function getTuiQuitBindingArgs(sessionName=TUI_SESSION){return["bind-key","-T","root","C-q","select-pane","-t",`${sessionName}:0.0`,"\\;","send-keys","-t",`${sessionName}:0.0`,"C-q"]}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}'`,...process.env.GENIE_TMUX_MOUSE!=="off"?[`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{execSync11(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function setupTuiKeybindings(){for(let cmd of getTuiKeybindings())try{if(cmd.startsWith("bind-key -T root C-q "))spawnSync6(tmuxBin(),["-L","genie-tui","-f",tuiTmuxConf(),...getTuiQuitBindingArgs()],{stdio:"ignore"});else execSync11(tuiTmux(cmd),{stdio:"ignore"})}catch{}}function startTuiTmuxServer(){try{execSync11(tuiTmux(`has-session -t ${TUI_SESSION}`),{stdio:"ignore"});let panes2=execSync11(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
2531
2531
  `);if(panes2.length>=2){try{execSync11(tuiTmux(`respawn-pane -k -t ${panes2[1]} 'cat'`),{stdio:"ignore"})}catch{}return{leftPane:panes2[0],rightPane:panes2[1]}}let cols2=Number.parseInt(execSync11(tuiTmux(`display-message -t ${TUI_SESSION}:0 -p '#{window_width}'`),{encoding:"utf-8"}).trim(),10)||120;execSync11(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols2-NAV_WIDTH-1}`),{stdio:"ignore"});let refreshed=execSync11(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
2532
2532
  `);applyTuiStyle(),setupTuiKeybindings();try{execSync11(tuiTmux(`select-pane -t ${refreshed[0]}`),{stdio:"ignore"})}catch{}return{leftPane:refreshed[0],rightPane:refreshed[1]||refreshed[0]}}catch{}let cols=120;execSync11(tuiTmux(`new-session -d -s ${TUI_SESSION} -x ${cols} -y ${40}`),{stdio:"ignore"}),execSync11(tuiTmux(`split-window -h -t ${TUI_SESSION}:0 -l ${cols-NAV_WIDTH-1}`),{stdio:"ignore"});let panes=execSync11(tuiTmux(`list-panes -t ${TUI_SESSION}:0 -F '#{pane_id}'`),{encoding:"utf-8"}).trim().split(`
2533
2533
  `);applyTuiStyle(),setupTuiKeybindings();try{execSync11(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=genieHome3(),bunPath=process.execPath||"bun",genieBin=process.argv[1]||"genie",scriptPath=join61(home,"tui-launch.sh"),logsDir=join61(home,"logs"),crashLog=join61(logsDir,"tui-crash.log"),envVars=["GENIE_TUI_PANE=left",`GENIE_TUI_RIGHT=${rightPane}`];if(workspaceRoot)envVars.push(`GENIE_TUI_WORKSPACE=${workspaceRoot}`);let content=["#!/bin/sh",`mkdir -p '${logsDir}'`,`exec 2>> '${crashLog}'`,`printf -- '--- tui-launch %s pid=%s ---\\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$$" >&2`,`export ${envVars.join(`
@@ -2540,8 +2540,8 @@ genie serve is running (${mode}). ${headless?"Send SIGTERM to stop.":"Press Ctrl
2540
2540
  `))}function printHookSocketStatus(){let sock=hookSocketPath();if(existsSync49(sock))console.log(` hook UDS: listening at ${sock}`);else console.log(` hook UDS: MISSING at ${sock} (F1 fallback active \u2014 see #1490)`)}function resolveBrainPortFromWorkspace(brain){try{let{findWorkspace:findWorkspace2}=(init_workspace(),__toCommonJS(exports_workspace)),ws=findWorkspace2();if(ws?.root&&brain.readServerInfo){let info=brain.readServerInfo(join61(ws.root,"brain"));if(info?.port)return info.port}}catch{}return null}async function probeBrainHealth(brainPort){try{let resp=await fetch(`http://127.0.0.1:${brainPort}/healthz`);console.log(resp.ok?` brain: running (port ${brainPort})`:` brain: unhealthy (port ${brainPort}, status ${resp.status})`)}catch{console.log(` brain: stopped (port ${brainPort} unreachable)`)}}async function printBrainStatus(){try{let brain=await import("@khal-os/brain");if(handles.brainHandles.length>0){for(let handle of handles.brainHandles)await probeBrainHealth(handle.port);return}let brainPort=resolveBrainPortFromWorkspace(brain);if(brainPort)await probeBrainHealth(brainPort);else console.log(" brain: stopped")}catch{console.log(" brain: not installed")}}async function printPgserveStatus(){await printPgserveHealth(),printHookSocketStatus(),await printBrainStatus()}function printTmuxStatus(){let agentRunning=isGenieTmuxRunning(),sessions=agentRunning?listAgentSessions():[];if(console.log(` tmux -L genie: ${agentRunning?`running (${sessions.length} sessions)`:"stopped"}`),sessions.length>0)console.log(` ${sessions.join(", ")}`);let tuiReady=isTuiSessionReady();console.log(` tmux -L genie-tui: ${tuiReady?"running":"stopped"}`)}async function printDaemonStatus(serveRunning){try{let schedulerPidPath=join61(genieHome3(),"scheduler.pid");if(existsSync49(schedulerPidPath)){let sPid=Number.parseInt(readFileSync30(schedulerPidPath,"utf-8").trim(),10),sAlive=!Number.isNaN(sPid)&&isProcessAlive(sPid);console.log(` scheduler: ${sAlive?`running (PID ${sPid})`:"stopped"}`)}else if(serveRunning)console.log(" scheduler: integrated (in-process)");else console.log(" scheduler: stopped")}catch{console.log(" scheduler: unknown")}try{let{getInboxPollIntervalMs:getInboxPollIntervalMs2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log(" inbox: disabled");else console.log(` inbox: ${serveRunning?"watching":"stopped"} (poll ${pollMs/1000}s)`)}catch{console.log(" inbox: unavailable")}}async function printBridgeStatus(){try{let{getBridgeStatus:getBridgeStatus2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2();if(res.state==="running"&&res.pong){let uptimeSec=Math.round(res.pong.uptimeMs/1000),latency=res.latencyMs??0;console.log(` omni-bridge: running (pid ${res.pong.pid}, uptime ${uptimeSec}s, ping ${latency}ms)`)}else if(res.state==="stale")console.log(` omni-bridge: stale \u2014 ${res.detail}`);else console.log(" omni-bridge: stopped")}catch{console.log(" omni-bridge: unavailable")}}async function statusServe(){let entry2=readServePid(),running2=entry2!==null&&isProcessAlive(entry2.pid);if(console.log(`
2541
2541
  Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${running2?"running":"stopped"}`),running2&&entry2)console.log(` PID: ${entry2.pid}`);await printPgserveStatus(),printTmuxStatus(),await printDaemonStatus(running2),await printBridgeStatus(),console.log(` PID file: ${servePidPath()}`),console.log("")}async function runStartPreconditions(autoFix,log2){if(process.env.GENIE_SKIP_PRECONDITIONS==="1")return!0;let{ensureServeReady:ensureServeReady2}=await Promise.resolve().then(() => (init_ensure_ready(),exports_ensure_ready)),report=await ensureServeReady2({autoFix,deps:log2?{log:log2}:void 0});if(autoFix)return!0;if(!report.ok){if(log2)log2("genie serve start refused: one or more preconditions are not ok (--no-fix mode).");else console.error("genie serve start refused: one or more preconditions are not ok (--no-fix mode).");return!1}return!0}function registerServeCommands(program2){let serve=program2.command("serve").description("Start all genie infrastructure (pgserve, tmux, scheduler)");serve.command("start",{isDefault:!0}).description("Start genie serve").option("--daemon","Run in background").option("--foreground","Run in foreground (default)").option("--headless","Run without TUI (services only: pgserve, scheduler, inbox-watcher)").option("--no-fix","Refuse to start when any precondition is not ok (default: auto-fix)").option("--strict-hooks","Refuse to start on any same-name external hook collision (default: warn + continue)").action(async(options)=>{let autoFix=options.fix!==!1;if(options.strictHooks)process.env.GENIE_STRICT_HOOKS="1";if(options.daemon)await startBackground(options.headless,autoFix);else await startForeground(options.headless,autoFix)}),serve.command("stop").description("Stop genie serve and all services").action(async()=>{await stopServe()}),serve.command("status").description("Show service health").action(async()=>{await statusServe()})}var STOPPING_LOCK_TTL_MS=30000,TUI_SESSION="genie-tui",NAV_WIDTH=30,TUI_STYLE,handles,operatorCwd;var init_serve=__esm(()=>{init_genie_tokens();init_brain_vaults();init_ensure_tmux();init_process_identity();init_tmux_wrapper();init_tui_disable();TUI_STYLE={activeBorder:palette.borderActive,inactiveBorder:palette.border};handles={schedulerHandle:null,agentWatcher:null,brainHandles:[],omniApprovalHandler:null,omniBridge:null,detectorScheduler:null,derivedSignals:null,hookSocket:null};operatorCwd=process.cwd()});var exports_tmux2={};__export(exports_tmux2,{newAgentWindow:()=>newAgentWindow,hasProjectSession:()=>hasProjectSession,attachTuiSession:()=>attachTuiSession,attachProjectWindow:()=>attachProjectWindow});import{spawnSync as spawnSync7}from"child_process";function runTuiTmux(args,stdio="ignore"){return spawnSync7(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{stdio})}function runTuiTmuxOutput(args){let result2=spawnSync7(TMUX_BIN,["-L",TMUX_SOCKET,"-f",TUI_TMUX_CONF,...args],{encoding:"utf-8"});return result2.status===0?result2.stdout.trim():null}function runAgentTmux(args,stdio="ignore"){return spawnSync7(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,...args],{stdio})}function shellQuote3(value){return`'${value.replace(/'/g,"'\\''")}'`}function buildAttachLoop(targetSession){return`while true; do TMUX='' ${[TMUX_BIN,"-L",GENIE_AGENT_SOCKET,"attach-session","-t",targetSession].map(shellQuote3).join(" ")} 2>/dev/null; sleep 0.3; done`}function resolveRightPane(rightPane){if(runTuiTmux(["display-message","-t",rightPane,"-p",""]).status===0)return rightPane;let panes=runTuiTmuxOutput(["list-panes","-t",`${SESSION_NAME}:0`,"-F","#{pane_id}"])?.split(`
2542
2542
  `)??[];return panes[1]||panes[0]||rightPane}function hasProjectSession(targetSession){return runAgentTmux(["has-session","-t",targetSession]).status===0}function attachProjectWindow(rightPane,targetSession,windowIndex){if(targetSession===SESSION_NAME)return;let pane=resolveRightPane(rightPane);if(!hasProjectSession(targetSession))return;if(windowIndex!==void 0)runAgentTmux(["select-window","-t",`${targetSession}:${windowIndex}`]);runAgentTmux(["set-option","-t",targetSession,"status","off"]),runTuiTmux(["respawn-pane","-k","-t",pane,"sh","-lc",buildAttachLoop(targetSession)]),runTuiTmux(["select-pane","-t",`${SESSION_NAME}:0.0`])}function attachTuiSession(){if(runTuiTmux(["select-pane","-t",`${SESSION_NAME}:0.0`]),process.env.TMUX)runTuiTmux(["switch-client","-t",SESSION_NAME],"inherit");else runTuiTmux(["attach-session","-t",SESSION_NAME],"inherit")}function nextRoleSuffix(baseName){let sessionName=baseName.replace(/\//g,"-"),output=spawnSync7(TMUX_BIN,["-L",GENIE_AGENT_SOCKET,"list-windows","-t",`=${sessionName}`,"-F","#{window_name}"],{encoding:"utf-8"}),names=output.status===0?output.stdout.trim().split(`
2543
- `):[],max=names.length+1,re=new RegExp(`^${baseName}-(\\d+)$`);for(let n of names){let m=n.match(re);if(m)max=Math.max(max,Number.parseInt(m[1],10)+1)}return max}function newAgentWindow(agentName){(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}let{spawn:spawn5}=__require("child_process"),{join:join62,resolve:resolve11}=__require("path"),{existsSync:existsSync50}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let parentName=agentName.includes("/")?agentName.slice(0,agentName.indexOf("/")):agentName,agentDir=resolve11(join62(wsRoot,"agents",parentName));if(existsSync50(agentDir))cwd=agentDir}let suffix=nextRoleSuffix(agentName),role=`${agentName}-${suffix}`,sessionName=agentName.replace(/\//g,"-"),args=["spawn",agentName,"--role",role,"--session",sessionName,"--new-window"];(genieBin&&genieBin!=="genie"?spawn5(bunPath,[genieBin,...args],{detached:!0,stdio:"ignore",cwd}):spawn5("genie",args,{detached:!0,stdio:"ignore",cwd})).unref()})()}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX_BIN;var init_tmux2=__esm(()=>{init_ensure_tmux();TUI_TMUX_CONF=(()=>{let{existsSync:existsSync50}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync50(tuiConf)?tuiConf:"/dev/null"})(),TMUX_BIN=tmuxBin()});function s(orgId,path3){return`khal.${orgId}.genie.${path3}`}var GENIE_SUBJECTS;var init_subjects=__esm(()=>{GENIE_SUBJECTS={dashboard:{stats:(orgId)=>s(orgId,"dashboard.stats")},agents:{list:(orgId)=>s(orgId,"agents.list"),show:(orgId)=>s(orgId,"agents.show")},sessions:{list:(orgId)=>s(orgId,"sessions.list"),content:(orgId)=>s(orgId,"sessions.content"),search:(orgId)=>s(orgId,"sessions.search")},tasks:{list:(orgId)=>s(orgId,"tasks.list"),show:(orgId)=>s(orgId,"tasks.show")},boards:{list:(orgId)=>s(orgId,"boards.list"),show:(orgId)=>s(orgId,"boards.show")},costs:{summary:(orgId)=>s(orgId,"costs.summary"),sessions:(orgId)=>s(orgId,"costs.sessions"),tokens:(orgId)=>s(orgId,"costs.tokens"),efficiency:(orgId)=>s(orgId,"costs.efficiency")},schedules:{list:(orgId)=>s(orgId,"schedules.list"),history:(orgId)=>s(orgId,"schedules.history")},system:{health:(orgId)=>s(orgId,"system.health"),snapshots:(orgId)=>s(orgId,"system.snapshots"),tables:(orgId)=>s(orgId,"system.tables"),channels:(orgId)=>s(orgId,"system.channels")},settings:{get:(orgId)=>s(orgId,"settings.get"),set:(orgId)=>s(orgId,"settings.set"),templates:(orgId)=>s(orgId,"settings.templates"),templateSave:(orgId)=>s(orgId,"settings.templates.save"),skills:(orgId)=>s(orgId,"settings.skills"),rules:(orgId)=>s(orgId,"settings.rules"),workspace:(orgId)=>s(orgId,"settings.workspace"),testPg:(orgId)=>s(orgId,"settings.test_pg")},pty:{create:(orgId)=>s(orgId,"pty.create"),input:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.input`),data:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.data`),resize:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.resize`),kill:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.kill`)},fs:{list:(orgId)=>s(orgId,"fs.list"),read:(orgId)=>s(orgId,"fs.read"),write:(orgId)=>s(orgId,"fs.write")},approval:{resolve:(orgId)=>s(orgId,"approval.resolve"),list:(orgId)=>s(orgId,"approval.list")},events:{agentState:(orgId)=>s(orgId,"events.agent_state"),executorState:(orgId)=>s(orgId,"events.executor_state"),taskStage:(orgId)=>s(orgId,"events.task_stage"),runtime:(orgId)=>s(orgId,"events.runtime"),audit:(orgId)=>s(orgId,"events.audit"),message:(orgId)=>s(orgId,"events.message"),mailbox:(orgId)=>s(orgId,"events.mailbox"),taskDep:(orgId)=>s(orgId,"events.task_dep"),trigger:(orgId)=>s(orgId,"events.trigger"),approvalRequest:(orgId)=>s(orgId,"events.approval_request"),approvalResolved:(orgId)=>s(orgId,"events.approval_resolved")}}});function emit2(event){for(let handler of listeners)handler(event)}async function startListening(nats,orgId){if(listenersActive)return;if(listenersActive=!0,nats)natsConn=nats,natsOrgId=orgId??"default";let sql=await getConnection();for(let mapping of CHANNEL_MAP){let listener=await sql.listen(mapping.channel,(payload)=>{let parsed;try{parsed=JSON.parse(payload)}catch{parsed={raw:payload}}if(emit2({type:mapping.eventType,payload:parsed}),natsConn){let subject=mapping.natsSubject(natsOrgId);natsConn.publish(subject,sc.encode(JSON.stringify(parsed)))}});stopFns.push(()=>listener.unlisten())}}async function stopListening(){listenersActive=!1,natsConn=null,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var import_nats4,listeners,natsConn=null,natsOrgId="default",sc,CHANNEL_MAP,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();init_subjects();import_nats4=__toESM(require_mod4(),1),listeners=new Set;sc=import_nats4.StringCodec(),CHANNEL_MAP=[{channel:"genie_agent_state",eventType:"agent-state-changed",natsSubject:GENIE_SUBJECTS.events.agentState},{channel:"genie_executor_state",eventType:"executor-state-changed",natsSubject:GENIE_SUBJECTS.events.executorState},{channel:"genie_task_stage",eventType:"task-stage-changed",natsSubject:GENIE_SUBJECTS.events.taskStage},{channel:"genie_runtime_event",eventType:"runtime-event",natsSubject:GENIE_SUBJECTS.events.runtime},{channel:"genie_audit_event",eventType:"audit-event",natsSubject:GENIE_SUBJECTS.events.audit},{channel:"genie_message",eventType:"message",natsSubject:GENIE_SUBJECTS.events.message},{channel:"genie_mailbox_delivery",eventType:"mailbox-delivery",natsSubject:GENIE_SUBJECTS.events.mailbox},{channel:"genie_task_dep",eventType:"task-dep-changed",natsSubject:GENIE_SUBJECTS.events.taskDep},{channel:"genie_trigger_due",eventType:"trigger-due",natsSubject:GENIE_SUBJECTS.events.trigger},{channel:"genie_approval_request",eventType:"approval-request",natsSubject:GENIE_SUBJECTS.events.approvalRequest},{channel:"genie_approval_resolved",eventType:"approval-resolved",natsSubject:GENIE_SUBJECTS.events.approvalResolved}],stopFns=[]});import{randomUUID as randomUUID10}from"crypto";function onPtyData(cb){dataCallback=cb}function onPtyExit(cb){exitCallback=cb}async function spawnForAgent(agentName,opts={}){let{buildClaudeCommand:buildClaudeCommand3}=await Promise.resolve().then(() => (init_provider_adapters(),exports_provider_adapters)),{createExecutor:createExecutor2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),{findOrCreateAgent:findOrCreateAgent2,setCurrentExecutor:setCurrentExecutor2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),cols=opts.cols??120,rows=opts.rows??40,cwd=opts.cwd??process.cwd(),launch=buildClaudeCommand3({provider:"claude",team:"app",role:"engineer",name:agentName}),agent=await findOrCreateAgent2(agentName,"app","engineer"),executor=await createExecutor2(agent.id,"app-pty","process",{repoPath:cwd,state:"spawning",metadata:{command:launch.command,source:"genie-app"}});await setCurrentExecutor2(agent.id,executor.id);let env={...process.env,...launch.env,GENIE_APP_PTY:"true"},session=spawnPty(launch.command,{cwd,cols,rows,env,agentId:agent.id,executorId:executor.id,taskId:opts.taskId??null}),{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await updateExecutorState2(executor.id,"running"),session}function spawnBash(cwd){let shell=process.env.SHELL??"/bin/bash";return spawnPty(shell,{cwd:cwd??process.cwd(),cols:120,rows:40,env:process.env,agentId:null,executorId:null,taskId:null})}function writeTerminal(sessionId,data){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.write(data),!0}function resizeTerminal(sessionId,cols,rows){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.resize(cols,rows),session.cols=cols,session.rows=rows,!0}async function killTerminal(sessionId){let session=sessions.get(sessionId);if(!session)return!1;if(session.pty.kill(),session.state="exited",session.executorId)try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,"terminated")}catch{}return sessions.delete(sessionId),!0}async function killAll(){let ids=[...sessions.keys()];await Promise.allSettled(ids.map((id)=>killTerminal(id)))}async function pipeStdout(stdout,sessionId,ptyHandle){let reader=stdout.getReader(),decoder=new TextDecoder;try{while(!0){let{done,value}=await reader.read();if(done)break;let text=decoder.decode(value);if(dataCallback)dataCallback(sessionId,text);if(ptyHandle.onData)ptyHandle.onData(text)}}catch{}}function onProcExit(sessionId,code,ptyHandle){let session=sessions.get(sessionId);if(session)session.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}function spawnPty(command,opts){let sessionId=randomUUID10(),ptyHandle;try{let bunPty=(()=>{throw new Error("Cannot require module "+"bun-pty");})(),parts=command.split(" "),raw=bunPty.spawn(parts,{cwd:opts.cwd,env:opts.env,cols:opts.cols,rows:opts.rows});ptyHandle={write:(data)=>raw.write(data),resize:(cols,rows)=>raw.resize(cols,rows),kill:()=>raw.kill(),onData:null,onExit:null},raw.onData=(data)=>{if(dataCallback)dataCallback(sessionId,data);if(ptyHandle.onData)ptyHandle.onData(data)},raw.onExit=(code)=>{let session2=sessions.get(sessionId);if(session2)session2.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}}catch{let parts=command.split(" "),proc=Bun.spawn(parts,{cwd:opts.cwd,env:opts.env,stdin:"pipe",stdout:"pipe",stderr:"pipe"});ptyHandle={write:(data)=>proc.stdin.write(data),resize:()=>{},kill:()=>proc.kill(),onData:null,onExit:null},pipeStdout(proc.stdout,sessionId,ptyHandle),proc.exited.then((code)=>onProcExit(sessionId,code,ptyHandle))}let session={id:sessionId,pty:ptyHandle,agentId:opts.agentId,executorId:opts.executorId,taskId:opts.taskId,command,state:"running",cols:opts.cols,rows:opts.rows,createdAt:new Date().toISOString()};return sessions.set(sessionId,session),session}function handlePtyExit(sessionId,code){let session=sessions.get(sessionId);if(!session?.executorId)return;(async()=>{try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,code===0?"done":"error")}catch{}})()}var sessions,dataCallback=null,exitCallback=null;var init_pty=__esm(()=>{sessions=new Map});var exports_src_backend={};import{existsSync as existsSync50,readFileSync as readFileSync31,readdirSync as readdirSync11,writeFileSync as writeFileSync20}from"fs";import{homedir as homedir41}from"os";import{join as join62,resolve as resolve11}from"path";function findSkillsDir(){let genieHome4=process.env.GENIE_HOME??join62(homedir41(),".genie"),candidateDirs=[join62(process.cwd(),"skills"),join62(genieHome4,"..","skills")];for(let d of candidateDirs)if(existsSync50(d))return d;return null}function parseSkillMd(content,fallbackName){let descMatch=content.match(/^description:\s*["']?(.+?)["']?\s*$/m),description="";if(descMatch)description=descMatch[1].trim();else description=content.split(`
2544
- `).filter((l)=>l.trim()&&!l.startsWith("---")&&!l.startsWith("#"))[0]?.trim()??"";let nameMatch=content.match(/^name:\s*(.+?)\s*$/m);return{name:nameMatch?nameMatch[1].trim():fallbackName,description}}function scanSkillsDirectory(skillsDir){try{let entries=readdirSync11(skillsDir,{withFileTypes:!0}),skills=[];for(let entry2 of entries){if(!entry2.isDirectory())continue;let skillMdPath=join62(skillsDir,entry2.name,"SKILL.md");if(!existsSync50(skillMdPath))continue;let content=readFileSync31(skillMdPath,"utf-8"),{name,description}=parseSkillMd(content,entry2.name);skills.push({name,slug:entry2.name,description,path:skillMdPath})}return skills.sort((a,b2)=>a.name.localeCompare(b2.name))}catch{return[]}}function scanRuleDirs(){let genieHome4=process.env.GENIE_HOME??join62(homedir41(),".genie"),ruleDirs=[join62(genieHome4,"rules"),join62(homedir41(),".claude","rules")],allRules=[];for(let rulesDir of ruleDirs){if(!existsSync50(rulesDir))continue;try{let files=readdirSync11(rulesDir).filter((f)=>f.endsWith(".md"));for(let f of files){let filePath=join62(rulesDir,f);allRules.push({name:f.replace(".md",""),path:filePath,content:readFileSync31(filePath,"utf-8"),source:rulesDir.includes(".claude")?"claude":"genie"})}}catch{}}return allRules}async function start2(){console.log("[genie-app] Starting backend service..."),console.log(`[genie-app] NATS: ${NATS_URL}`),console.log(`[genie-app] Org: ${ORG_ID}`);let sql=await getConnection();console.log("[genie-app] PG connected"),nc=await import_nats5.connect({servers:NATS_URL,name:"genie-app-backend",reconnect:!0,maxReconnectAttempts:-1,reconnectTimeWait:2000}),console.log("[genie-app] NATS connected"),registerHandlers(sql),await startListening(nc,ORG_ID),console.log("[genie-app] PG LISTEN/NOTIFY active (11 channels)"),onPtyData((sessionId,data)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({data})))}),onPtyExit((sessionId,code)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({code,type:"exit"})))}),console.log("[genie-app] Backend ready")}function registerHandlers(sql){if(!nc)return;let sub=GENIE_SUBJECTS;reply(sub.dashboard.stats(ORG_ID),async()=>{let[agentRows,taskRows,teamRows,costRows,snapshot]=await Promise.all([sql`
2543
+ `):[],max=names.length+1,re=new RegExp(`^${baseName}-(\\d+)$`);for(let n of names){let m=n.match(re);if(m)max=Math.max(max,Number.parseInt(m[1],10)+1)}return max}function newAgentWindow(agentName){(async()=>{try{let{reconcileStaleSpawns:reconcileStaleSpawns2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));await reconcileStaleSpawns2()}catch{}let{spawn:spawn5}=__require("child_process"),{join:join62,resolve:resolve11}=__require("path"),{existsSync:existsSync50}=__require("fs"),bunPath=process.execPath||"bun",genieBin=process.argv[1],wsRoot=process.env.GENIE_TUI_WORKSPACE,cwd;if(wsRoot){let parentName=agentName.includes("/")?agentName.slice(0,agentName.indexOf("/")):agentName,agentDir=resolve11(join62(wsRoot,"agents",parentName));if(existsSync50(agentDir))cwd=agentDir}let suffix=nextRoleSuffix(agentName),role=`${agentName}-${suffix}`,sessionName=agentName.replace(/\//g,"-"),args=["spawn",agentName,"--role",role,"--session",sessionName,"--new-window"];(genieBin&&genieBin!=="genie"?spawn5(bunPath,[genieBin,...args],{detached:!0,stdio:"ignore",cwd}):spawn5("genie",args,{detached:!0,stdio:"ignore",cwd})).unref()})()}var SESSION_NAME="genie-tui",TMUX_SOCKET="genie-tui",GENIE_AGENT_SOCKET="genie",TUI_TMUX_CONF,TMUX_BIN;var init_tmux2=__esm(()=>{init_ensure_tmux();TUI_TMUX_CONF=(()=>{let{existsSync:existsSync50}=__require("fs"),tuiConf=`${process.env.GENIE_HOME??`${process.env.HOME}/.genie`}/tui-tmux.conf`;return existsSync50(tuiConf)?tuiConf:"/dev/null"})(),TMUX_BIN=tmuxBin()});function s(orgId,path3){return`khal.${orgId}.genie.${path3}`}var GENIE_SUBJECTS;var init_subjects=__esm(()=>{GENIE_SUBJECTS={dashboard:{stats:(orgId)=>s(orgId,"dashboard.stats")},agents:{list:(orgId)=>s(orgId,"agents.list"),show:(orgId)=>s(orgId,"agents.show")},sessions:{list:(orgId)=>s(orgId,"sessions.list"),content:(orgId)=>s(orgId,"sessions.content"),search:(orgId)=>s(orgId,"sessions.search")},tasks:{list:(orgId)=>s(orgId,"tasks.list"),show:(orgId)=>s(orgId,"tasks.show")},boards:{list:(orgId)=>s(orgId,"boards.list"),show:(orgId)=>s(orgId,"boards.show")},costs:{summary:(orgId)=>s(orgId,"costs.summary"),sessions:(orgId)=>s(orgId,"costs.sessions"),tokens:(orgId)=>s(orgId,"costs.tokens"),efficiency:(orgId)=>s(orgId,"costs.efficiency")},schedules:{list:(orgId)=>s(orgId,"schedules.list"),history:(orgId)=>s(orgId,"schedules.history")},system:{health:(orgId)=>s(orgId,"system.health"),snapshots:(orgId)=>s(orgId,"system.snapshots"),tables:(orgId)=>s(orgId,"system.tables"),channels:(orgId)=>s(orgId,"system.channels")},settings:{get:(orgId)=>s(orgId,"settings.get"),set:(orgId)=>s(orgId,"settings.set"),templates:(orgId)=>s(orgId,"settings.templates"),templateSave:(orgId)=>s(orgId,"settings.templates.save"),skills:(orgId)=>s(orgId,"settings.skills"),rules:(orgId)=>s(orgId,"settings.rules"),workspace:(orgId)=>s(orgId,"settings.workspace"),testPg:(orgId)=>s(orgId,"settings.test_pg")},pty:{create:(orgId)=>s(orgId,"pty.create"),input:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.input`),data:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.data`),resize:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.resize`),kill:(orgId,sessionId)=>s(orgId,`pty.${sessionId}.kill`)},fs:{list:(orgId)=>s(orgId,"fs.list"),read:(orgId)=>s(orgId,"fs.read"),write:(orgId)=>s(orgId,"fs.write")},approval:{resolve:(orgId)=>s(orgId,"approval.resolve"),list:(orgId)=>s(orgId,"approval.list")},events:{agentState:(orgId)=>s(orgId,"events.agent_state"),executorState:(orgId)=>s(orgId,"events.executor_state"),taskStage:(orgId)=>s(orgId,"events.task_stage"),runtime:(orgId)=>s(orgId,"events.runtime"),audit:(orgId)=>s(orgId,"events.audit"),message:(orgId)=>s(orgId,"events.message"),mailbox:(orgId)=>s(orgId,"events.mailbox"),taskDep:(orgId)=>s(orgId,"events.task_dep"),trigger:(orgId)=>s(orgId,"events.trigger"),approvalRequest:(orgId)=>s(orgId,"events.approval_request"),approvalResolved:(orgId)=>s(orgId,"events.approval_resolved")}}});function emit2(event){for(let handler of listeners)handler(event)}async function startListening(nats,orgId){if(listenersActive)return;if(listenersActive=!0,nats)natsConn=nats,natsOrgId=orgId??"default";let sql=await getConnection();for(let mapping of CHANNEL_MAP){let listener=await sql.listen(mapping.channel,(payload)=>{let parsed;try{parsed=JSON.parse(payload)}catch{parsed={raw:payload}}if(emit2({type:mapping.eventType,payload:parsed}),natsConn){let subject=mapping.natsSubject(natsOrgId);natsConn.publish(subject,sc.encode(JSON.stringify(parsed)))}});stopFns.push(()=>listener.unlisten())}}async function stopListening(){listenersActive=!1,natsConn=null,await Promise.allSettled(stopFns.map((fn)=>fn())),stopFns=[]}var import_nats5,listeners,natsConn=null,natsOrgId="default",sc,CHANNEL_MAP,listenersActive=!1,stopFns;var init_pg_bridge=__esm(()=>{init_db();init_subjects();import_nats5=__toESM(require_mod4(),1),listeners=new Set;sc=import_nats5.StringCodec(),CHANNEL_MAP=[{channel:"genie_agent_state",eventType:"agent-state-changed",natsSubject:GENIE_SUBJECTS.events.agentState},{channel:"genie_executor_state",eventType:"executor-state-changed",natsSubject:GENIE_SUBJECTS.events.executorState},{channel:"genie_task_stage",eventType:"task-stage-changed",natsSubject:GENIE_SUBJECTS.events.taskStage},{channel:"genie_runtime_event",eventType:"runtime-event",natsSubject:GENIE_SUBJECTS.events.runtime},{channel:"genie_audit_event",eventType:"audit-event",natsSubject:GENIE_SUBJECTS.events.audit},{channel:"genie_message",eventType:"message",natsSubject:GENIE_SUBJECTS.events.message},{channel:"genie_mailbox_delivery",eventType:"mailbox-delivery",natsSubject:GENIE_SUBJECTS.events.mailbox},{channel:"genie_task_dep",eventType:"task-dep-changed",natsSubject:GENIE_SUBJECTS.events.taskDep},{channel:"genie_trigger_due",eventType:"trigger-due",natsSubject:GENIE_SUBJECTS.events.trigger},{channel:"genie_approval_request",eventType:"approval-request",natsSubject:GENIE_SUBJECTS.events.approvalRequest},{channel:"genie_approval_resolved",eventType:"approval-resolved",natsSubject:GENIE_SUBJECTS.events.approvalResolved}],stopFns=[]});import{randomUUID as randomUUID10}from"crypto";function onPtyData(cb){dataCallback=cb}function onPtyExit(cb){exitCallback=cb}async function spawnForAgent(agentName,opts={}){let{buildClaudeCommand:buildClaudeCommand3}=await Promise.resolve().then(() => (init_provider_adapters(),exports_provider_adapters)),{createExecutor:createExecutor2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),{findOrCreateAgent:findOrCreateAgent2,setCurrentExecutor:setCurrentExecutor2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),cols=opts.cols??120,rows=opts.rows??40,cwd=opts.cwd??process.cwd(),launch=buildClaudeCommand3({provider:"claude",team:"app",role:"engineer",name:agentName}),agent=await findOrCreateAgent2(agentName,"app","engineer"),executor=await createExecutor2(agent.id,"app-pty","process",{repoPath:cwd,state:"spawning",metadata:{command:launch.command,source:"genie-app"}});await setCurrentExecutor2(agent.id,executor.id);let env={...process.env,...launch.env,GENIE_APP_PTY:"true"},session=spawnPty(launch.command,{cwd,cols,rows,env,agentId:agent.id,executorId:executor.id,taskId:opts.taskId??null}),{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await updateExecutorState2(executor.id,"running"),session}function spawnBash(cwd){let shell=process.env.SHELL??"/bin/bash";return spawnPty(shell,{cwd:cwd??process.cwd(),cols:120,rows:40,env:process.env,agentId:null,executorId:null,taskId:null})}function writeTerminal(sessionId,data){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.write(data),!0}function resizeTerminal(sessionId,cols,rows){let session=sessions.get(sessionId);if(!session||session.state!=="running")return!1;return session.pty.resize(cols,rows),session.cols=cols,session.rows=rows,!0}async function killTerminal(sessionId){let session=sessions.get(sessionId);if(!session)return!1;if(session.pty.kill(),session.state="exited",session.executorId)try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,"terminated")}catch{}return sessions.delete(sessionId),!0}async function killAll(){let ids=[...sessions.keys()];await Promise.allSettled(ids.map((id)=>killTerminal(id)))}async function pipeStdout(stdout,sessionId,ptyHandle){let reader=stdout.getReader(),decoder=new TextDecoder;try{while(!0){let{done,value}=await reader.read();if(done)break;let text=decoder.decode(value);if(dataCallback)dataCallback(sessionId,text);if(ptyHandle.onData)ptyHandle.onData(text)}}catch{}}function onProcExit(sessionId,code,ptyHandle){let session=sessions.get(sessionId);if(session)session.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}function spawnPty(command,opts){let sessionId=randomUUID10(),ptyHandle;try{let bunPty=(()=>{throw new Error("Cannot require module "+"bun-pty");})(),parts=command.split(" "),raw=bunPty.spawn(parts,{cwd:opts.cwd,env:opts.env,cols:opts.cols,rows:opts.rows});ptyHandle={write:(data)=>raw.write(data),resize:(cols,rows)=>raw.resize(cols,rows),kill:()=>raw.kill(),onData:null,onExit:null},raw.onData=(data)=>{if(dataCallback)dataCallback(sessionId,data);if(ptyHandle.onData)ptyHandle.onData(data)},raw.onExit=(code)=>{let session2=sessions.get(sessionId);if(session2)session2.state="exited";if(exitCallback)exitCallback(sessionId,code);if(ptyHandle.onExit)ptyHandle.onExit(code);handlePtyExit(sessionId,code)}}catch{let parts=command.split(" "),proc=Bun.spawn(parts,{cwd:opts.cwd,env:opts.env,stdin:"pipe",stdout:"pipe",stderr:"pipe"});ptyHandle={write:(data)=>proc.stdin.write(data),resize:()=>{},kill:()=>proc.kill(),onData:null,onExit:null},pipeStdout(proc.stdout,sessionId,ptyHandle),proc.exited.then((code)=>onProcExit(sessionId,code,ptyHandle))}let session={id:sessionId,pty:ptyHandle,agentId:opts.agentId,executorId:opts.executorId,taskId:opts.taskId,command,state:"running",cols:opts.cols,rows:opts.rows,createdAt:new Date().toISOString()};return sessions.set(sessionId,session),session}function handlePtyExit(sessionId,code){let session=sessions.get(sessionId);if(!session?.executorId)return;(async()=>{try{let{updateExecutorState:updateExecutorState2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));await updateExecutorState2(session.executorId,code===0?"done":"error")}catch{}})()}var sessions,dataCallback=null,exitCallback=null;var init_pty=__esm(()=>{sessions=new Map});var exports_src_backend={};import{existsSync as existsSync50,readFileSync as readFileSync31,readdirSync as readdirSync11,writeFileSync as writeFileSync20}from"fs";import{homedir as homedir41}from"os";import{join as join62,resolve as resolve11}from"path";function findSkillsDir(){let genieHome4=process.env.GENIE_HOME??join62(homedir41(),".genie"),candidateDirs=[join62(process.cwd(),"skills"),join62(genieHome4,"..","skills")];for(let d of candidateDirs)if(existsSync50(d))return d;return null}function parseSkillMd(content,fallbackName){let descMatch=content.match(/^description:\s*["']?(.+?)["']?\s*$/m),description="";if(descMatch)description=descMatch[1].trim();else description=content.split(`
2544
+ `).filter((l)=>l.trim()&&!l.startsWith("---")&&!l.startsWith("#"))[0]?.trim()??"";let nameMatch=content.match(/^name:\s*(.+?)\s*$/m);return{name:nameMatch?nameMatch[1].trim():fallbackName,description}}function scanSkillsDirectory(skillsDir){try{let entries=readdirSync11(skillsDir,{withFileTypes:!0}),skills=[];for(let entry2 of entries){if(!entry2.isDirectory())continue;let skillMdPath=join62(skillsDir,entry2.name,"SKILL.md");if(!existsSync50(skillMdPath))continue;let content=readFileSync31(skillMdPath,"utf-8"),{name,description}=parseSkillMd(content,entry2.name);skills.push({name,slug:entry2.name,description,path:skillMdPath})}return skills.sort((a,b2)=>a.name.localeCompare(b2.name))}catch{return[]}}function scanRuleDirs(){let genieHome4=process.env.GENIE_HOME??join62(homedir41(),".genie"),ruleDirs=[join62(genieHome4,"rules"),join62(homedir41(),".claude","rules")],allRules=[];for(let rulesDir of ruleDirs){if(!existsSync50(rulesDir))continue;try{let files=readdirSync11(rulesDir).filter((f)=>f.endsWith(".md"));for(let f of files){let filePath=join62(rulesDir,f);allRules.push({name:f.replace(".md",""),path:filePath,content:readFileSync31(filePath,"utf-8"),source:rulesDir.includes(".claude")?"claude":"genie"})}}catch{}}return allRules}async function start2(){console.log("[genie-app] Starting backend service..."),console.log(`[genie-app] NATS: ${NATS_URL}`),console.log(`[genie-app] Org: ${ORG_ID}`);let sql=await getConnection();console.log("[genie-app] PG connected"),nc=await import_nats6.connect({servers:NATS_URL,name:"genie-app-backend",reconnect:!0,maxReconnectAttempts:-1,reconnectTimeWait:2000}),console.log("[genie-app] NATS connected"),registerHandlers(sql),await startListening(nc,ORG_ID),console.log("[genie-app] PG LISTEN/NOTIFY active (11 channels)"),onPtyData((sessionId,data)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({data})))}),onPtyExit((sessionId,code)=>{if(!nc)return;nc.publish(GENIE_SUBJECTS.pty.data(ORG_ID,sessionId),sc2.encode(JSON.stringify({code,type:"exit"})))}),console.log("[genie-app] Backend ready")}function registerHandlers(sql){if(!nc)return;let sub=GENIE_SUBJECTS;reply(sub.dashboard.stats(ORG_ID),async()=>{let[agentRows,taskRows,teamRows,costRows,snapshot]=await Promise.all([sql`
2545
2545
  SELECT
2546
2546
  COUNT(*) FILTER (WHERE a.state IN ('working', 'idle', 'permission', 'question')) AS online,
2547
2547
  COUNT(*) FILTER (WHERE a.state = 'error') AS errored,
@@ -2770,7 +2770,7 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
2770
2770
  cwd = EXCLUDED.cwd,
2771
2771
  extra_args = EXCLUDED.extra_args,
2772
2772
  native_team_enabled = EXCLUDED.native_team_enabled
2773
- `,{ok:!0}}),reply(sub.settings.testPg(ORG_ID),async()=>{try{return await sql`SELECT 1 AS ok`,{ok:!0,message:"Connection successful"}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}),reply(sub.pty.create(ORG_ID),async(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}}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.input`,async(sessionId,params)=>{writeTerminal(sessionId,params.data)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.resize`,async(sessionId,params)=>{resizeTerminal(sessionId,params.cols,params.rows)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.kill`,async(sessionId)=>{await killTerminal(sessionId)}),reply(sub.fs.list(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);if(!existsSync50(targetPath))return{error:"not_found"};try{return readdirSync11(targetPath,{withFileTypes:!0}).map((e)=>({name:e.name,isDirectory:e.isDirectory(),path:join62(targetPath,e.name)}))}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.read(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);if(!existsSync50(targetPath))return{error:"not_found"};try{return{content:readFileSync31(targetPath,"utf-8")}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.write(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);try{return writeFileSync20(targetPath,params.content),{ok:!0}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.approval.resolve(ORG_ID),async(params)=>{return{ok:await resolveApproval(params.id,params.decision,params.decided_by)}}),reply(sub.approval.list(ORG_ID),async(params)=>{return listPendingApprovals(params.agent_name)}),console.log("[genie-app] All request/reply handlers registered")}function decodeParams(data){if(data.length===0)return{};try{return JSON.parse(sc2.decode(data))}catch{return{}}}function subscribePtyWildcard(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);(async()=>{for await(let msg of subscription)try{let params=decodeParams(msg.data),sessionId=msg.subject.split(".")[4];if(sessionId)await handler(sessionId,params)}catch(err){console.error(`[genie-app] PTY wildcard error on ${msg.subject}:`,err instanceof Error?err.message:String(err))}})()}function reply(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);processSubscription(subscription,subject,handler)}async function processSubscription(subscription,subject,handler){for await(let msg of subscription)try{let params=decodeParams(msg.data),result2=await handler(params);if(msg.reply)msg.respond(sc2.encode(JSON.stringify(result2)))}catch(err){let errorMsg=err instanceof Error?err.message:String(err);if(console.error(`[genie-app] Handler error on ${subject}:`,errorMsg),msg.reply)msg.respond(sc2.encode(JSON.stringify({error:errorMsg})))}}async function shutdown2(){if(shutdownRequested)return;if(shutdownRequested=!0,console.log("[genie-app] Shutting down..."),await killAll(),await stopListening(),nc){try{await nc.drain()}catch{}nc=null}console.log("[genie-app] Shutdown complete"),process.exit(0)}var import_nats5,NATS_URL,ORG_ID,_PG_URL,sc2,nc=null,shutdownRequested=!1;var init_src_backend=__esm(()=>{init_db();init_claude_sdk_remote_approval();init_subjects();init_pg_bridge();init_pty();import_nats5=__toESM(require_mod4(),1);process.env.GENIE_APP="1";NATS_URL=process.env.GENIE_NATS_URL??"nats://localhost:4222",ORG_ID=process.env.GENIE_ORG_ID??"default",_PG_URL=process.env.GENIE_PG_URL??"postgresql://localhost:19642/genie",sc2=import_nats5.StringCodec();process.on("SIGTERM",()=>void shutdown2());process.on("SIGINT",()=>void shutdown2());start2().catch((err)=>{console.error("[genie-app] Fatal error:",err),process.exit(1)})});var exports_event_renderer={};__export(exports_event_renderer,{renderRuntimeEvent:()=>renderRuntimeEvent,renderAuditEvent:()=>renderAuditEvent,formatEventLine:()=>formatEventLine});function renderAuditEvent(input){let details=input.details;if(typeof details==="string")try{details=JSON.parse(details)}catch{details={}}let normalized={...input,details:details??{}},renderer=auditRenderers[input.event_type];if(renderer)return renderer(normalized);let event=normalized,json2=JSON.stringify(event.details);return{indicator:color("dim","\u25CB"),content:`${color("gray",event.event_type)} ${json2==="{}"?"":color("dim",json2)}`,context:`${event.entity_type}:${event.entity_id}`}}function renderRuntimeEvent(input){let renderer=runtimeRenderers[input.kind];if(renderer)return renderer(input);return{indicator:color("dim","\u25CB"),content:`${color("gray",input.kind)} ${input.text}`,context:input.agent}}function formatEventLine(timeStr,rendered){let termWidth=process.stdout.columns||120,timeCol=color("gray",timeStr),timeWidth=stripAnsi2(timeCol).length,iconWidth=stripAnsi2(rendered.indicator).length,prefixWidth=timeWidth+2+iconWidth+2,contextStr=rendered.context?color("dim",rendered.context):"",contextWidth=contextStr?stripAnsi2(contextStr).length+2:0,contentWidth=Math.max(20,termWidth-prefixWidth-contextWidth),content=stripAnsi2(rendered.content).replace(/[\r\n]+/g," ");if(content.length<=contentWidth){let paddedContent=padToWidth(rendered.content,contentWidth);return`${timeCol} ${rendered.indicator} ${paddedContent}${contextStr?` ${contextStr}`:""}`}let chunks=wrapText(content,contentWidth),indent2=" ".repeat(prefixWidth),firstLine=`${timeCol} ${rendered.indicator} ${padToWidth(chunks[0],contentWidth)}${contextStr?` ${contextStr}`:""}`,rest=chunks.slice(1).map((c)=>`${indent2}${c}`);return[firstLine,...rest].join(`
2773
+ `,{ok:!0}}),reply(sub.settings.testPg(ORG_ID),async()=>{try{return await sql`SELECT 1 AS ok`,{ok:!0,message:"Connection successful"}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}),reply(sub.pty.create(ORG_ID),async(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}}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.input`,async(sessionId,params)=>{writeTerminal(sessionId,params.data)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.resize`,async(sessionId,params)=>{resizeTerminal(sessionId,params.cols,params.rows)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.kill`,async(sessionId)=>{await killTerminal(sessionId)}),reply(sub.fs.list(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);if(!existsSync50(targetPath))return{error:"not_found"};try{return readdirSync11(targetPath,{withFileTypes:!0}).map((e)=>({name:e.name,isDirectory:e.isDirectory(),path:join62(targetPath,e.name)}))}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.read(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);if(!existsSync50(targetPath))return{error:"not_found"};try{return{content:readFileSync31(targetPath,"utf-8")}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.write(ORG_ID),async(params)=>{let targetPath=resolve11(params.path);try{return writeFileSync20(targetPath,params.content),{ok:!0}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.approval.resolve(ORG_ID),async(params)=>{return{ok:await resolveApproval(params.id,params.decision,params.decided_by)}}),reply(sub.approval.list(ORG_ID),async(params)=>{return listPendingApprovals(params.agent_name)}),console.log("[genie-app] All request/reply handlers registered")}function decodeParams(data){if(data.length===0)return{};try{return JSON.parse(sc2.decode(data))}catch{return{}}}function subscribePtyWildcard(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);(async()=>{for await(let msg of subscription)try{let params=decodeParams(msg.data),sessionId=msg.subject.split(".")[4];if(sessionId)await handler(sessionId,params)}catch(err){console.error(`[genie-app] PTY wildcard error on ${msg.subject}:`,err instanceof Error?err.message:String(err))}})()}function reply(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);processSubscription(subscription,subject,handler)}async function processSubscription(subscription,subject,handler){for await(let msg of subscription)try{let params=decodeParams(msg.data),result2=await handler(params);if(msg.reply)msg.respond(sc2.encode(JSON.stringify(result2)))}catch(err){let errorMsg=err instanceof Error?err.message:String(err);if(console.error(`[genie-app] Handler error on ${subject}:`,errorMsg),msg.reply)msg.respond(sc2.encode(JSON.stringify({error:errorMsg})))}}async function shutdown2(){if(shutdownRequested)return;if(shutdownRequested=!0,console.log("[genie-app] Shutting down..."),await killAll(),await stopListening(),nc){try{await nc.drain()}catch{}nc=null}console.log("[genie-app] Shutdown complete"),process.exit(0)}var import_nats6,NATS_URL,ORG_ID,_PG_URL,sc2,nc=null,shutdownRequested=!1;var init_src_backend=__esm(()=>{init_db();init_claude_sdk_remote_approval();init_subjects();init_pg_bridge();init_pty();import_nats6=__toESM(require_mod4(),1);process.env.GENIE_APP="1";NATS_URL=process.env.GENIE_NATS_URL??"nats://localhost:4222",ORG_ID=process.env.GENIE_ORG_ID??"default",_PG_URL=process.env.GENIE_PG_URL??"postgresql://localhost:19642/genie",sc2=import_nats6.StringCodec();process.on("SIGTERM",()=>void shutdown2());process.on("SIGINT",()=>void shutdown2());start2().catch((err)=>{console.error("[genie-app] Fatal error:",err),process.exit(1)})});var exports_event_renderer={};__export(exports_event_renderer,{renderRuntimeEvent:()=>renderRuntimeEvent,renderAuditEvent:()=>renderAuditEvent,formatEventLine:()=>formatEventLine});function renderAuditEvent(input){let details=input.details;if(typeof details==="string")try{details=JSON.parse(details)}catch{details={}}let normalized={...input,details:details??{}},renderer=auditRenderers[input.event_type];if(renderer)return renderer(normalized);let event=normalized,json2=JSON.stringify(event.details);return{indicator:color("dim","\u25CB"),content:`${color("gray",event.event_type)} ${json2==="{}"?"":color("dim",json2)}`,context:`${event.entity_type}:${event.entity_id}`}}function renderRuntimeEvent(input){let renderer=runtimeRenderers[input.kind];if(renderer)return renderer(input);return{indicator:color("dim","\u25CB"),content:`${color("gray",input.kind)} ${input.text}`,context:input.agent}}function formatEventLine(timeStr,rendered){let termWidth=process.stdout.columns||120,timeCol=color("gray",timeStr),timeWidth=stripAnsi2(timeCol).length,iconWidth=stripAnsi2(rendered.indicator).length,prefixWidth=timeWidth+2+iconWidth+2,contextStr=rendered.context?color("dim",rendered.context):"",contextWidth=contextStr?stripAnsi2(contextStr).length+2:0,contentWidth=Math.max(20,termWidth-prefixWidth-contextWidth),content=stripAnsi2(rendered.content).replace(/[\r\n]+/g," ");if(content.length<=contentWidth){let paddedContent=padToWidth(rendered.content,contentWidth);return`${timeCol} ${rendered.indicator} ${paddedContent}${contextStr?` ${contextStr}`:""}`}let chunks=wrapText(content,contentWidth),indent2=" ".repeat(prefixWidth),firstLine=`${timeCol} ${rendered.indicator} ${padToWidth(chunks[0],contentWidth)}${contextStr?` ${contextStr}`:""}`,rest=chunks.slice(1).map((c)=>`${indent2}${c}`);return[firstLine,...rest].join(`
2774
2774
  `)}function padToWidth(coloredText,width){let visible=stripAnsi2(coloredText).length,pad=Math.max(0,width-visible);return coloredText+" ".repeat(pad)}function wrapText(text,width){let chunks=[],remaining=text;while(remaining.length>width){let breakAt=remaining.lastIndexOf(" ",width);if(breakAt<=0||breakAt<width/2)breakAt=width;chunks.push(remaining.slice(0,breakAt).trimEnd()),remaining=remaining.slice(breakAt).trimStart()}if(remaining.length>0)chunks.push(remaining);return chunks}function formatDuration3(ms){if(ms<1000)return`${ms}ms`;if(ms<60000)return`${(ms/1000).toFixed(1)}s`;let s2=Math.floor(ms/1000);return`${Math.floor(s2/60)}m${s2%60}s`}var shortEntity=(id)=>id.split(":")[0]??id,auditRenderers,runtimeRenderers;var init_event_renderer=__esm(()=>{init_term_format();auditRenderers={"sdk.assistant.message":(e)=>({indicator:color("brightCyan","\uD83D\uDCAC"),content:color("brightCyan",`"${e.details.textPreview??""}"`),context:shortEntity(e.entity_id)}),"sdk.user.message":(e)=>{let d=e.details,text=d.textPreview;if(typeof text==="string"&&text.length>0){let tag=d.isReplay?color("dim"," (replay)"):d.isSynthetic?color("dim"," (synthetic)"):"";return{indicator:color("cyan","\uD83D\uDC64"),content:`${color("cyan",`"${text}"`)}${tag}`,context:shortEntity(e.entity_id)}}let kind=d.isReplay?"replay":d.isSynthetic?"synthetic":"turn";return{indicator:color("cyan","\uD83D\uDC64"),content:color("dim",`user ${kind}`),context:shortEntity(e.entity_id)}},"sdk.hook.started":(e)=>({indicator:color("yellow","\uD83E\uDE9D"),content:`${e.details.hookName??"?"} ${color("dim","started")}`,context:shortEntity(e.entity_id)}),"sdk.hook.response":(e)=>{let outcome=e.details.outcome==="success"?color("green","\u2713"):color("red","\u2717"),hookId=e.details.hookId?color("dim",` ${String(e.details.hookId).slice(0,8)}`):"";return{indicator:color("yellow","\uD83E\uDE9D"),content:`${e.details.hookName??"?"} ${outcome}${hookId}`,context:shortEntity(e.entity_id)}},"sdk.system":(e)=>{let model=String(e.details.model??"?").replace(/^claude-/,""),tools=e.details.tools??0,session=e.details.sessionId?` \xB7 ${color("dim",String(e.details.sessionId).slice(0,8))}`:"";return{indicator:color("gray","\u2699"),content:`init ${color("cyan",model)} \xB7 ${tools} tools${session}`,context:shortEntity(e.entity_id)}},"sdk.result.success":(e)=>{let d=e.details,dur=d.durationMs?formatDuration3(d.durationMs):"",cost=typeof d.totalCostUsd==="number"?`$${d.totalCostUsd.toFixed(4)}`:"",preview=d.resultPreview?` \xB7 "${d.resultPreview}"`:"";return{indicator:color("green","\u2728"),content:`${color("brightGreen",dur)} \xB7 ${color("yellow",cost)}${color("dim",preview)}`,context:shortEntity(e.entity_id)}},"sdk.rate_limit":(e)=>({indicator:color("gray","\u23F1"),content:color("dim",`rate limit: ${e.details.status??"?"}`),context:shortEntity(e.entity_id)}),"sdk.api.retry":(e)=>{let d=e.details,attempt=d.attempt??"?",max=d.maxRetries??"?",delay=d.retryDelayMs?`${Math.round(d.retryDelayMs)}ms`:"";return{indicator:color("yellow","\u21BB"),content:color("yellow",`retry ${attempt}/${max} \xB7 ${d.error??"?"}${delay?` \xB7 ${delay}`:""}`),context:shortEntity(e.entity_id)}},"session.created_fresh":(e)=>({indicator:color("green","\u2726"),content:color("green",`session created \xB7 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"session.resumed":(e)=>({indicator:color("cyan","\u21BB"),content:color("cyan",`session resumed \xB7 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"deliver.start":(e)=>({indicator:color("blue","\u25B8"),content:color("dim",`deliver \u2192 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"deliver.end":(e)=>{let turns=e.details.turn_count?` \xB7 turn ${e.details.turn_count}`:"";return{indicator:color("blue","\u25C2"),content:color("dim",`deliver done${turns}`),context:shortEntity(e.entity_id)}},state_changed:(e)=>({indicator:color("gray","\u25C9"),content:color("dim",`state \u2192 ${e.details.state??"?"}`),context:shortEntity(e.entity_id)}),"executor.spawn":(e)=>({indicator:color("green","\u25B6"),content:color("green",`spawn ${e.details.source??""}`),context:e.entity_id}),"executor.terminate":(e)=>({indicator:color("yellow","\u25A0"),content:color("yellow",`terminate ${e.details.source??""}`),context:e.entity_id}),"executor.deliver":(e)=>{let d=e.details,dur=d.durationMs?formatDuration3(d.durationMs):"",tokens2=d.tokens,tokStr=tokens2?`${tokens2.input??0}\u2192${tokens2.output??0}`:"",parts=[dur,tokStr].filter(Boolean);return{indicator:color("blue","\u2192"),content:parts.join(" \xB7 "),context:e.entity_id}},"task.error":(e)=>({indicator:color("red","\u2717"),content:color("red",String(e.details.error??"unknown error")),context:e.entity_id}),command_success:(e)=>({indicator:color("dim","\xB7"),content:color("dim",`${e.details.duration_ms??0}ms`),context:e.entity_id})};runtimeRenderers={user:(e)=>({indicator:color("brightCyan","\uD83D\uDC64"),content:e.text,context:e.agent}),assistant:(e)=>({indicator:color("cyan","\uD83E\uDD16"),content:color("cyan",e.text),context:e.agent}),message:(e)=>({indicator:color("magenta","\u2709"),content:e.text,context:e.agent}),tool_call:(e)=>({indicator:color("yellow","\uD83D\uDD27"),content:color("yellow",e.text),context:e.agent}),tool_result:(e)=>({indicator:color("gray","\u2B90"),content:color("dim",e.text),context:e.agent}),state:(e)=>({indicator:color("gray","\u25C9"),content:color("dim",e.text),context:e.agent}),system:(e)=>({indicator:color("gray","\u2699"),content:color("dim",e.text),context:e.agent}),qa:(e)=>({indicator:color("magenta","\uD83E\uDDEA"),content:e.text,context:e.agent})}});var exports_board_service={};__export(exports_board_service,{updateColumn:()=>updateColumn,updateBoard:()=>updateBoard,reorderColumns:()=>reorderColumns,removeColumn:()=>removeColumn,reconcileBoard:()=>reconcileBoard,listBoards:()=>listBoards,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??palette.textDim,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`
2775
2775
  SELECT columns FROM board_templates WHERE name = ${input.fromTemplate} OR id = ${input.fromTemplate} LIMIT 1
2776
2776
  `;if(tmplRows.length===0)throw Error(`Template not found: ${input.fromTemplate}`);columns=tmplRows[0].columns.map((col,i2)=>({...col,id:generateColumnId(),position:i2}))}else if(input.columns)columns=input.columns.map((col,i2)=>fillColumnDefaults(col,i2));let rows=await sql`
@@ -3890,12 +3890,12 @@ Bus `);for(let i2=1;i2<parts.length;i2++){let usb2=parseLinuxUsb(parts[i2]);resu
3890
3890
  `;appendFileSync5(logPath,line,{mode:420})}catch{}}function installTuiExitSignals(){let exitOnSignal=(signal)=>{process.exit(signal==="SIGHUP"?0:128+(signal==="SIGINT"?2:15))};process.on("SIGHUP",exitOnSignal),process.on("SIGINT",exitOnSignal),process.on("SIGTERM",exitOnSignal)}async function launchTui(){installTuiExitSignals(),await recordTuiLaunchBreadcrumb();let{renderNav:renderNav2}=await init_render().then(() => exports_render);await renderNav2()}var TUI_CRASH_LOG_BANNER_PREFIX="--- tui-launch ",TUI_CRASH_LOG_RECOVERY_MAX_BYTES=65536,TUI_CRASH_LOG_RECOVERY_MAX_MSG_CHARS=3000;var init_tui=()=>{};var exports_resolve_agent_cwd={};__export(exports_resolve_agent_cwd,{resolveAgentFromCwd:()=>resolveAgentFromCwd});import{existsSync as existsSync75}from"fs";import{basename as basename17,dirname as dirname31,join as join92,relative as relative10,sep as sep3}from"path";function isRelativeWithin(rel,original){return!rel.startsWith("..")&&rel!==original}function resolveFromCanonicalDir(cwd,agentsDir){let relToAgents=relative10(agentsDir,cwd);if(!isRelativeWithin(relToAgents,cwd))return null;let segments=relToAgents.split(sep3).filter(Boolean);if(segments.length===0)return null;let agentName=segments[0];if(!existsSync75(join92(agentsDir,agentName,"AGENTS.md")))return null;let source=segments.length===1?"exact":"parent";return{agent:agentName,source}}function resolveFromWalkUp(cwd,workspaceRoot){let wsRel=relative10(workspaceRoot,cwd);if(!isRelativeWithin(wsRel,cwd))return null;let current=cwd;while(current!==workspaceRoot&&current!==dirname31(current)){if(existsSync75(join92(current,"AGENTS.md")))return{agent:basename17(current),source:"parent"};current=dirname31(current)}return null}function resolveAgentFromCwd(cwd,workspaceRoot){return resolveFromCanonicalDir(cwd,join92(workspaceRoot,"agents"))??resolveFromWalkUp(cwd,workspaceRoot)??{agent:"genie",source:"default"}}var init_resolve_agent_cwd=()=>{};var import__=__toESM(require_commander(),1),{program,createCommand,createArgument,createOption,CommanderError,InvalidArgumentError,InvalidOptionArgumentError,Command,Argument,Option,Help}=import__.default;init_doctor();import{execFileSync as execFileSync3,spawnSync as spawnSync2}from"child_process";import{existsSync as existsSync21,mkdirSync as mkdirSync11,writeFileSync as writeFileSync8}from"fs";import{homedir as homedir21}from"os";import{join as join26}from"path";var PM2_PROCESS_NAME="genie-serve",HARDENED_DEFAULTS={maxRestarts:50,minUptimeMs:1e4,restartDelayMs:4000,expBackoffRestartDelayMs:100,maxMemory:process.env.GENIE_SERVE_MAX_MEMORY||"4G",killTimeoutMs:60000,logDateFormat:"YYYY-MM-DD HH:mm:ss.SSS"};function getLogsDir(){return join26(homedir21(),".genie","logs")}function ok(msg){process.stdout.write(`genie install: ${msg}
3891
3891
  `)}function note(msg){process.stderr.write(`genie install: ${msg}
3892
3892
  `)}function fail(msg){process.stderr.write(`genie install: ${msg}
3893
- `),process.exit(1)}function which(cmd){try{return execFileSync3("which",[cmd],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}function pm2GetProcess(name){try{let out=execFileSync3("pm2",["jlist"],{encoding:"utf8",timeout:5000,stdio:["ignore","pipe","ignore"]});return JSON.parse(out).find((p)=>p.name===name)??null}catch{return null}}function pm2IsAvailable(){try{return execFileSync3("pm2",["--version"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),!0}catch{return!1}}function pgserveIsAvailable(){return which("pgserve")!==null}function tryPgserveInstall(){if(!pgserveIsAvailable())return note("pgserve binary not found in PATH. Skipping the canonical pgserve hookup. Install with: bun add -g pgserve (then re-run genie install)."),!1;let result2=spawnSync2("pgserve",["install"],{stdio:"inherit"});if(result2.status!==0)return note(`pgserve install exited with code ${result2.status}. Continuing \u2014 genie-serve registration proceeds.`),!1;return!0}function resolveGenieBinary(){let wired=which("genie");if(wired)return wired;return process.argv[1]??"genie"}function getEcosystemConfigPath(){return join26(homedir21(),".genie",`${PM2_PROCESS_NAME}.config.cjs`)}function buildEcosystemConfigSource(geniePath){let logs={out:join26(getLogsDir(),`${PM2_PROCESS_NAME}-out.log`),error:join26(getLogsDir(),`${PM2_PROCESS_NAME}-error.log`)},cfg={name:PM2_PROCESS_NAME,script:geniePath,args:"serve start --headless --no-tui --no-interactive",interpreter:"none",autorestart:!0,max_restarts:HARDENED_DEFAULTS.maxRestarts,min_uptime:HARDENED_DEFAULTS.minUptimeMs,restart_delay:HARDENED_DEFAULTS.restartDelayMs,exp_backoff_restart_delay:HARDENED_DEFAULTS.expBackoffRestartDelayMs,max_memory_restart:HARDENED_DEFAULTS.maxMemory,kill_timeout:HARDENED_DEFAULTS.killTimeoutMs,log_date_format:HARDENED_DEFAULTS.logDateFormat,error_file:logs.error,out_file:logs.out,merge_logs:!0,time:!0};return`// Generated by \`genie install\` \u2014 do not edit by hand.
3893
+ `),process.exit(1)}function which(cmd){try{return execFileSync3("which",[cmd],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}function pm2GetProcess(name){try{let out=execFileSync3("pm2",["jlist"],{encoding:"utf8",timeout:5000,stdio:["ignore","pipe","ignore"]});return JSON.parse(out).find((p)=>p.name===name)??null}catch{return null}}function pm2IsAvailable(){try{return execFileSync3("pm2",["--version"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),!0}catch{return!1}}function pgserveIsAvailable(){return which("pgserve")!==null}function tryPgserveInstall(){if(!pgserveIsAvailable())return note("pgserve binary not found in PATH. Skipping the canonical pgserve hookup. Install with: bun add -g pgserve (then re-run genie install)."),!1;let result2=spawnSync2("pgserve",["install"],{stdio:"inherit"});if(result2.status!==0)return note(`pgserve install exited with code ${result2.status}. Continuing \u2014 genie-serve registration proceeds.`),!1;return!0}function tryPgservePort(){if(!pgserveIsAvailable())return null;let result2=spawnSync2("pgserve",["port"],{encoding:"utf8",timeout:5000});if(result2.status!==0)return null;let port=Number.parseInt((result2.stdout??"").trim(),10);if(!Number.isFinite(port)||port<=0||port>65535)return null;return port}function buildGenieDatabaseUrl(port){return`postgresql://postgres:postgres@localhost:${port}/genie`}function resolveGenieBinary(){let wired=which("genie");if(wired)return wired;return process.argv[1]??"genie"}function getEcosystemConfigPath(){return join26(homedir21(),".genie",`${PM2_PROCESS_NAME}.config.cjs`)}function buildEcosystemConfigSource(geniePath,databaseUrl){let logs={out:join26(getLogsDir(),`${PM2_PROCESS_NAME}-out.log`),error:join26(getLogsDir(),`${PM2_PROCESS_NAME}-error.log`)},cfg={name:PM2_PROCESS_NAME,script:geniePath,args:"serve start --headless --no-tui --no-interactive",interpreter:"none",autorestart:!0,max_restarts:HARDENED_DEFAULTS.maxRestarts,min_uptime:HARDENED_DEFAULTS.minUptimeMs,restart_delay:HARDENED_DEFAULTS.restartDelayMs,exp_backoff_restart_delay:HARDENED_DEFAULTS.expBackoffRestartDelayMs,max_memory_restart:HARDENED_DEFAULTS.maxMemory,kill_timeout:HARDENED_DEFAULTS.killTimeoutMs,log_date_format:HARDENED_DEFAULTS.logDateFormat,error_file:logs.error,out_file:logs.out,merge_logs:!0,time:!0};if(databaseUrl)cfg.env={DATABASE_URL:databaseUrl};return`// Generated by \`genie install\` \u2014 do not edit by hand.
3894
3894
  // Regenerated on every \`genie install\` invocation.
3895
3895
  module.exports = {
3896
3896
  apps: [${JSON.stringify(cfg,null,2)}],
3897
3897
  };
3898
- `}function writeEcosystemConfig(geniePath){let path2=getEcosystemConfigPath();return ensureLogsDir(),writeFileSync8(path2,buildEcosystemConfigSource(geniePath),{mode:420}),path2}function buildPm2StartArgs(geniePath){return["start",writeEcosystemConfig(geniePath),"--update-env"]}function ensureLogsDir(){let dir=getLogsDir();if(!existsSync21(dir))mkdirSync11(dir,{recursive:!0,mode:493})}async function installCommand(options={}){if(!pm2IsAvailable())fail("pm2 not found in PATH. Install with: bun add -g pm2 (or npm i -g pm2)");if(!options.skipPgserve)tryPgserveInstall();let existing=pm2GetProcess(PM2_PROCESS_NAME);if(existing){ok(`already installed (pm2 process "${PM2_PROCESS_NAME}", status=${existing.pm2_env?.status??"unknown"}). Use \`pm2 restart genie-serve\` to pick up code changes.`);return}ensureLogsDir();let geniePath=resolveGenieBinary(),pm2Args=buildPm2StartArgs(geniePath),result2=spawnSync2("pm2",pm2Args,{stdio:"inherit"});if(result2.status!==0)fail(`pm2 start failed (exit ${result2.status}). Logs: ${getLogsDir()}/${PM2_PROCESS_NAME}-error.log`);ok(`installed: pm2 process "${PM2_PROCESS_NAME}" (logs: ${getLogsDir()})`),ok("the genie bridge will now survive shell closure and host reboots (after `pm2 save` + `pm2 startup`).")}init_setup();init_shortcuts();import{existsSync as existsSync24}from"fs";import{homedir as homedir25}from"os";import{join as join30}from"path";async function shortcutsShowCommand(){displayShortcuts();let home=homedir25(),tmuxConf=join30(home,".tmux.conf"),zshrc=join30(home,".zshrc"),bashrc=join30(home,".bashrc");if(console.log("Installation status:"),isShortcutsInstalled(tmuxConf))console.log(" \x1B[32m\u2713\x1B[0m tmux.conf");else console.log(" \x1B[33m-\x1B[0m tmux.conf");let shellRc=existsSync24(zshrc)?zshrc:bashrc;if(isShortcutsInstalled(shellRc))console.log(` \x1B[32m\u2713\x1B[0m ${shellRc.replace(home,"~")}`);else console.log(` \x1B[33m-\x1B[0m ${shellRc.replace(home,"~")}`);console.log(),console.log("Run \x1B[36mgenie shortcuts install\x1B[0m to install shortcuts."),console.log("Run \x1B[36mgenie shortcuts uninstall\x1B[0m to remove shortcuts."),console.log()}async function shortcutsInstallCommand(){await installShortcuts()}async function shortcutsUninstallCommand(){await uninstallShortcuts()}init_esm14();init_claude_settings();init_genie_config2();import{existsSync as existsSync25,lstatSync,rmSync as rmSync2,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir26}from"os";import{join as join31}from"path";var ORCHESTRATION_RULES_PATH=join31(homedir26(),".claude","rules","genie-orchestration.md"),LOCAL_BIN=join31(homedir26(),".local","bin"),SYMLINKS=["genie","term"];function isGenieSymlink(path3){try{if(!existsSync25(path3))return!1;if(!lstatSync(path3).isSymbolicLink())return!1;return!0}catch{return!1}}function removeSymlinks(){let removed=[];for(let name of SYMLINKS){let symlinkPath=join31(LOCAL_BIN,name);if(isGenieSymlink(symlinkPath))try{unlinkSync8(symlinkPath),removed.push(name)}catch{}}return removed}function tryRemoveStep(label,successMsg,fn){console.log(`\x1B[2m${label}\x1B[0m`);try{fn(),console.log(` \x1B[32m+\x1B[0m ${successMsg}`)}catch(error){let message=error instanceof Error?error.message:String(error);console.log(` \x1B[33m!\x1B[0m ${label.replace("...","")} failed: ${message}`)}}function performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir){if(hasHookScript)tryRemoveStep("Removing hook script...","Hook script removed",()=>removeHookScript());if(existingSymlinks.length>0){console.log("\x1B[2mRemoving symlinks...\x1B[0m");let removed=removeSymlinks();if(removed.length>0)console.log(` \x1B[32m+\x1B[0m Removed: ${removed.join(", ")}`)}if(existsSync25(ORCHESTRATION_RULES_PATH))tryRemoveStep("Removing orchestration rules...","Orchestration rules removed (~/.claude/rules/genie-orchestration.md)",()=>unlinkSync8(ORCHESTRATION_RULES_PATH));if(hasGenieDir)tryRemoveStep("Removing genie directory...","Directory removed",()=>rmSync2(genieDir,{recursive:!0,force:!0}))}async function uninstallCommand(){console.log(),console.log("\x1B[1m\x1B[33m Uninstall Genie CLI\x1B[0m"),console.log();let genieDir=getGenieDir(),hasGenieDir=existsSync25(genieDir),hasHookScript=hookScriptExists(),hasOrchestrationRules=existsSync25(ORCHESTRATION_RULES_PATH),existingSymlinks=SYMLINKS.filter((name)=>isGenieSymlink(join31(LOCAL_BIN,name)));if(console.log("\x1B[2mThis will remove:\x1B[0m"),hasHookScript)console.log(" \x1B[31m-\x1B[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)");if(hasOrchestrationRules)console.log(" \x1B[31m-\x1B[0m Orchestration rules (~/.claude/rules/genie-orchestration.md)");if(hasGenieDir)console.log(` \x1B[31m-\x1B[0m Genie directory (${contractPath(genieDir)})`);if(existingSymlinks.length>0)console.log(` \x1B[31m-\x1B[0m Symlinks from ~/.local/bin: ${existingSymlinks.join(", ")}`);if(console.log(),!hasGenieDir&&!hasHookScript&&!hasOrchestrationRules&&existingSymlinks.length===0){console.log("\x1B[33mNothing to uninstall.\x1B[0m"),console.log();return}if(!await esm_default4({message:"Are you sure you want to uninstall Genie CLI?",default:!1})){console.log(),console.log("\x1B[2mUninstall cancelled.\x1B[0m"),console.log();return}console.log(),performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir),console.log(),console.log("\x1B[32m+\x1B[0m Genie CLI uninstalled."),console.log(),console.log("\x1B[2mNote: If you installed via npm/bun, also run:\x1B[0m"),console.log(" \x1B[36mbun remove -g @automagik/genie\x1B[0m"),console.log(" \x1B[2mor\x1B[0m"),console.log(" \x1B[36mnpm uninstall -g @automagik/genie\x1B[0m"),console.log()}init_genie_config2();import{execSync as execSync4,spawn as spawn3}from"child_process";import{chmodSync as chmodSync2,closeSync as closeSync3,copyFileSync as copyFileSync3,existsSync as existsSync26,mkdirSync as mkdirSync14,openSync as openSync3,readFileSync as readFileSync17,readSync as readSync2,readdirSync as readdirSync8,rmSync as rmSync3,statSync as statSync7,writeFileSync as writeFileSync12}from"fs";import{chmod,copyFile,mkdir as mkdir5,unlink as unlink2}from"fs/promises";import{homedir as homedir27}from"os";import{join as join32}from"path";var GENIE_HOME2=process.env.GENIE_HOME||join32(homedir27(),".genie"),GENIE_SRC=join32(GENIE_HOME2,"src"),GENIE_BIN=join32(GENIE_HOME2,"bin"),LOCAL_BIN2=join32(homedir27(),".local","bin"),TRUTHY=new Set(["1","true","yes","on"]),UPDATE_DIAGNOSTIC_SCHEMA_VERSION=1;function log(message){console.log(`\x1B[32m\u25B8\x1B[0m ${message}`)}function success(message){console.log(`\x1B[32m\u2714\x1B[0m ${message}`)}function error(message){console.log(`\x1B[31m\u2716\x1B[0m ${message}`)}function formatDuration2(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function isTruthyEnv(value){return value!==void 0&&TRUTHY.has(value.trim().toLowerCase())}async function withTemporaryEnv(key,value,fn){let previous=process.env[key];process.env[key]=value;try{return await fn()}finally{if(previous===void 0)delete process.env[key];else process.env[key]=previous}}function safeExec(command,timeoutMs=1500){try{return execSync4(command,{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:timeoutMs}).trim()}catch(err){let stdout=err.stdout;if(typeof stdout==="string"&&stdout.trim())return stdout.trim();return""}}function safeRead(path3,maxChars=4000){try{let value=readFileSync17(path3,"utf-8");if(value.length<=maxChars)return value;return value.slice(value.length-maxChars)}catch{return null}}function tailLines(path3,maxBytes=64000,maxLines=200){let fd=null;try{let stat4=statSync7(path3),bytesToRead=Math.min(stat4.size,maxBytes),buffer2=Buffer.alloc(bytesToRead);return fd=openSync3(path3,"r"),readSync2(fd,buffer2,0,bytesToRead,Math.max(0,stat4.size-bytesToRead)),buffer2.toString("utf-8").split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).slice(-maxLines)}catch{return[]}finally{if(fd!==null)try{closeSync3(fd)}catch{}}}function summarizeJsonlSignals(path3){let signals2=new Map;for(let line of tailLines(path3))try{let event=JSON.parse(line),level=typeof event.level==="string"?event.level:"unknown";if(level!=="error"&&level!=="warn")continue;let name=typeof event.event==="string"?event.event:"unknown",key=`${level}:${name}`,existing=signals2.get(key)??{level,event:name,count:0};if(existing.count++,typeof event.timestamp==="string")existing.lastTimestamp=event.timestamp;if(typeof event.error==="string")existing.lastError=event.error;signals2.set(key,existing)}catch{}return[...signals2.values()].sort((a,b2)=>b2.count-a.count).slice(0,10)}async function collectUpdateDiagnostics(ctx,maintenance){let logsDir=join32(GENIE_HOME2,"logs");mkdirSync14(logsDir,{recursive:!0});let generatedAt=new Date().toISOString(),safeStamp=generatedAt.replace(/[:.]/g,"-"),path3=join32(logsDir,`update-diagnostics-${safeStamp}.json`),schedulerLog=join32(logsDir,"scheduler.log"),tuiCrashLog=join32(logsDir,"tui-crash.log"),signals2=summarizeJsonlSignals(schedulerLog),diagnostics={schemaVersion:UPDATE_DIAGNOSTIC_SCHEMA_VERSION,generatedAt,update:ctx,runtime:{platform:process.platform,arch:process.arch,cwd:process.cwd(),node:process.version,bun:(await runCommandSilent("bun",["--version"])).output.trim()||null,npm:(await runCommandSilent("npm",["--version"])).output.trim()||null,genie:{which:(await runCommandSilent("which",["genie"])).output.trim()||null,tuiDisabled:isTruthyEnv(process.env.GENIE_TUI_DISABLE),updateSkipMaintenance:isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)}},paths:{genieHome:GENIE_HOME2,logsDir,servePid:safeRead(join32(GENIE_HOME2,"serve.pid"),200),pgservePort:safeRead(join32(GENIE_HOME2,"pgserve.port"),200),schedulerLog,tuiCrashLog},processSnapshot:{genie:safeExec("ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r | rg -i 'dist/genie.js|/src/genie.ts|pgserve|postgres -D .*\\.genie/data/pgserve|tmux -L genie-tui|bun' || true",2000)||null,tuiTmux:safeExec("tmux -L genie-tui ls 2>/dev/null || true",1000)||null},maintenance:{...maintenance,pgAutostartDisabled:!0,legend:{"[ok]":"healthy","[fix]":"fixed during maintenance","[--]":"skipped/non-blocking","[!!]":"operator action needed; update still completed"}},recentLogSignals:{scheduler:signals2,schedulerTail:tailLines(schedulerLog,32000,80),tuiCrashTail:tailLines(tuiCrashLog,32000,80)}};return writeFileSync12(path3,`${JSON.stringify(diagnostics,null,2)}
3898
+ `}function writeEcosystemConfig(geniePath,databaseUrl){let path2=getEcosystemConfigPath();return ensureLogsDir(),writeFileSync8(path2,buildEcosystemConfigSource(geniePath,databaseUrl),{mode:420}),path2}function buildPm2StartArgs(geniePath,databaseUrl){return["start",writeEcosystemConfig(geniePath,databaseUrl),"--update-env"]}function ensureLogsDir(){let dir=getLogsDir();if(!existsSync21(dir))mkdirSync11(dir,{recursive:!0,mode:493})}async function installCommand(options={}){if(!pm2IsAvailable())fail("pm2 not found in PATH. Install with: bun add -g pm2 (or npm i -g pm2)");let canonicalDatabaseUrl;if(!options.skipPgserve){if(tryPgserveInstall()){let port=tryPgservePort();if(port!==null)canonicalDatabaseUrl=buildGenieDatabaseUrl(port),ok(`canonical pgserve detected; genie-serve will connect to ${canonicalDatabaseUrl}`)}}let existing=pm2GetProcess(PM2_PROCESS_NAME);if(existing){ok(`already installed (pm2 process "${PM2_PROCESS_NAME}", status=${existing.pm2_env?.status??"unknown"}). Use \`pm2 delete genie-serve && genie install\` to refresh the env (e.g. to pick up a new canonical pgserve URL).`);return}ensureLogsDir();let geniePath=resolveGenieBinary(),pm2Args=buildPm2StartArgs(geniePath,canonicalDatabaseUrl),result2=spawnSync2("pm2",pm2Args,{stdio:"inherit"});if(result2.status!==0)fail(`pm2 start failed (exit ${result2.status}). Logs: ${getLogsDir()}/${PM2_PROCESS_NAME}-error.log`);if(ok(`installed: pm2 process "${PM2_PROCESS_NAME}" (logs: ${getLogsDir()})`),canonicalDatabaseUrl)ok(`genie-serve env DATABASE_URL \u2192 ${canonicalDatabaseUrl}`);ok("the genie bridge will now survive shell closure and host reboots (after `pm2 save` + `pm2 startup`).")}init_setup();init_shortcuts();import{existsSync as existsSync24}from"fs";import{homedir as homedir25}from"os";import{join as join30}from"path";async function shortcutsShowCommand(){displayShortcuts();let home=homedir25(),tmuxConf=join30(home,".tmux.conf"),zshrc=join30(home,".zshrc"),bashrc=join30(home,".bashrc");if(console.log("Installation status:"),isShortcutsInstalled(tmuxConf))console.log(" \x1B[32m\u2713\x1B[0m tmux.conf");else console.log(" \x1B[33m-\x1B[0m tmux.conf");let shellRc=existsSync24(zshrc)?zshrc:bashrc;if(isShortcutsInstalled(shellRc))console.log(` \x1B[32m\u2713\x1B[0m ${shellRc.replace(home,"~")}`);else console.log(` \x1B[33m-\x1B[0m ${shellRc.replace(home,"~")}`);console.log(),console.log("Run \x1B[36mgenie shortcuts install\x1B[0m to install shortcuts."),console.log("Run \x1B[36mgenie shortcuts uninstall\x1B[0m to remove shortcuts."),console.log()}async function shortcutsInstallCommand(){await installShortcuts()}async function shortcutsUninstallCommand(){await uninstallShortcuts()}init_esm14();init_claude_settings();init_genie_config2();import{existsSync as existsSync25,lstatSync,rmSync as rmSync2,unlinkSync as unlinkSync8}from"fs";import{homedir as homedir26}from"os";import{join as join31}from"path";var ORCHESTRATION_RULES_PATH=join31(homedir26(),".claude","rules","genie-orchestration.md"),LOCAL_BIN=join31(homedir26(),".local","bin"),SYMLINKS=["genie","term"];function isGenieSymlink(path3){try{if(!existsSync25(path3))return!1;if(!lstatSync(path3).isSymbolicLink())return!1;return!0}catch{return!1}}function removeSymlinks(){let removed=[];for(let name of SYMLINKS){let symlinkPath=join31(LOCAL_BIN,name);if(isGenieSymlink(symlinkPath))try{unlinkSync8(symlinkPath),removed.push(name)}catch{}}return removed}function tryRemoveStep(label,successMsg,fn){console.log(`\x1B[2m${label}\x1B[0m`);try{fn(),console.log(` \x1B[32m+\x1B[0m ${successMsg}`)}catch(error){let message=error instanceof Error?error.message:String(error);console.log(` \x1B[33m!\x1B[0m ${label.replace("...","")} failed: ${message}`)}}function performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir){if(hasHookScript)tryRemoveStep("Removing hook script...","Hook script removed",()=>removeHookScript());if(existingSymlinks.length>0){console.log("\x1B[2mRemoving symlinks...\x1B[0m");let removed=removeSymlinks();if(removed.length>0)console.log(` \x1B[32m+\x1B[0m Removed: ${removed.join(", ")}`)}if(existsSync25(ORCHESTRATION_RULES_PATH))tryRemoveStep("Removing orchestration rules...","Orchestration rules removed (~/.claude/rules/genie-orchestration.md)",()=>unlinkSync8(ORCHESTRATION_RULES_PATH));if(hasGenieDir)tryRemoveStep("Removing genie directory...","Directory removed",()=>rmSync2(genieDir,{recursive:!0,force:!0}))}async function uninstallCommand(){console.log(),console.log("\x1B[1m\x1B[33m Uninstall Genie CLI\x1B[0m"),console.log();let genieDir=getGenieDir(),hasGenieDir=existsSync25(genieDir),hasHookScript=hookScriptExists(),hasOrchestrationRules=existsSync25(ORCHESTRATION_RULES_PATH),existingSymlinks=SYMLINKS.filter((name)=>isGenieSymlink(join31(LOCAL_BIN,name)));if(console.log("\x1B[2mThis will remove:\x1B[0m"),hasHookScript)console.log(" \x1B[31m-\x1B[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)");if(hasOrchestrationRules)console.log(" \x1B[31m-\x1B[0m Orchestration rules (~/.claude/rules/genie-orchestration.md)");if(hasGenieDir)console.log(` \x1B[31m-\x1B[0m Genie directory (${contractPath(genieDir)})`);if(existingSymlinks.length>0)console.log(` \x1B[31m-\x1B[0m Symlinks from ~/.local/bin: ${existingSymlinks.join(", ")}`);if(console.log(),!hasGenieDir&&!hasHookScript&&!hasOrchestrationRules&&existingSymlinks.length===0){console.log("\x1B[33mNothing to uninstall.\x1B[0m"),console.log();return}if(!await esm_default4({message:"Are you sure you want to uninstall Genie CLI?",default:!1})){console.log(),console.log("\x1B[2mUninstall cancelled.\x1B[0m"),console.log();return}console.log(),performUninstall(hasHookScript,existingSymlinks,genieDir,hasGenieDir),console.log(),console.log("\x1B[32m+\x1B[0m Genie CLI uninstalled."),console.log(),console.log("\x1B[2mNote: If you installed via npm/bun, also run:\x1B[0m"),console.log(" \x1B[36mbun remove -g @automagik/genie\x1B[0m"),console.log(" \x1B[2mor\x1B[0m"),console.log(" \x1B[36mnpm uninstall -g @automagik/genie\x1B[0m"),console.log()}init_genie_config2();import{execSync as execSync4,spawn as spawn3}from"child_process";import{chmodSync as chmodSync2,closeSync as closeSync3,copyFileSync as copyFileSync3,existsSync as existsSync26,mkdirSync as mkdirSync14,openSync as openSync3,readFileSync as readFileSync17,readSync as readSync2,readdirSync as readdirSync8,rmSync as rmSync3,statSync as statSync7,writeFileSync as writeFileSync12}from"fs";import{chmod,copyFile,mkdir as mkdir5,unlink as unlink2}from"fs/promises";import{homedir as homedir27}from"os";import{join as join32}from"path";var GENIE_HOME2=process.env.GENIE_HOME||join32(homedir27(),".genie"),GENIE_SRC=join32(GENIE_HOME2,"src"),GENIE_BIN=join32(GENIE_HOME2,"bin"),LOCAL_BIN2=join32(homedir27(),".local","bin"),TRUTHY=new Set(["1","true","yes","on"]),UPDATE_DIAGNOSTIC_SCHEMA_VERSION=1;function log(message){console.log(`\x1B[32m\u25B8\x1B[0m ${message}`)}function success(message){console.log(`\x1B[32m\u2714\x1B[0m ${message}`)}function error(message){console.log(`\x1B[31m\u2716\x1B[0m ${message}`)}function formatDuration2(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function isTruthyEnv(value){return value!==void 0&&TRUTHY.has(value.trim().toLowerCase())}async function withTemporaryEnv(key,value,fn){let previous=process.env[key];process.env[key]=value;try{return await fn()}finally{if(previous===void 0)delete process.env[key];else process.env[key]=previous}}function safeExec(command,timeoutMs=1500){try{return execSync4(command,{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:timeoutMs}).trim()}catch(err){let stdout=err.stdout;if(typeof stdout==="string"&&stdout.trim())return stdout.trim();return""}}function safeRead(path3,maxChars=4000){try{let value=readFileSync17(path3,"utf-8");if(value.length<=maxChars)return value;return value.slice(value.length-maxChars)}catch{return null}}function tailLines(path3,maxBytes=64000,maxLines=200){let fd=null;try{let stat4=statSync7(path3),bytesToRead=Math.min(stat4.size,maxBytes),buffer2=Buffer.alloc(bytesToRead);return fd=openSync3(path3,"r"),readSync2(fd,buffer2,0,bytesToRead,Math.max(0,stat4.size-bytesToRead)),buffer2.toString("utf-8").split(/\r?\n/).map((line)=>line.trim()).filter(Boolean).slice(-maxLines)}catch{return[]}finally{if(fd!==null)try{closeSync3(fd)}catch{}}}function summarizeJsonlSignals(path3){let signals2=new Map;for(let line of tailLines(path3))try{let event=JSON.parse(line),level=typeof event.level==="string"?event.level:"unknown";if(level!=="error"&&level!=="warn")continue;let name=typeof event.event==="string"?event.event:"unknown",key=`${level}:${name}`,existing=signals2.get(key)??{level,event:name,count:0};if(existing.count++,typeof event.timestamp==="string")existing.lastTimestamp=event.timestamp;if(typeof event.error==="string")existing.lastError=event.error;signals2.set(key,existing)}catch{}return[...signals2.values()].sort((a,b2)=>b2.count-a.count).slice(0,10)}async function collectUpdateDiagnostics(ctx,maintenance){let logsDir=join32(GENIE_HOME2,"logs");mkdirSync14(logsDir,{recursive:!0});let generatedAt=new Date().toISOString(),safeStamp=generatedAt.replace(/[:.]/g,"-"),path3=join32(logsDir,`update-diagnostics-${safeStamp}.json`),schedulerLog=join32(logsDir,"scheduler.log"),tuiCrashLog=join32(logsDir,"tui-crash.log"),signals2=summarizeJsonlSignals(schedulerLog),diagnostics={schemaVersion:UPDATE_DIAGNOSTIC_SCHEMA_VERSION,generatedAt,update:ctx,runtime:{platform:process.platform,arch:process.arch,cwd:process.cwd(),node:process.version,bun:(await runCommandSilent("bun",["--version"])).output.trim()||null,npm:(await runCommandSilent("npm",["--version"])).output.trim()||null,genie:{which:(await runCommandSilent("which",["genie"])).output.trim()||null,tuiDisabled:isTruthyEnv(process.env.GENIE_TUI_DISABLE),updateSkipMaintenance:isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)}},paths:{genieHome:GENIE_HOME2,logsDir,servePid:safeRead(join32(GENIE_HOME2,"serve.pid"),200),pgservePort:safeRead(join32(GENIE_HOME2,"pgserve.port"),200),schedulerLog,tuiCrashLog},processSnapshot:{genie:safeExec("ps -axo pid,ppid,pgid,stat,pcpu,pmem,etime,command -r | rg -i 'dist/genie.js|/src/genie.ts|pgserve|postgres -D .*\\.genie/data/pgserve|tmux -L genie-tui|bun' || true",2000)||null,tuiTmux:safeExec("tmux -L genie-tui ls 2>/dev/null || true",1000)||null},maintenance:{...maintenance,pgAutostartDisabled:!0,legend:{"[ok]":"healthy","[fix]":"fixed during maintenance","[--]":"skipped/non-blocking","[!!]":"operator action needed; update still completed"}},recentLogSignals:{scheduler:signals2,schedulerTail:tailLines(schedulerLog,32000,80),tuiCrashTail:tailLines(tuiCrashLog,32000,80)}};return writeFileSync12(path3,`${JSON.stringify(diagnostics,null,2)}
3899
3899
  `),{path:path3,signals:signals2}}async function runCommand(command,args,cwd){return new Promise((resolve4)=>{let output=[],child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"],env:{...process.env,FORCE_COLOR:"1"}});child.stdout?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stdout.write(str2)}),child.stderr?.on("data",(data)=>{let str2=data.toString();output.push(str2),process.stderr.write(str2)}),child.on("close",(code)=>{resolve4({success:code===0,output:output.join("")})}),child.on("error",(err)=>{error(err.message),resolve4({success:!1,output:err.message})})})}async function getGitInfo(cwd){try{let branchResult=await runCommandSilent("git",["rev-parse","--abbrev-ref","HEAD"],cwd),commitResult=await runCommandSilent("git",["rev-parse","--short","HEAD"],cwd),dateResult=await runCommandSilent("git",["log","-1","--format=%ci"],cwd);if(branchResult.success&&commitResult.success&&dateResult.success)return{branch:branchResult.output.trim(),commit:commitResult.output.trim(),commitDate:dateResult.output.trim().split(" ")[0]}}catch{}return null}async function runCommandSilent(command,args,cwd,timeoutMs=4000){return new Promise((resolve4)=>{let output=[],settled=!1,child=spawn3(command,args,{cwd,stdio:["inherit","pipe","pipe"]}),timer2=setTimeout(()=>{if(settled)return;settled=!0,child.kill("SIGTERM"),resolve4({success:!1,output:`Timed out after ${timeoutMs}ms`})},timeoutMs);child.stdout?.on("data",(data)=>{output.push(data.toString())}),child.stderr?.on("data",(data)=>{output.push(data.toString())}),child.on("close",(code)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve4({success:code===0,output:output.join("")})}),child.on("error",(err)=>{if(settled)return;settled=!0,clearTimeout(timer2),resolve4({success:!1,output:err.message})})})}function detectFromBinaryPath(path3){let resolved=path3;try{resolved=__require("fs").realpathSync(path3)}catch{}if(resolved.includes(".bun"))return"bun";if(resolved.includes("node_modules"))return"npm";if(path3===join32(LOCAL_BIN2,"genie")||resolved.startsWith(GENIE_BIN))return"source";return null}async function detectInstallationType(){let whichResult=await runCommandSilent("which",["genie"]);if(whichResult.success){let detected=detectFromBinaryPath(whichResult.output.trim());if(detected)return detected}if(genieConfigExists())try{let config=await loadGenieConfig();if(config.installMethod)return config.installMethod}catch{}if(existsSync26(join32(GENIE_SRC,".git")))return"source";return(await runCommandSilent("which",["bun"])).success?"bun":"npm"}async function updateViaBun(channel){try{__require("fs").unlinkSync(join32(homedir27(),".bun","install","global","bun.lock"))}catch{}if(log(`Updating via bun (channel: ${channel})...`),!(await runCommand("bun",["add","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via bun"),!1;return console.log(),success(`Genie CLI updated via bun (${channel})!`),!0}async function updateViaNpm(channel){if(log(`Updating via npm (channel: ${channel})...`),!(await runCommand("npm",["install","-g",`@automagik/genie@${channel}`])).success)return error("Failed to update via npm"),!1;return console.log(),success(`Genie CLI updated via npm (${channel})!`),!0}async function detectGlobalInstalls(){let found=new Set,[npmResult,bunResult]=await Promise.all([runCommandSilent("npm",["list","-g","@automagik/genie"]),runCommandSilent("bun",["pm","ls","-g"])]);if(npmResult.success&&!npmResult.output.includes("(empty)"))found.add("npm");if(bunResult.success&&bunResult.output.includes("@automagik/genie"))found.add("bun");return found}async function updateSource(){if(!existsSync26(GENIE_SRC))error(`Source install path not found: ${GENIE_SRC}`),console.error(" Detection picked the source-install path, but the directory does not exist."),console.error(" This usually means a stale install hint (config or ~/.genie/src/.git) is"),console.error(" pointing somewhere genuine. Either:"),console.error(` 1. Re-clone the source: git clone https://github.com/automagik-dev/genie ${GENIE_SRC}`),console.error(" 2. Update via package manager instead: genie update --next --via bun"),console.error(" 3. Inspect detection: genie doctor --update-detection"),process.exit(1);if(!existsSync26(join32(GENIE_SRC,".git")))error(`Source install path is not a git checkout: ${GENIE_SRC}`),console.error(` ${GENIE_SRC} exists but has no .git/. Cannot run \`git fetch\` from it.`),console.error(` Either delete ${GENIE_SRC} and re-clone, or update via package manager:`),console.error(" genie update --next --via bun"),process.exit(1);let beforeInfo=await getGitInfo(GENIE_SRC);if(beforeInfo)console.log(`Current: \x1B[2m${beforeInfo.branch}@${beforeInfo.commit} (${beforeInfo.commitDate})\x1B[0m`),console.log();if(log("Fetching latest changes..."),!(await runCommand("git",["fetch","origin"],GENIE_SRC)).success)error("Failed to fetch from origin"),process.exit(1);if(log("Resetting to origin/main..."),!(await runCommand("git",["reset","--hard","origin/main"],GENIE_SRC)).success)error("Failed to reset to origin/main"),process.exit(1);console.log();let afterInfo=await getGitInfo(GENIE_SRC);if(beforeInfo&&afterInfo&&beforeInfo.commit===afterInfo.commit){success("Already up to date!"),console.log();return}if(log("Installing dependencies..."),!(await runCommand("bun",["install"],GENIE_SRC)).success)error("Failed to install dependencies"),process.exit(1);if(console.log(),log("Building..."),!(await runCommand("bun",["run","build"],GENIE_SRC)).success)error("Failed to build"),process.exit(1);console.log(),log("Installing binaries...");try{await mkdir5(GENIE_BIN,{recursive:!0}),await mkdir5(LOCAL_BIN2,{recursive:!0});let binaries=["genie.js","term.js"],names=["genie","term"];for(let i2=0;i2<binaries.length;i2++){let src=join32(GENIE_SRC,"dist",binaries[i2]),binDest=join32(GENIE_BIN,binaries[i2]),linkDest=join32(LOCAL_BIN2,names[i2]);await copyFile(src,binDest),await chmod(binDest,493),await symlinkOrCopy(binDest,linkDest)}for(let legacy of["claudio.js","claudio"]){let legacyBin=join32(GENIE_BIN,legacy),legacyLink=join32(LOCAL_BIN2,legacy);try{await unlink2(legacyBin)}catch{}try{await unlink2(legacyLink)}catch{}}success("Binaries installed")}catch(err){error(`Failed to install binaries: ${err}`),process.exit(1)}if(console.log(),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),success("Genie CLI updated successfully!"),console.log(),afterInfo)console.log(`Version: \x1B[36m${afterInfo.branch}@${afterInfo.commit}\x1B[0m (${afterInfo.commitDate})`),console.log()}async function symlinkOrCopy(src,dest){let{symlink:symlink2,unlink:unlink3}=await import("fs/promises");try{if(existsSync26(dest))await unlink3(dest);await symlink2(src,dest)}catch{await copyFile(src,dest)}}function copyDirSync(src,dest){mkdirSync14(dest,{recursive:!0});for(let entry2 of readdirSync8(src,{withFileTypes:!0})){let srcPath=join32(src,entry2.name),destPath=join32(dest,entry2.name);if(entry2.isDirectory())copyDirSync(srcPath,destPath);else copyFileSync3(srcPath,destPath)}}async function resolveGlobalPkgDir(installType){if(installType==="bun"){let bunPath=join32(homedir27(),".bun","install","global","node_modules","@automagik","genie");if(existsSync26(bunPath))return bunPath}if(installType==="npm"){let npmRootResult=await runCommandSilent("npm",["root","-g"]);if(npmRootResult.success){let npmPath=join32(npmRootResult.output.trim(),"@automagik","genie");if(existsSync26(npmPath))return npmPath}}let bunFallback=join32(homedir27(),".bun","install","global","node_modules","@automagik","genie");if(existsSync26(bunFallback))return bunFallback;let npmRootFallback=await runCommandSilent("npm",["root","-g"]);if(npmRootFallback.success){let npmPath=join32(npmRootFallback.output.trim(),"@automagik","genie");if(existsSync26(npmPath))return npmPath}return null}function updatePluginRegistry(claudePlugins,cacheDir,version){let registryPath=join32(claudePlugins,"installed_plugins.json");try{if(!existsSync26(registryPath))return;let registry=JSON.parse(readFileSync17(registryPath,"utf-8")),entries=registry.plugins?.["genie@automagik"];if(!Array.isArray(entries))return;for(let entry2 of entries)if(entry2.scope==="user")entry2.installPath=cacheDir,entry2.version=version,entry2.lastUpdated=new Date().toISOString();writeFileSync12(registryPath,JSON.stringify(registry,null,2))}catch(err){log(`Registry update failed (non-fatal): ${err}`)}}function syncTmuxConf(tmuxScriptsSrc){mkdirSync14(GENIE_HOME2,{recursive:!0});let tmuxConfSrc=join32(tmuxScriptsSrc,"genie.tmux.conf"),tmuxConfDest=join32(GENIE_HOME2,"tmux.conf");if(existsSync26(tmuxConfSrc))try{copyFileSync3(tmuxConfSrc,tmuxConfDest),success(`Installed tmux config to ${tmuxConfDest}`);try{let{tmuxBin:tmuxBin2}=(init_ensure_tmux(),__toCommonJS(exports_ensure_tmux));execSync4(`${tmuxBin2()} -L genie source-file '${tmuxConfDest}'`,{stdio:"ignore"}),success("Reloaded genie tmux server configuration")}catch{}}catch{}let tuiConfSrc=join32(tmuxScriptsSrc,"tui-tmux.conf"),tuiConfDest=join32(GENIE_HOME2,"tui-tmux.conf");if(existsSync26(tuiConfSrc))try{copyFileSync3(tuiConfSrc,tuiConfDest),success(`Installed TUI tmux config to ${tuiConfDest}`)}catch{}let themeSrc=join32(tmuxScriptsSrc,".generated.theme.conf"),themeDest=join32(GENIE_HOME2,".generated.theme.conf");if(existsSync26(themeSrc))try{copyFileSync3(themeSrc,themeDest),success(`Installed tmux theme to ${themeDest}`)}catch{}let osc52Src=join32(tmuxScriptsSrc,"osc52-copy.sh"),osc52Dest=join32(GENIE_HOME2,"scripts","osc52-copy.sh");if(existsSync26(osc52Src))try{copyFileSync3(osc52Src,osc52Dest),chmodSync2(osc52Dest,493),success(`Installed OSC 52 clipboard helper to ${osc52Dest}`)}catch{}}function syncTmuxScripts(globalPkgDir){let tmuxScriptsSrc=join32(globalPkgDir,"scripts","tmux");if(!existsSync26(tmuxScriptsSrc))return;let scriptsDir=join32(GENIE_HOME2,"scripts");mkdirSync14(scriptsDir,{recursive:!0});let scriptCount=0;for(let entry2 of readdirSync8(tmuxScriptsSrc))if(entry2.endsWith(".sh")||entry2==="genie.tmux.conf"||entry2==="tui-tmux.conf"||entry2===".generated.theme.conf"){let src=join32(tmuxScriptsSrc,entry2),dest=join32(scriptsDir,entry2);copyFileSync3(src,dest);try{chmodSync2(dest,entry2.endsWith(".sh")?493:420)}catch{}scriptCount++}if(scriptCount>0)success(`Refreshed ${scriptCount} tmux scripts at ${scriptsDir}`);syncTmuxConf(tmuxScriptsSrc)}function syncMarketplaceVersion(claudePlugins,version){let marketplacePath=join32(claudePlugins,"marketplaces","automagik",".claude-plugin","marketplace.json");try{if(!existsSync26(marketplacePath))return;let data=JSON.parse(readFileSync17(marketplacePath,"utf-8"));if(Array.isArray(data.plugins)){for(let plugin of data.plugins)if(plugin.name==="genie")plugin.version=version}writeFileSync12(marketplacePath,JSON.stringify(data,null,2)),success(`Updated marketplace.json to v${version}`)}catch(err){log(`Marketplace version update failed (non-fatal): ${err}`)}}function syncPluginPackageVersion(claudePlugins,version){let pkgPath=join32(claudePlugins,"marketplaces","automagik","plugins","genie","package.json");try{if(!existsSync26(pkgPath))return;let data=JSON.parse(readFileSync17(pkgPath,"utf-8"));data.version=version,writeFileSync12(pkgPath,JSON.stringify(data,null,2)),success(`Updated plugin package.json to v${version}`)}catch(err){log(`Plugin package.json update failed (non-fatal): ${err}`)}}function syncSkillsSymlink(claudePlugins,version){let skillsLink=join32(claudePlugins,"marketplaces","automagik","plugins","genie","skills"),cacheSkills=join32("..","..","..","..","cache","automagik","genie",version,"skills");try{let{symlinkSync,unlinkSync:unlinkSync9,lstatSync:lstatSync2}=__require("fs");try{lstatSync2(skillsLink),unlinkSync9(skillsLink)}catch{}symlinkSync(cacheSkills,skillsLink),success(`Skills symlink \u2192 cache/${version}/skills`)}catch(err){log(`Skills symlink update failed (non-fatal): ${err}`)}}async function syncPlugin(installType){log("Syncing Claude Code plugin...");let globalPkgDir=await resolveGlobalPkgDir(installType);if(!globalPkgDir)return log("Could not find installed package \u2014 skipping plugin sync"),{skippedReason:"installed package not found"};let pluginSrc=join32(globalPkgDir,"plugins","genie");if(!existsSync26(pluginSrc))return log("Plugin source not found in package \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"plugin source not found in package"};let version;try{version=JSON.parse(readFileSync17(join32(globalPkgDir,"package.json"),"utf-8")).version}catch{return log("Could not read package version \u2014 skipping plugin sync"),{globalPkgDir,skippedReason:"could not read package version"}}let claudePlugins=join32(homedir27(),".claude","plugins"),cacheDir=join32(claudePlugins,"cache","automagik","genie",version);try{if(existsSync26(cacheDir))rmSync3(cacheDir,{recursive:!0,force:!0});copyDirSync(pluginSrc,cacheDir);let skillsSrc=join32(globalPkgDir,"skills");if(existsSync26(skillsSrc)&&!existsSync26(join32(cacheDir,"skills")))copyDirSync(skillsSrc,join32(cacheDir,"skills"))}catch(err){return error(`Failed to copy plugin: ${err}`),{version,globalPkgDir,cacheDir,skippedReason:`failed to copy plugin: ${err}`}}return updatePluginRegistry(claudePlugins,cacheDir,version),syncMarketplaceVersion(claudePlugins,version),syncPluginPackageVersion(claudePlugins,version),syncSkillsSymlink(claudePlugins,version),syncTmuxScripts(globalPkgDir),success(`Plugin synced to v${version}`),{version,globalPkgDir,cacheDir}}async function resolveChannel(options){if(options.next)return"next";if(options.stable)return"latest";if(genieConfigExists())try{let config=await loadGenieConfig();if(config.updateChannel)return config.updateChannel}catch{}return"latest"}async function persistChannel(channel){try{let config=await loadGenieConfig();config.updateChannel=channel,await saveGenieConfig(config)}catch{}}async function updateCommand(options={}){console.log(),console.log("\x1B[1m\uD83E\uDDDE Genie CLI Update\x1B[0m"),console.log("\x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m"),console.log();let channel=await resolveChannel(options);if(options.next||options.stable)await persistChannel(channel);let installType=await detectInstallationType();if(log(`Detected installation: ${installType}`),log(`Channel: ${channel}${channel==="next"?" (dev builds)":" (stable)"}`),console.log(),installType==="unknown")error("No Genie CLI installation found"),console.log(),console.log("Install method not configured. Please reinstall genie:"),console.log("\x1B[36m curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash\x1B[0m"),console.log(),process.exit(1);if(installType==="source"){await updateSource();return}let globalInstalls=await detectGlobalInstalls(),primaryMethod=installType;if(!(primaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))process.exit(1);let secondaryMethod=primaryMethod==="bun"?"npm":"bun";if(globalInstalls.has(secondaryMethod)){if(console.log(),log(`Also updating ${secondaryMethod}-global install...`),!(secondaryMethod==="bun"?await updateViaBun(channel):await updateViaNpm(channel)))error(`Secondary update via ${secondaryMethod} failed (non-blocking)`)}let plugin=await syncPlugin(installType);await runPostUpdateMaintenanceSafe(options,{channel,installType,primaryMethod,globalInstalls:[...globalInstalls].sort(),plugin})}function printPostUpdateMaintenanceIntro(){console.log(),log("Running post-update maintenance..."),console.log(" Purpose: make first launch after update fast and collect upgrade health signals."),console.log(" Checks: runtime partitions, watchdog status, session backfill drift, zombie rows, team config orphans."),console.log(" PG policy: read-only; uses an already-running pgserve when available and will not auto-start it."),console.log(" Legend: [ok]=healthy, [fix]=fixed, [--]=skipped/non-blocking, [!!]=operator action needed.")}async function runMaintenanceWithCapturedLines(maintenanceLines){let{runPostUpdateMaintenance:runPostUpdateMaintenance2}=await Promise.resolve().then(() => (init_doctor(),exports_doctor));await withTemporaryEnv("GENIE_PG_NO_AUTOSTART","1",()=>runPostUpdateMaintenance2({log:(line)=>{maintenanceLines.push(line),console.log(line)}}))}function printDiagnosticsSummary(diagnostics){if(log("Post-update diagnostics captured."),console.log(` Report: ${diagnostics.path}`),console.log(" Include this file when opening a GitHub issue; it contains install metadata, step output,"),console.log(" local process state, and recent scheduler/TUI log signals."),diagnostics.signals.length===0)return;console.log(" Recent scheduler signals:");for(let signal of diagnostics.signals.slice(0,3)){let errorDetail=signal.lastError?` \u2014 ${signal.lastError}`:"";console.log(` ${signal.level}:${signal.event} \xD7${signal.count}${errorDetail}`)}}async function capturePostUpdateDiagnostics(diagnosticsCtx,maintenance){if(!diagnosticsCtx)return;try{let diagnostics=await collectUpdateDiagnostics(diagnosticsCtx,maintenance);printDiagnosticsSummary(diagnostics)}catch(err){let msg=err instanceof Error?err.message:String(err);log(`Post-update diagnostics capture failed (non-fatal): ${msg}`)}}async function runPostUpdateMaintenanceSafe(options={},diagnosticsCtx){if(options.skipMaintenance||isTruthyEnv(process.env.GENIE_UPDATE_SKIP_MAINTENANCE)){log("Skipping post-update maintenance (requested).");return}let startedAt=Date.now(),maintenanceLines=[],outcome="completed",maintenanceError;try{printPostUpdateMaintenanceIntro(),await runMaintenanceWithCapturedLines(maintenanceLines),success(`Post-update maintenance complete (${formatDuration2(Date.now()-startedAt)}).`)}catch(err){outcome="failed",maintenanceError=err instanceof Error?err.message:String(err),error(`Post-update maintenance skipped: ${maintenanceError}`)}await capturePostUpdateDiagnostics(diagnosticsCtx,{outcome,durationMs:Date.now()-startedAt,lines:maintenanceLines,error:maintenanceError})}init_version();init_trust();init_trust();init_trust();import{execSync as execSync5}from"child_process";import{existsSync as existsSync29,mkdirSync as mkdirSync15,readFileSync as readFileSync20,renameSync as renameSync5,writeFileSync as writeFileSync13}from"fs";import{dirname as dirname10,isAbsolute,join as join34,resolve as resolve5}from"path";function resolveOriginUrl(repoRoot){try{return execSync5("git config --get remote.origin.url",{cwd:repoRoot,encoding:"utf-8"}).trim()||null}catch{return null}}function findRepoRoot(start){let current=start;while(current!=="/"){if(existsSync29(join34(current,".git")))return current;current=dirname10(current)}return null}function writeTrustFile(path3,file){let dir=dirname10(path3);if(!existsSync29(dir))mkdirSync15(dir,{recursive:!0});let sorted={version:1,entries:[...file.entries].sort((a,b2)=>a.path.localeCompare(b2.path))},serialized=`${JSON.stringify(sorted,null,2)}
3900
3900
  `,tmpPath=`${path3}.tmp.${process.pid}`;writeFileSync13(tmpPath,serialized,"utf-8"),renameSync5(tmpPath,path3)}async function trustAction(target,options){let trustPath=defaultTrustPath();if(!target){let current2=readTrustFile(trustPath);if(current2.entries.length===0){console.log(`(trust file empty: ${trustPath})`);return}console.log(`Trusted hooks (${current2.entries.length}, from ${trustPath}):`);for(let entry2 of current2.entries){let scope2=entry2.scope==="repo"?`repo:${entry2.repoRemoteUrl??"?"}`:entry2.scope;if(console.log(` ${entry2.path}`),console.log(` scope: ${scope2}`),console.log(` sha256: ${entry2.sha256}`),console.log(` trusted: ${entry2.trustedAt}`),entry2.capabilities&&entry2.capabilities.length>0)console.log(` caps: ${entry2.capabilities.join(", ")}`);if(entry2.note)console.log(` note: ${entry2.note}`)}return}let filePath=isAbsolute(target)?target:resolve5(process.cwd(),target);if(!existsSync29(filePath))console.error(`Error: file not found: ${filePath}`),process.exit(1);if(!filePath.endsWith(".ts"))console.error(`Error: trust target must be a .ts file: ${filePath}`),process.exit(1);let scope,repoRemoteUrl;if(options.repo){scope="repo";let repoRoot=findRepoRoot(dirname10(filePath));if(!repoRoot)console.error(`Error: --repo passed but no .git directory found above ${filePath}`),process.exit(1);let remote=resolveOriginUrl(repoRoot);if(!remote)console.error(`Error: --repo passed but ${repoRoot} has no remote.origin.url`),process.exit(1);repoRemoteUrl=remote}else if(options.team)scope="team";else scope="global";let sha=sha256OfFile(filePath),source=readFileSync20(filePath,"utf-8"),capabilities=parseCapabilities(source);if(console.log(`About to trust: ${filePath}`),console.log(` scope: ${scope==="repo"?`repo:${repoRemoteUrl}`:scope}`),console.log(` sha256: ${sha}`),capabilities.length>0)console.log(` caps: ${capabilities.join(", ")}`);if(options.note)console.log(` note: ${options.note}`);if(!options.yes&&process.stdin.isTTY){process.stdout.write("Confirm? (y/N) ");let buf=Buffer.alloc(8),read=0;try{read=(await import("fs")).readSync(0,buf,0,buf.length,null)}catch{console.error("Error: cannot read confirmation; pass --yes to trust non-interactively"),process.exit(1)}let answer=buf.subarray(0,read).toString().trim().toLowerCase();if(answer!=="y"&&answer!=="yes")console.log("Aborted."),process.exit(1)}let current=readTrustFile(trustPath),newEntry={path:filePath,sha256:sha,scope,repoRemoteUrl,trustedAt:new Date().toISOString(),note:options.note,capabilities:capabilities.length>0?capabilities:void 0},next={version:1,entries:[...current.entries.filter((e)=>e.path!==filePath),newEntry]};writeTrustFile(trustPath,next),console.log(`Trusted ${filePath} \u2192 ${trustPath}`)}function registerHookTrustCommand(parent){parent.command("trust [path]").description("Add a .ts hook file to the trust allowlist (or list current entries when [path] is omitted)").option("--repo","Scope to current repo (pinned to remote.origin.url)").option("--global","Scope globally (default)").option("--team <name>","Scope to a specific team directory").option("--note <text>","Free-form note saved with the entry").option("--yes","Skip the interactive confirmation prompt").action(trustAction)}import{appendFileSync as appendFileSync3,mkdirSync as mkdirSync16,statSync as statSync8}from"fs";import{connect as connect2}from"net";import{homedir as homedir29}from"os";import{dirname as dirname11,join as join35}from"path";function defaultSocketPath(){if(process.env.GENIE_HOOK_SOCK)return process.env.GENIE_HOOK_SOCK;let home=process.env.GENIE_HOME??join35(homedir29(),".genie");return join35(home,"hook.sock")}function fallbackLogPath2(){let home=process.env.GENIE_HOME??join35(homedir29(),".genie");return join35(home,"hook-fallback.log")}var MAX_FRAME_BYTES=1048576,FALLBACK_LOG_MAX_BYTES=104857600,DEFAULT_TIMEOUT_MS=5000;function readStdinSync(){let chunks=[],total=0,fd=0,buf=Buffer.alloc(65536);while(!0){let n;try{n=__require("fs").readSync(fd,buf,0,buf.length,null)}catch{break}if(n===0)break;if(chunks.push(Buffer.from(buf.subarray(0,n))),total+=n,total>MAX_FRAME_BYTES)break}return Buffer.concat(chunks,Math.min(total,MAX_FRAME_BYTES))}function summarizePayload(payload){try{let obj=JSON.parse(payload.toString("utf-8")),event=typeof obj.hook_event_name==="string"?obj.hook_event_name:null,tool=typeof obj.tool_name==="string"?obj.tool_name:null,command=null,ti=obj.tool_input;if(ti&&typeof ti.command==="string")command=ti.command.split(`
3901
3901
  `)[0].slice(0,256);return{event,tool,command}}catch{return{event:null,tool:null,command:null}}}function appendFallback(record){let path3=fallbackLogPath2();try{mkdirSync16(dirname11(path3),{recursive:!0});let writeFresh=!1;try{if(statSync8(path3).size>=FALLBACK_LOG_MAX_BYTES)writeFresh=!0}catch{}let line=`${JSON.stringify(record)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260501.2",
3
+ "version": "4.260501.4",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260501.2",
3
+ "version": "4.260501.4",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260501.2",
3
+ "version": "4.260501.4",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",