@automagik/genie 4.260429.21 → 4.260429.22
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
|
@@ -306,7 +306,7 @@ COLOR=$(${bin} display-message -p -t "$PANE_ID" '#{@genie_color}' 2>/dev/null)
|
|
|
306
306
|
${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
307
307
|
`),chmodSync2(PANE_COLOR_SCRIPT,493)}async function applyPaneColor(paneId,color,windowId){let hex=TMUX_COLOR_MAP[color]??TMUX_COLOR_MAP.blue;try{ensurePaneColorScript(),await executeTmux2(`set-option -p -t '${paneId}' @genie_color '${hex}'`);try{let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));await(await getConnection2())`UPDATE executors SET pane_color = ${hex} WHERE tmux_pane_id = ${paneId}`}catch{}if(windowId)await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function rehydratePaneColorHook(windowId){try{ensurePaneColorScript(),await executeTmux2(`set-hook -w -t '${windowId}' pane-focus-in "run-shell '${PANE_COLOR_SCRIPT} #{pane_id}'"`)}catch{}}async function resolveRepoSession(repoPath){let derived=basename(repoPath);try{let sessions=await listSessions(),exact=sessions.find((s)=>s.name===derived);if(exact)return exact.name;if(process.env.TMUX)try{let name=(await executeTmux2("display-message -p '#{session_name}'")).trim();if(name)return name}catch{}let partial=sessions.find((s)=>s.name.includes(derived));if(partial)return partial.name}catch{}return derived}function isTmuxSocketAlive(socketName){if(!socketName)return!1;let uid=process.getuid?.()??501;return existsSync9(join12(`/tmp/tmux-${uid}`,socketName))}async function isTmuxServerReachable(socketName){if(!socketName)return!1;if(!isTmuxSocketAlive(socketName))return!1;try{let{execSync:execSync2}=await import("child_process");return execSync2(`${tmuxBin()} -L ${shellQuote(socketName)} list-sessions -F ''`,{stdio:["ignore","ignore","ignore"],timeout:2000}),!0}catch{return!1}}async function isPaneAlive(paneId){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{return(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_dead}'`)).trim()==="0"}catch(error){let message=error instanceof Error?error.message:String(error);if(message.includes("no server running")||message.includes("server exited")||message.includes("error connecting"))throw new TmuxUnreachableError(message);return!1}}async function isPaneProcessRunning(paneId,processName,execSyncFn){if(!paneId||paneId==="inline")return!1;if(!/^%\d+$/.test(paneId))return!1;try{let panePid=(await executeTmux2(`display-message -t '${paneId}' -p '#{pane_pid}'`)).trim();if(!panePid||!/^\d+$/.test(panePid))return!1;return(execSyncFn??(await import("child_process")).execSync)(`pgrep -la -P ${panePid} 2>/dev/null; for cpid in $(pgrep -P ${panePid} 2>/dev/null); do pgrep -la -P "$cpid" 2>/dev/null; done; true`,{encoding:"utf-8",timeout:5000}).toLowerCase().includes(processName.toLowerCase())}catch{return!1}}async function killWindow(sessionName,windowName){try{return await executeTmux2(`kill-window -t ${shellQuote(`${sessionName}:${windowName}`)}`),!0}catch{return!1}}var TMUX_COLOR_NAMES,TMUX_COLOR_MAP,PANE_COLOR_SCRIPT,TmuxUnreachableError;var init_tmux=__esm(()=>{init_genie_tokens();init_ensure_tmux();init_team_lead_command();TMUX_COLOR_NAMES=["red","blue","green","yellow","purple","orange","pink","cyan"],TMUX_COLOR_MAP=Object.fromEntries(TMUX_COLOR_NAMES.map((name,i2)=>[name,rotateHue(palette.accent,i2*45)])),PANE_COLOR_SCRIPT=`${__require("os").homedir()}/.genie/tmux-pane-color.sh`;TmuxUnreachableError=class TmuxUnreachableError extends Error{constructor(message){super(message);this.name="TmuxUnreachableError"}}});var exports_agent_registry={};__export(exports_agent_registry,{update:()=>update,unregister:()=>unregister,setCurrentExecutor:()=>setCurrentExecutor,saveTemplate:()=>saveTemplate,removeSubPane:()=>removeSubPane,register:()=>register,reconcileStaleSpawns:()=>reconcileStaleSpawns,listTemplates:()=>listTemplates,listForRender:()=>listForRender,listExhaustedZombies:()=>listExhaustedZombies,listAgentsForRender:()=>listAgentsForRender,listAgents:()=>listAgents,list:()=>list,getTeamLeadEntry:()=>getTeamLeadEntry,getPane:()=>getPane,getElapsedTime:()=>getElapsedTime,getAgentEffectiveState:()=>getAgentEffectiveState,getAgentByName:()=>getAgentByName,getAgent:()=>getAgent,get:()=>get,findOrCreateAgent:()=>findOrCreateAgent,findByWindow:()=>findByWindow,findByTask:()=>findByTask,findByPane:()=>findByPane,filterBySession:()=>filterBySession,auditAgentKind:()=>auditAgentKind,archiveExhaustedZombies:()=>archiveExhaustedZombies,addSubPane:()=>addSubPane});import{createHash,randomUUID}from"crypto";function resolveWorkerSocketName(){return process.env.GENIE_TMUX_SOCKET||"genie"}function ts(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToAgent(r){let agent={id:r.id,paneId:r.pane_id,session:r.session,worktree:r.worktree??null,startedAt:ts(r.started_at),state:r.state,lastStateChange:ts(r.last_state_change),repoPath:r.repo_path};if(r.task_id!=null)agent.taskId=r.task_id;if(r.task_title!=null)agent.taskTitle=r.task_title;if(r.wish_slug!=null)agent.wishSlug=r.wish_slug;if(r.group_number!=null)agent.groupNumber=r.group_number;if(r.window_name!=null)agent.windowName=r.window_name;if(r.window_id!=null)agent.windowId=r.window_id;if(r.role!=null)agent.role=r.role;if(r.custom_name!=null)agent.customName=r.custom_name;if(r.sub_panes!=null){let sp=typeof r.sub_panes==="string"?JSON.parse(r.sub_panes):r.sub_panes;if(Array.isArray(sp)&&sp.length>0)agent.subPanes=sp}if(r.provider!=null)agent.provider=r.provider;if(r.transport!=null)agent.transport=r.transport;if(r.skill!=null)agent.skill=r.skill;if(r.team!=null)agent.team=r.team;if(r.tmux_window!=null)agent.window=r.tmux_window;if(r.native_agent_id!=null)agent.nativeAgentId=r.native_agent_id;if(r.native_color!=null)agent.nativeColor=r.native_color;if(r.native_team_enabled)agent.nativeTeamEnabled=r.native_team_enabled;if(r.parent_session_id!=null)agent.parentSessionId=r.parent_session_id;if(r.suspended_at!=null)agent.suspendedAt=ts(r.suspended_at);if(r.auto_resume!=null)agent.autoResume=r.auto_resume;if(r.resume_attempts!=null)agent.resumeAttempts=r.resume_attempts;if(r.last_resume_attempt!=null)agent.lastResumeAttempt=ts(r.last_resume_attempt);if(r.max_resume_attempts!=null)agent.maxResumeAttempts=r.max_resume_attempts;if(r.pane_color!=null)agent.paneColor=r.pane_color;if(agent.currentExecutorId=r.current_executor_id??null,agent.reportsTo=r.reports_to??null,agent.title=r.title??null,r.kind!=null)agent.kind=r.kind;return agent}function rowToTemplate(r){let tpl={id:r.id,provider:r.provider,team:r.team,cwd:r.cwd,lastSpawnedAt:ts(r.last_spawned_at)};if(r.role!=null)tpl.role=r.role;if(r.skill!=null)tpl.skill=r.skill;if(r.extra_args!=null){let ea=typeof r.extra_args==="string"?JSON.parse(r.extra_args):r.extra_args;if(Array.isArray(ea)&&ea.length>0)tpl.extraArgs=ea}if(r.native_team_enabled)tpl.nativeTeamEnabled=r.native_team_enabled;return tpl}function shortProjectHash(repoPath){return createHash("sha1").update(repoPath).digest("hex").slice(0,8)}function buildProjectTeamLeadEntryId(teamName,session,repoPath){return`team-lead:${session}:${shortProjectHash(repoPath)}:${teamName}`}function buildSessionTeamLeadEntryId(teamName,session){return`team-lead:${session}:${teamName}`}function buildLegacyTeamLeadEntryId(teamName){return`team-lead:${teamName}`}async function register(agent){let sql=await getConnection(),now=new Date().toISOString();if(agent.team){let existing=await sql`
|
|
308
308
|
SELECT team FROM agents WHERE id = ${agent.id} LIMIT 1
|
|
309
|
-
`;if(existing.length>0&&existing[0].team!==null&&existing[0].team!==agent.team)throw Error(`register: cross-team id collision \u2014 agent id "${agent.id}" already exists in team "${existing[0].team}", refusing to re-register under team "${agent.team}". Unregister the stale row first, or pick a different id/suffix for this spawn.`)}await sql`INSERT INTO agents (id, pane_id, session, worktree, task_id, task_title, wish_slug, group_number, started_at, state, last_state_change, repo_path, window_name, window_id, role, custom_name, sub_panes, provider, transport, skill, team, tmux_window, native_agent_id, native_color, native_team_enabled, parent_session_id, suspended_at, auto_resume, resume_attempts, last_resume_attempt, max_resume_attempts, pane_color) VALUES (${agent.id}, ${agent.paneId}, ${agent.session}, ${agent.worktree??null}, ${agent.taskId??null}, ${agent.taskTitle??null}, ${agent.wishSlug??null}, ${agent.groupNumber??null}, ${agent.startedAt??now}, ${agent.state??"spawning"}, ${agent.lastStateChange??now}, ${agent.repoPath}, ${agent.windowName??null}, ${agent.windowId??null}, ${agent.role??null}, ${agent.customName??null}, ${sql.json(agent.subPanes??[])}, ${agent.provider??null}, ${agent.transport??"tmux"}, ${agent.skill??null}, ${agent.team??null}, ${agent.window??null}, ${agent.nativeAgentId??null}, ${agent.nativeColor??null}, ${agent.nativeTeamEnabled??!1}, ${agent.parentSessionId??null}, ${agent.suspendedAt??null}, ${agent.autoResume??!
|
|
309
|
+
`;if(existing.length>0&&existing[0].team!==null&&existing[0].team!==agent.team)throw Error(`register: cross-team id collision \u2014 agent id "${agent.id}" already exists in team "${existing[0].team}", refusing to re-register under team "${agent.team}". Unregister the stale row first, or pick a different id/suffix for this spawn.`)}await sql`INSERT INTO agents (id, pane_id, session, worktree, task_id, task_title, wish_slug, group_number, started_at, state, last_state_change, repo_path, window_name, window_id, role, custom_name, sub_panes, provider, transport, skill, team, tmux_window, native_agent_id, native_color, native_team_enabled, parent_session_id, suspended_at, auto_resume, resume_attempts, last_resume_attempt, max_resume_attempts, pane_color) VALUES (${agent.id}, ${agent.paneId}, ${agent.session}, ${agent.worktree??null}, ${agent.taskId??null}, ${agent.taskTitle??null}, ${agent.wishSlug??null}, ${agent.groupNumber??null}, ${agent.startedAt??now}, ${agent.state??"spawning"}, ${agent.lastStateChange??now}, ${agent.repoPath}, ${agent.windowName??null}, ${agent.windowId??null}, ${agent.role??null}, ${agent.customName??null}, ${sql.json(agent.subPanes??[])}, ${agent.provider??null}, ${agent.transport??"tmux"}, ${agent.skill??null}, ${agent.team??null}, ${agent.window??null}, ${agent.nativeAgentId??null}, ${agent.nativeColor??null}, ${agent.nativeTeamEnabled??!1}, ${agent.parentSessionId??null}, ${agent.suspendedAt??null}, ${agent.autoResume??!1}, ${agent.resumeAttempts??0}, ${agent.lastResumeAttempt??null}, ${agent.maxResumeAttempts??3}, ${agent.paneColor??null}) ON CONFLICT (id) DO UPDATE SET pane_id = EXCLUDED.pane_id, session = EXCLUDED.session, state = EXCLUDED.state, last_state_change = EXCLUDED.last_state_change, team = COALESCE(agents.team, EXCLUDED.team), role = COALESCE(agents.role, EXCLUDED.role), custom_name = COALESCE(agents.custom_name, EXCLUDED.custom_name), updated_at = now()`}async function unregister(id){await(await getConnection())`DELETE FROM agents WHERE id = ${id}`}async function get(id){let rows=await(await getConnection())`SELECT * FROM agents WHERE id = ${id}`;return rows.length>0?rowToAgent(rows[0]):null}async function list(){return(await(await getConnection())`SELECT * FROM agents`).map(rowToAgent)}function dedupeShadowRows(rows,opts){let groups=new Map;for(let r of rows){let k=opts.keyFor(r),arr=groups.get(k);if(arr)arr.push(r);else groups.set(k,[r])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b)=>{let aExec=opts.hasExecutor(a)?1:0,bExec=opts.hasExecutor(b)?1:0;if(aExec!==bExec)return bExec-aExec;return opts.startedAt(b).localeCompare(opts.startedAt(a))}),out.push(arr[0])}return out}function shadowKey(customName,id,team){return`${customName??id}\x00${team??""}`}async function listForRender(){let all=await list();return dedupeShadowRows(all,{keyFor:(a)=>shadowKey(a.customName,a.id,a.team),hasExecutor:(a)=>a.currentExecutorId!=null,startedAt:(a)=>a.startedAt})}async function reconcileStaleSpawns(thresholdSeconds=60){try{let sql=await getConnection(),rows=await sql`
|
|
310
310
|
UPDATE agents
|
|
311
311
|
SET state = 'error', last_state_change = now()
|
|
312
312
|
WHERE state = 'spawning'
|
|
@@ -422,7 +422,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
422
422
|
WHERE (${includeArchived} OR state IS DISTINCT FROM 'archived')
|
|
423
423
|
`;return rows.map(rowToAgentIdentity)}async function listAgentsForRender(filters){let all=await listAgents(filters);return dedupeShadowRows(all,{keyFor:(a)=>shadowKey(a.customName,a.id,a.team),hasExecutor:(a)=>a.currentExecutorId!=null,startedAt:(a)=>a.startedAt})}async function auditAgentKind(){let rows=await(await getConnection())`
|
|
424
424
|
SELECT id, kind, reports_to FROM agents
|
|
425
|
-
`,drifted=[];for(let row of rows){let expected=row.id.startsWith("dir:")||row.reports_to===null?"permanent":"task";if(row.kind!==expected)drifted.push({id:row.id,kind:row.kind,expected})}for(let drift of drifted)recordAuditEvent("worker",drift.id,"agents.kind.audit_drift","auditor",{stored:drift.kind,expected:drift.expected}).catch(()=>{});return{total:rows.length,drifted}}var DEAD_PANE_ZOMBIE_TTL_HOURS=24;var init_agent_registry=__esm(()=>{init_audit();init_db();init_tmux()});function ts2(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToExecutor(r){return{id:r.id,agentId:r.agent_id,provider:r.provider,transport:r.transport,pid:r.pid,tmuxSession:r.tmux_session,tmuxPaneId:r.tmux_pane_id,tmuxWindow:r.tmux_window,tmuxWindowId:r.tmux_window_id,claudeSessionId:r.claude_session_id,state:r.state,metadata:typeof r.metadata==="string"?JSON.parse(r.metadata):r.metadata??{},worktree:r.worktree,repoPath:r.repo_path,paneColor:r.pane_color,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,createdAt:ts2(r.created_at),updatedAt:ts2(r.updated_at),turnId:r.turn_id??null,outcome:r.outcome??null,closedAt:r.closed_at?ts2(r.closed_at):null,closeReason:r.close_reason??null}}function rowToAssignment(r){return{id:r.id,executorId:r.executor_id,taskId:r.task_id,wishSlug:r.wish_slug,groupNumber:r.group_number,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,outcome:r.outcome,createdAt:ts2(r.created_at)}}var exports_executor_registry={};__export(exports_executor_registry,{updateExecutorState:()=>updateExecutorState,updateClaudeSessionId:()=>updateClaudeSessionId,terminateExecutor:()=>terminateExecutor,terminateActiveExecutor:()=>terminateActiveExecutor,resolveWorkerLivenessByTransport:()=>resolveWorkerLivenessByTransport,relinkExecutorToAgent:()=>relinkExecutorToAgent,recordResumeProviderRejected:()=>recordResumeProviderRejected,listExecutors:()=>listExecutors,isExecutorAlive:()=>isExecutorAlive,getResumeSessionId:()=>getResumeSessionId,getLiveExecutorState:()=>getLiveExecutorState,getExecutor:()=>getExecutor,getCurrentExecutor:()=>getCurrentExecutor,findLatestByMetadata:()=>findLatestByMetadata,findExecutorBySession:()=>findExecutorBySession,findExecutorByPane:()=>findExecutorByPane,createExecutor:()=>createExecutor,createAndLinkExecutor:()=>createAndLinkExecutor,_resumeJsonlScannerDeps:()=>_resumeJsonlScannerDeps});import{randomUUID as randomUUID2}from"crypto";import{open,readdir,stat}from"fs/promises";import{homedir as homedir7}from"os";import{join as join13}from"path";async function createExecutor(agentId,provider,transport,opts={}){let sql=await getConnection(),id=opts.id??randomUUID2(),now=new Date().toISOString(),rows=await sql`
|
|
425
|
+
`,drifted=[];for(let row of rows){let expected=row.id.startsWith("dir:")||row.reports_to===null?"permanent":"task";if(row.kind!==expected)drifted.push({id:row.id,kind:row.kind,expected})}for(let drift of drifted)recordAuditEvent("worker",drift.id,"agents.kind.audit_drift","auditor",{stored:drift.kind,expected:drift.expected}).catch(()=>{});return{total:rows.length,drifted}}var DEAD_PANE_ZOMBIE_TTL_HOURS=24;var init_agent_registry=__esm(()=>{init_audit();init_db();init_tmux()});function ts2(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToExecutor(r){return{id:r.id,agentId:r.agent_id,provider:r.provider,transport:r.transport,pid:r.pid,tmuxSession:r.tmux_session,tmuxPaneId:r.tmux_pane_id,tmuxWindow:r.tmux_window,tmuxWindowId:r.tmux_window_id,claudeSessionId:r.claude_session_id,state:r.state,metadata:typeof r.metadata==="string"?JSON.parse(r.metadata):r.metadata??{},worktree:r.worktree,repoPath:r.repo_path,paneColor:r.pane_color,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,createdAt:ts2(r.created_at),updatedAt:ts2(r.updated_at),turnId:r.turn_id??null,outcome:r.outcome??null,closedAt:r.closed_at?ts2(r.closed_at):null,closeReason:r.close_reason??null}}function rowToAssignment(r){return{id:r.id,executorId:r.executor_id,taskId:r.task_id,wishSlug:r.wish_slug,groupNumber:r.group_number,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,outcome:r.outcome,createdAt:ts2(r.created_at)}}var exports_executor_registry={};__export(exports_executor_registry,{updateExecutorState:()=>updateExecutorState,updateClaudeSessionId:()=>updateClaudeSessionId,terminateExecutor:()=>terminateExecutor,terminateActiveExecutor:()=>terminateActiveExecutor,resolveWorkerLivenessByTransport:()=>resolveWorkerLivenessByTransport,relinkExecutorToAgent:()=>relinkExecutorToAgent,recordResumeProviderRejected:()=>recordResumeProviderRejected,listExecutors:()=>listExecutors,isExecutorAlive:()=>isExecutorAlive,getResumeSessionId:()=>getResumeSessionId,getLiveExecutorState:()=>getLiveExecutorState,getExecutor:()=>getExecutor,getCurrentExecutor:()=>getCurrentExecutor,findLatestByMetadata:()=>findLatestByMetadata,findExecutorBySession:()=>findExecutorBySession,findExecutorByPane:()=>findExecutorByPane,createExecutor:()=>createExecutor,createAndLinkExecutor:()=>createAndLinkExecutor,_resumeJsonlScannerDeps:()=>_resumeJsonlScannerDeps,_resetMissingSessionDedupeForTests:()=>_resetMissingSessionDedupeForTests});import{randomUUID as randomUUID2}from"crypto";import{open,readdir,stat}from"fs/promises";import{homedir as homedir7}from"os";import{join as join13}from"path";async function createExecutor(agentId,provider,transport,opts={}){let sql=await getConnection(),id=opts.id??randomUUID2(),now=new Date().toISOString(),rows=await sql`
|
|
426
426
|
INSERT INTO executors (
|
|
427
427
|
id, agent_id, provider, transport, pid,
|
|
428
428
|
tmux_session, tmux_pane_id, tmux_window, tmux_window_id,
|
|
@@ -477,7 +477,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
477
477
|
ORDER BY started_at DESC
|
|
478
478
|
LIMIT 1
|
|
479
479
|
`;return rows.length>0?rowToExecutor(rows[0]):null}async function relinkExecutorToAgent(executorId,agentId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function updateClaudeSessionId(executorId,sessionId){await(await getConnection())`UPDATE executors SET claude_session_id = ${sessionId} WHERE id = ${executorId}`}function sanitizeCwdForProjects(p){return p.replace(/[^a-zA-Z0-9]/g,"-")}function resolveClaudeConfigDir(){return process.env.CLAUDE_CONFIG_DIR??join13(homedir7(),".claude")}async function readJsonlIdentity(filePath){let handle=null;try{handle=await open(filePath,"r");let buffer=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer,0,buffer.length,0),head=buffer.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
480
|
-
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:null,agentName=typeof entry.agentName==="string"?entry.agentName:null;if(teamName!==null&&agentName!==null)return{teamName,agentName}}catch{}}}catch{return{teamName:null,agentName:null}}finally{await handle?.close().catch(()=>{})}return{teamName:null,agentName:null}}async function mapWithConcurrency(items,cap,fn){let results=Array(items.length),cursor=0,workers=[],workerCount=Math.min(cap,items.length);for(let w=0;w<workerCount;w++)workers.push((async()=>{while(cursor<items.length){let i2=cursor++;if(i2>=items.length)return;results[i2]=await fn(items[i2])}})());return await Promise.all(workers),results}async function defaultScanForSession(cwd,identity){if(!identity)return null;let projectDir=join13(resolveClaudeConfigDir(),"projects",sanitizeCwdForProjects(cwd)),entries;try{entries=await readdir(projectDir)}catch{return null}let jsonls=entries.filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let sorted=(await mapWithConcurrency(jsonls,STAT_CONCURRENCY_CAP,async(name)=>{let full=join13(projectDir,name);try{let s=await stat(full);return{name,full,mtime:s.mtimeMs}}catch{return null}})).filter((x)=>x!==null).sort((a,b)=>b.mtime-a.mtime);for(let candidate of sorted){let{teamName,agentName}=await readJsonlIdentity(candidate.full);if(teamName!==identity.team||agentName!==identity.customName)continue;return candidate.name.replace(/\.jsonl$/,"")}return null}async function tryJsonlFallback(agentId,row,actor){let cwd=row?.repo_path??null;if(!cwd)return null;let team=row?.team??null,customName=row?.custom_name??null,identity=team&&customName?{team,customName}:null;if(!identity)return null;let recoveredSessionId=await(_resumeJsonlScannerDeps.scanForSession??defaultScanForSession)(cwd,identity);if(!recoveredSessionId)return null;let reason=row&&row.executor_id!==null?"null_session":"no_executor";return await recordAuditEvent("agent",agentId,"resume.recovered_via_jsonl",actor,{sessionId:recoveredSessionId,executorId:row?.executor_id??null,cwd,team:identity.team,customName:identity.customName,recoveredFrom:reason}),recoveredSessionId}async function emitMissingSession(agentId,row,actor){if(row===null||row.executor_id===null){await recordAuditEvent("agent",agentId,"resume.missing_session",actor,{reason:"no_executor"});return}await recordAuditEvent("agent",agentId,"resume.missing_session",actor,{reason:"null_session",executorId:row.executor_id})}async function getResumeSessionId(agentId){let rows=await(await getConnection())`
|
|
480
|
+
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:null,agentName=typeof entry.agentName==="string"?entry.agentName:null;if(teamName!==null&&agentName!==null)return{teamName,agentName}}catch{}}}catch{return{teamName:null,agentName:null}}finally{await handle?.close().catch(()=>{})}return{teamName:null,agentName:null}}async function mapWithConcurrency(items,cap,fn){let results=Array(items.length),cursor=0,workers=[],workerCount=Math.min(cap,items.length);for(let w=0;w<workerCount;w++)workers.push((async()=>{while(cursor<items.length){let i2=cursor++;if(i2>=items.length)return;results[i2]=await fn(items[i2])}})());return await Promise.all(workers),results}async function defaultScanForSession(cwd,identity){if(!identity)return null;let projectDir=join13(resolveClaudeConfigDir(),"projects",sanitizeCwdForProjects(cwd)),entries;try{entries=await readdir(projectDir)}catch{return null}let jsonls=entries.filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let sorted=(await mapWithConcurrency(jsonls,STAT_CONCURRENCY_CAP,async(name)=>{let full=join13(projectDir,name);try{let s=await stat(full);return{name,full,mtime:s.mtimeMs}}catch{return null}})).filter((x)=>x!==null).sort((a,b)=>b.mtime-a.mtime);for(let candidate of sorted){let{teamName,agentName}=await readJsonlIdentity(candidate.full);if(teamName!==identity.team||agentName!==identity.customName)continue;return candidate.name.replace(/\.jsonl$/,"")}return null}async function tryJsonlFallback(agentId,row,actor){let cwd=row?.repo_path??null;if(!cwd)return null;let team=row?.team??null,customName=row?.custom_name??null,identity=team&&customName?{team,customName}:null;if(!identity)return null;let recoveredSessionId=await(_resumeJsonlScannerDeps.scanForSession??defaultScanForSession)(cwd,identity);if(!recoveredSessionId)return null;let reason=row&&row.executor_id!==null?"null_session":"no_executor";return await recordAuditEvent("agent",agentId,"resume.recovered_via_jsonl",actor,{sessionId:recoveredSessionId,executorId:row?.executor_id??null,cwd,team:identity.team,customName:identity.customName,recoveredFrom:reason}),recoveredSessionId}function shouldEmitMissingSession(agentId,actor,reason){let key=`${agentId}:${actor}:${reason}`,now=Date.now(),last=missingSessionLastEmit.get(key);if(last!==void 0&&now-last<MISSING_SESSION_DEDUPE_WINDOW_MS)return!1;if(missingSessionLastEmit.size>=MISSING_SESSION_DEDUPE_MAX_KEYS){let entries=Array.from(missingSessionLastEmit.entries()).sort((a,b)=>a[1]-b[1]),drop=Math.floor(entries.length/4);for(let i2=0;i2<drop;i2++)missingSessionLastEmit.delete(entries[i2][0])}return missingSessionLastEmit.set(key,now),!0}function _resetMissingSessionDedupeForTests(){missingSessionLastEmit.clear()}async function emitMissingSession(agentId,row,actor){if(row===null||row.executor_id===null){if(!shouldEmitMissingSession(agentId,actor,"no_executor"))return;await recordAuditEvent("agent",agentId,"resume.missing_session",actor,{reason:"no_executor"});return}if(!shouldEmitMissingSession(agentId,actor,"null_session"))return;await recordAuditEvent("agent",agentId,"resume.missing_session",actor,{reason:"null_session",executorId:row.executor_id})}async function getResumeSessionId(agentId){let rows=await(await getConnection())`
|
|
481
481
|
SELECT a.current_executor_id AS executor_id,
|
|
482
482
|
e.claude_session_id,
|
|
483
483
|
a.repo_path,
|
|
@@ -492,7 +492,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
492
492
|
WHERE a.id = ${agentId}
|
|
493
493
|
AND e.state IN ('spawning', 'running', 'working', 'idle', 'permission', 'question')
|
|
494
494
|
LIMIT 1
|
|
495
|
-
`;return rows.length>0?rows[0].state:null}async function isExecutorAlive(agentId){return await getLiveExecutorState(agentId)!==null}async function resolveWorkerLivenessByTransport(worker,opts){if(/^%\d+$/.test(worker.paneId))return(opts?.isPaneAliveFn??(async(pane)=>{let{isPaneAlive:isPaneAlive2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));return isPaneAlive2(pane)}))(worker.paneId);return(opts?.isExecutorAliveFn??isExecutorAlive)(worker.id)}var STAT_CONCURRENCY_CAP=32,_resumeJsonlScannerDeps;var init_executor_registry=__esm(()=>{init_audit();init_db();_resumeJsonlScannerDeps={scanForSession:null}});var exports_team_manager={};__export(exports_team_manager,{validateBranchName:()=>validateBranchName,updateTeamConfig:()=>updateTeamConfig,unarchiveTeam:()=>unarchiveTeam,setTeamStatus:()=>setTeamStatus,resolveLeaderName:()=>resolveLeaderName,listTeams:()=>listTeams,listMembers:()=>listMembers,killTeamMembers:()=>killTeamMembers,hireAgent:()=>hireAgent,getTeam:()=>getTeam,fireAgent:()=>fireAgent,ensureTeamRow:()=>ensureTeamRow,disbandTeam:()=>disbandTeam,createTeam:()=>createTeam,archiveTeam:()=>archiveTeam});import{existsSync as existsSync10}from"fs";import{mkdir as mkdir2,rm,symlink}from"fs/promises";import{homedir as homedir8}from"os";import path,{join as join14}from"path";var{$:$2}=globalThis.Bun;function parseMembers(raw){if(Array.isArray(raw))return raw;if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed:[]}catch{return[]}return[]}function rowToTeamConfig(row){let config={name:row.name,repo:row.repo,baseBranch:row.base_branch,worktreePath:row.worktree_path,members:parseMembers(row.members),status:row.status,createdAt:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)};if(row.leader)config.leader=row.leader;if(row.native_team_parent_session_id)config.nativeTeamParentSessionId=row.native_team_parent_session_id;if(row.native_teams_enabled)config.nativeTeamsEnabled=row.native_teams_enabled;if(row.tmux_session_name)config.tmuxSessionName=row.tmux_session_name;if(row.wish_slug)config.wishSlug=row.wish_slug;if(row.spawner)config.spawner=row.spawner;if(row.archived_at)config.archivedAt=row.archived_at instanceof Date?row.archived_at.toISOString():String(row.archived_at);if(row.parent_team)config.parentTeam=row.parent_team;let allow=parseAllowChildReachback(row.allow_child_reachback);if(allow.length>0)config.allowChildReachback=allow;return config}function parseAllowChildReachback(raw){if(Array.isArray(raw))return raw.filter((x)=>typeof x==="string");if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter((x)=>typeof x==="string"):[]}catch{return[]}return[]}function getGenieDir2(){return process.env.GENIE_HOME??join14(homedir8(),".genie")}function getWorktreeBase(repoPath){let base=loadGenieConfigSync().terminal?.worktreeBase;if(base&&base!==".worktrees"){if(path.isAbsolute(base))return base;return join14(repoPath,base)}let projectName=path.basename(repoPath);return join14(getGenieDir2(),"worktrees",projectName)}function validateBranchName(name){let errors2=[];if(/\s/.test(name))errors2.push("contains spaces");if(name.includes(".."))errors2.push('contains ".."');if(name.includes("~"))errors2.push('contains "~"');if(name.includes("^"))errors2.push('contains "^"');if(name.includes(":"))errors2.push('contains ":"');if(name.includes("?"))errors2.push('contains "?"');if(name.includes("*"))errors2.push('contains "*"');if(name.includes("["))errors2.push('contains "["');if(name.includes("\\"))errors2.push('contains "\\"');if(/[\x00-\x1f\x7f]/.test(name))errors2.push("contains control characters");if(name.endsWith(".lock"))errors2.push('ends with ".lock"');if(name.endsWith("/"))errors2.push('ends with "/"');if(name.endsWith("."))errors2.push('ends with "."');if(name.startsWith("-"))errors2.push('starts with "-"');if(errors2.length>0)throw Error(`Invalid team name '${name}': must be a valid git branch name (${errors2.join(", ")})`)}async function killWorkersByName(agentName,teamName){let matches=(await list()).filter((w)=>(w.role===agentName||w.id===agentName)&&(!teamName||w.team===teamName));for(let w of matches){if(w.currentExecutorId)try{await terminateExecutor(w.currentExecutorId),await setCurrentExecutor(w.id,null)}catch{}try{if(w.paneId&&w.paneId!=="inline"){let{execSync:execSync2}=__require("child_process"),{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync2(genieTmuxCmd2(`kill-pane -t ${w.paneId}`),{stdio:"ignore"})}}catch{}await unregister(w.id)}}async function postCloneInit(repoPath,worktreePath){let parentNodeModules=join14(repoPath,"node_modules"),worktreeNodeModules=join14(worktreePath,"node_modules");if(existsSync10(parentNodeModules)&&!existsSync10(worktreeNodeModules))try{await symlink(parentNodeModules,worktreeNodeModules,"dir")}catch{}let initScript=join14(repoPath,".genie","init.sh");if(existsSync10(initScript))try{await $2`bash ${initScript}`.cwd(worktreePath).quiet()}catch{}}async function ensureWorktree(repoPath,branchName,worktreePath,baseBranch){try{await $2`git -C ${repoPath} fetch origin ${baseBranch}`.quiet()}catch{}if(await mkdir2(path.dirname(worktreePath),{recursive:!0}),existsSync10(worktreePath))return;let branchExists=!1;try{await $2`git -C ${repoPath} rev-parse --verify ${branchName}`.quiet(),branchExists=!0}catch{if(!branchExists)try{await $2`git -C ${repoPath} branch ${branchName} origin/${baseBranch}`.quiet()}catch{try{await $2`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet()}catch{await $2`git -C ${repoPath} branch ${branchName}`.quiet()}}}await $2`git clone --shared --branch ${branchName} ${repoPath} ${worktreePath}`.quiet();try{let userName=(await $2`git -C ${repoPath} config user.name`.quiet()).text().trim(),userEmail=(await $2`git -C ${repoPath} config user.email`.quiet()).text().trim();if(userName)await $2`git -C ${worktreePath} config user.name ${userName}`.quiet();if(userEmail)await $2`git -C ${worktreePath} config user.email ${userEmail}`.quiet()}catch{}await postCloneInit(repoPath,worktreePath)}async function detectSpawnerParentTeam(newTeamName){let envTeam=process.env.GENIE_TEAM,spawnerName=process.env.GENIE_AGENT_NAME;if(!envTeam||!spawnerName)return null;if(spawnerName==="cli")return null;if(envTeam===newTeamName)return null;try{return await getTeam(envTeam)?envTeam:null}catch{return null}}async function createTeam(name,repo,baseBranch="dev"){validateBranchName(name);let repoPath=path.resolve(repo),existing=await getTeam(name);if(existing)return existing;let worktreeBase=getWorktreeBase(repoPath),worktreePath=join14(worktreeBase,name);await ensureWorktree(repoPath,name,worktreePath,baseBranch);let now=new Date().toISOString(),config={name,repo:repoPath,baseBranch,worktreePath,members:[],status:"in_progress",createdAt:now},promoted=await detectSpawnerParentTeam(name);if(promoted)config.parentTeam=promoted;if(isInsideClaudeCode()){config.nativeTeamsEnabled=!0;try{let result2=await registerAsTeamLead(name);config.nativeTeamParentSessionId=result2.sessionId}catch{}}return await(await getConnection())`
|
|
495
|
+
`;return rows.length>0?rows[0].state:null}async function isExecutorAlive(agentId){return await getLiveExecutorState(agentId)!==null}async function resolveWorkerLivenessByTransport(worker,opts){if(/^%\d+$/.test(worker.paneId))return(opts?.isPaneAliveFn??(async(pane)=>{let{isPaneAlive:isPaneAlive2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));return isPaneAlive2(pane)}))(worker.paneId);return(opts?.isExecutorAliveFn??isExecutorAlive)(worker.id)}var STAT_CONCURRENCY_CAP=32,_resumeJsonlScannerDeps,MISSING_SESSION_DEDUPE_WINDOW_MS=60000,missingSessionLastEmit,MISSING_SESSION_DEDUPE_MAX_KEYS=4096;var init_executor_registry=__esm(()=>{init_audit();init_db();_resumeJsonlScannerDeps={scanForSession:null};missingSessionLastEmit=new Map});var exports_team_manager={};__export(exports_team_manager,{validateBranchName:()=>validateBranchName,updateTeamConfig:()=>updateTeamConfig,unarchiveTeam:()=>unarchiveTeam,setTeamStatus:()=>setTeamStatus,resolveLeaderName:()=>resolveLeaderName,listTeams:()=>listTeams,listMembers:()=>listMembers,killTeamMembers:()=>killTeamMembers,hireAgent:()=>hireAgent,getTeam:()=>getTeam,fireAgent:()=>fireAgent,ensureTeamRow:()=>ensureTeamRow,disbandTeam:()=>disbandTeam,createTeam:()=>createTeam,archiveTeam:()=>archiveTeam});import{existsSync as existsSync10}from"fs";import{mkdir as mkdir2,rm,symlink}from"fs/promises";import{homedir as homedir8}from"os";import path,{join as join14}from"path";var{$:$2}=globalThis.Bun;function parseMembers(raw){if(Array.isArray(raw))return raw;if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed:[]}catch{return[]}return[]}function rowToTeamConfig(row){let config={name:row.name,repo:row.repo,baseBranch:row.base_branch,worktreePath:row.worktree_path,members:parseMembers(row.members),status:row.status,createdAt:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)};if(row.leader)config.leader=row.leader;if(row.native_team_parent_session_id)config.nativeTeamParentSessionId=row.native_team_parent_session_id;if(row.native_teams_enabled)config.nativeTeamsEnabled=row.native_teams_enabled;if(row.tmux_session_name)config.tmuxSessionName=row.tmux_session_name;if(row.wish_slug)config.wishSlug=row.wish_slug;if(row.spawner)config.spawner=row.spawner;if(row.archived_at)config.archivedAt=row.archived_at instanceof Date?row.archived_at.toISOString():String(row.archived_at);if(row.parent_team)config.parentTeam=row.parent_team;let allow=parseAllowChildReachback(row.allow_child_reachback);if(allow.length>0)config.allowChildReachback=allow;return config}function parseAllowChildReachback(raw){if(Array.isArray(raw))return raw.filter((x)=>typeof x==="string");if(typeof raw==="string")try{let parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter((x)=>typeof x==="string"):[]}catch{return[]}return[]}function getGenieDir2(){return process.env.GENIE_HOME??join14(homedir8(),".genie")}function getWorktreeBase(repoPath){let base=loadGenieConfigSync().terminal?.worktreeBase;if(base&&base!==".worktrees"){if(path.isAbsolute(base))return base;return join14(repoPath,base)}let projectName=path.basename(repoPath);return join14(getGenieDir2(),"worktrees",projectName)}function validateBranchName(name){let errors2=[];if(/\s/.test(name))errors2.push("contains spaces");if(name.includes(".."))errors2.push('contains ".."');if(name.includes("~"))errors2.push('contains "~"');if(name.includes("^"))errors2.push('contains "^"');if(name.includes(":"))errors2.push('contains ":"');if(name.includes("?"))errors2.push('contains "?"');if(name.includes("*"))errors2.push('contains "*"');if(name.includes("["))errors2.push('contains "["');if(name.includes("\\"))errors2.push('contains "\\"');if(/[\x00-\x1f\x7f]/.test(name))errors2.push("contains control characters");if(name.endsWith(".lock"))errors2.push('ends with ".lock"');if(name.endsWith("/"))errors2.push('ends with "/"');if(name.endsWith("."))errors2.push('ends with "."');if(name.startsWith("-"))errors2.push('starts with "-"');if(errors2.length>0)throw Error(`Invalid team name '${name}': must be a valid git branch name (${errors2.join(", ")})`)}async function killWorkersByName(agentName,teamName){let matches=(await list()).filter((w)=>(w.role===agentName||w.id===agentName)&&(!teamName||w.team===teamName));for(let w of matches){if(w.currentExecutorId)try{await terminateExecutor(w.currentExecutorId),await setCurrentExecutor(w.id,null)}catch{}try{if(w.paneId&&w.paneId!=="inline"){let{execSync:execSync2}=__require("child_process"),{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync2(genieTmuxCmd2(`kill-pane -t ${w.paneId}`),{stdio:"ignore"})}}catch{}await unregister(w.id)}}async function postCloneInit(repoPath,worktreePath){let parentNodeModules=join14(repoPath,"node_modules"),worktreeNodeModules=join14(worktreePath,"node_modules");if(existsSync10(parentNodeModules)&&!existsSync10(worktreeNodeModules))try{await symlink(parentNodeModules,worktreeNodeModules,"dir")}catch{}let initScript=join14(repoPath,".genie","init.sh");if(existsSync10(initScript))try{await $2`bash ${initScript}`.cwd(worktreePath).quiet()}catch{}}async function ensureWorktree(repoPath,branchName,worktreePath,baseBranch){try{await $2`git -C ${repoPath} fetch origin ${baseBranch}`.quiet()}catch{}if(await mkdir2(path.dirname(worktreePath),{recursive:!0}),existsSync10(worktreePath))return;let branchExists=!1;try{await $2`git -C ${repoPath} rev-parse --verify ${branchName}`.quiet(),branchExists=!0}catch{if(!branchExists)try{await $2`git -C ${repoPath} branch ${branchName} origin/${baseBranch}`.quiet()}catch{try{await $2`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet()}catch{await $2`git -C ${repoPath} branch ${branchName}`.quiet()}}}await $2`git clone --shared --branch ${branchName} ${repoPath} ${worktreePath}`.quiet();try{let userName=(await $2`git -C ${repoPath} config user.name`.quiet()).text().trim(),userEmail=(await $2`git -C ${repoPath} config user.email`.quiet()).text().trim();if(userName)await $2`git -C ${worktreePath} config user.name ${userName}`.quiet();if(userEmail)await $2`git -C ${worktreePath} config user.email ${userEmail}`.quiet()}catch{}await postCloneInit(repoPath,worktreePath)}async function detectSpawnerParentTeam(newTeamName){let envTeam=process.env.GENIE_TEAM,spawnerName=process.env.GENIE_AGENT_NAME;if(!envTeam||!spawnerName)return null;if(spawnerName==="cli")return null;if(envTeam===newTeamName)return null;try{return await getTeam(envTeam)?envTeam:null}catch{return null}}async function createTeam(name,repo,baseBranch="dev"){validateBranchName(name);let repoPath=path.resolve(repo),existing=await getTeam(name);if(existing)return existing;let worktreeBase=getWorktreeBase(repoPath),worktreePath=join14(worktreeBase,name);await ensureWorktree(repoPath,name,worktreePath,baseBranch);let now=new Date().toISOString(),config={name,repo:repoPath,baseBranch,worktreePath,members:[],status:"in_progress",createdAt:now},promoted=await detectSpawnerParentTeam(name);if(promoted)config.parentTeam=promoted;if(isInsideClaudeCode()){config.nativeTeamsEnabled=!0;try{let result2=await registerAsTeamLead(name);config.nativeTeamParentSessionId=result2.sessionId}catch{}}return await(await getConnection())`
|
|
496
496
|
INSERT INTO teams (
|
|
497
497
|
name, repo, base_branch, worktree_path, leader,
|
|
498
498
|
members, status, native_team_parent_session_id,
|
|
@@ -573,7 +573,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
573
573
|
`).slice(0,20)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:void 0,agentName=typeof entry.agentName==="string"?entry.agentName:void 0;if(teamName||agentName)return{teamName,agentName}}catch{}}}catch{return{}}finally{await handle?.close().catch(()=>{})}return{}}function rootScore(metadata){if(!metadata.teamName&&!metadata.agentName)return 2;if(metadata.teamName&&!metadata.agentName)return 1;return 0}function compareSessionRanking(a,b,leadRefs){let aLeadRefs=leadRefs.get(a.name.replace(".jsonl",""))??0,bLeadRefs=leadRefs.get(b.name.replace(".jsonl",""))??0;if(aLeadRefs!==bLeadRefs)return bLeadRefs-aLeadRefs;let aRoot=rootScore(a.metadata),bRoot=rootScore(b.metadata);if(aRoot!==bRoot)return bRoot-aRoot;return b.mtime-a.mtime}async function discoverClaudeParentSessionId(cwd){let envSessionId=process.env.CLAUDE_CODE_SESSION_ID;if(envSessionId)return envSessionId;let projectDir=join15(claudeConfigDir2(),"projects",sanitizePath(cwd??process.cwd()));try{let jsonls=(await readdir2(projectDir)).filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let ranked=await Promise.all(jsonls.map(async(name)=>{let filePath=join15(projectDir,name),s=await stat2(filePath),metadata=await readSessionMetadata(filePath);return{name,mtime:s.mtimeMs,metadata}})),leadRefs=await countLeadSessionRefs();return ranked.sort((a,b)=>compareSessionRanking(a,b,leadRefs)),ranked[0]?.name.replace(".jsonl","")??null}catch{return null}}function isInsideClaudeCode(){return process.env.CLAUDECODE==="1"}async function discoverTeamName(cwd){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let base=teamsBaseDir(),sessionId=await discoverClaudeSessionId(cwd);if(sessionId)try{let teams=await readdir2(base);for(let name of teams){let cfgPath=join15(base,name,"config.json");try{let content=await readFile3(cfgPath,"utf-8"),config=JSON.parse(content);if(config.leadSessionId===sessionId)return config.name}catch{}}}catch{}let tmuxSessionName=await currentTmuxSessionName();if(tmuxSessionName){let cfgPath=join15(base,tmuxSessionName,"config.json");try{let content=await readFile3(cfgPath,"utf-8");return JSON.parse(content).name}catch{}}return null}async function currentTmuxSessionName(){if(!process.env.TMUX)return null;try{let{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));return await getCurrentSessionName2()}catch{return null}}async function registerAsTeamLead(teamName,opts){let sessionId=await discoverClaudeSessionId(opts?.cwd);if(!sessionId)throw Error("Could not discover Claude Code session ID. Are you running inside Claude Code with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1?");let resolvedLeaderName=opts?.leaderName??teamName,config=await ensureNativeTeam(teamName,`Genie team: ${teamName}`,sessionId,resolvedLeaderName);if(config.leadSessionId!==sessionId)config.leadSessionId=sessionId,await saveConfig(teamName,config);let sanitized=sanitizeTeamName(teamName),leadAgentId=`${sanitizeTeamName(resolvedLeaderName)}@${sanitized}`,existingLead=config.members.find((m)=>m.agentId===leadAgentId),resolvedPaneId=opts?.tmuxPaneId??process.env.TMUX_PANE;if(!existingLead||!existingLead.isActive)await registerNativeMember(teamName,{agentName:resolvedLeaderName,agentType:"general-purpose",color:opts?.color??"blue",tmuxPaneId:resolvedPaneId,cwd:opts?.cwd??process.cwd()});else if(resolvedPaneId&&existingLead.tmuxPaneId!==resolvedPaneId)existingLead.tmuxPaneId=resolvedPaneId,await saveConfig(teamName,config);let inbox=inboxPath(teamName,resolvedLeaderName);if(!existsSync11(inbox))await writeFile3(inbox,"[]");let finalConfig=await loadConfig(teamName);if(!finalConfig)throw Error(`Failed to load config for team "${teamName}" after creation`);return{sessionId,config:finalConfig}}var init_claude_native_teams=__esm(()=>{init_claude_settings();init_lockfile();init_provider_adapters()});import{existsSync as existsSync12,readFileSync as readFileSync7,statSync}from"fs";import{mkdir as mkdir4,readFile as readFile4,readdir as readdir3,rename,writeFile as writeFile4}from"fs/promises";import{homedir as homedir10}from"os";import{join as join16}from"path";function getGenieHome(){return process.env.GENIE_HOME??join16(homedir10(),".genie")}function workersJsonPath(){return join16(getGenieHome(),"workers.json")}function claudeTeamsDirPath(){return join16(process.env.CLAUDE_CONFIG_DIR??join16(homedir10(),".claude"),"teams")}function teamsSeedMarkerPath(){return join16(getGenieHome(),"state","teams-seed-marker")}function teamsDirMtime(claudeTeamsDir){try{return String(statSync(claudeTeamsDir).mtimeMs)}catch{return null}}function readFreshTeamsSeedMarker(claudeTeamsDir){let mtimeMs=teamsDirMtime(claudeTeamsDir);if(!mtimeMs)return null;try{let marker=JSON.parse(readFileSync7(teamsSeedMarkerPath(),"utf-8"));if(marker.teamsDir!==claudeTeamsDir||marker.mtimeMs!==mtimeMs||!Array.isArray(marker.teamNames))return null;return{teamsDir:marker.teamsDir,mtimeMs:marker.mtimeMs,teamNames:marker.teamNames.filter((name)=>typeof name==="string"&&name.length>0)}}catch{return null}}function hasFreshTeamsSeedMarker(claudeTeamsDir){return readFreshTeamsSeedMarker(claudeTeamsDir)!==null}async function writeTeamsSeedMarker(teamNames){let claudeTeamsDir=claudeTeamsDirPath(),mtimeMs=teamsDirMtime(claudeTeamsDir);if(!mtimeMs)return;let markerPath=teamsSeedMarkerPath(),marker={teamsDir:claudeTeamsDir,mtimeMs,teamNames:[...new Set(teamNames)].sort()};await mkdir4(join16(getGenieHome(),"state"),{recursive:!0}),await writeFile4(markerPath,`${JSON.stringify(marker)}
|
|
574
574
|
`,"utf-8")}function needsMigration(filePath){return existsSync12(filePath)&&!existsSync12(`${filePath}.migrated`)}async function readJson(filePath){try{let content=await readFile4(filePath,"utf-8");return JSON.parse(content)}catch{return null}}async function renameMatchingFiles(dir,filter){if(!existsSync12(dir))return;try{let files=await readdir3(dir);for(let f of files){if(!filter(f))continue;let fp=join16(dir,f);if(needsMigration(fp))await rename(fp,`${fp}.migrated`)}}catch{}}function needsSeed(){if(needsMigration(workersJsonPath()))return!0;let claudeTeamsDir=claudeTeamsDirPath();if(!existsSync12(claudeTeamsDir))return!1;if(hasFreshTeamsSeedMarker(claudeTeamsDir))return!1;try{return __require("fs").readdirSync(claudeTeamsDir).some((e)=>!e.startsWith("."))}catch{return!1}}async function needsSeededTeams(sql){let marker=readFreshTeamsSeedMarker(claudeTeamsDirPath());if(!marker||marker.teamNames.length===0)return!1;try{return(await sql`
|
|
575
575
|
SELECT name FROM teams WHERE name = ANY(${marker.teamNames})
|
|
576
|
-
`).length<marker.teamNames.length}catch{return!0}}function toAgentRow(a){let now=new Date().toISOString();return{id:a.id,pane_id:a.paneId??"",session:a.session??"",worktree:a.worktree??null,task_id:a.taskId??null,task_title:a.taskTitle??null,wish_slug:a.wishSlug??null,group_number:a.groupNumber??null,started_at:a.startedAt??now,state:a.state??"spawning",last_state_change:a.lastStateChange??now,repo_path:a.repoPath??"",window_name:a.windowName??null,window_id:a.windowId??null,role:a.role??null,custom_name:a.customName??null,sub_panes:a.subPanes??[],provider:a.provider??null,transport:a.transport??"tmux",skill:a.skill??null,team:a.team??null,tmux_window:a.window??null,native_agent_id:a.nativeAgentId??null,native_color:a.nativeColor??null,native_team_enabled:a.nativeTeamEnabled??!1,parent_session_id:a.parentSessionId??null,suspended_at:a.suspendedAt??null,auto_resume:a.autoResume??!
|
|
576
|
+
`).length<marker.teamNames.length}catch{return!0}}function toAgentRow(a){let now=new Date().toISOString();return{id:a.id,pane_id:a.paneId??"",session:a.session??"",worktree:a.worktree??null,task_id:a.taskId??null,task_title:a.taskTitle??null,wish_slug:a.wishSlug??null,group_number:a.groupNumber??null,started_at:a.startedAt??now,state:a.state??"spawning",last_state_change:a.lastStateChange??now,repo_path:a.repoPath??"",window_name:a.windowName??null,window_id:a.windowId??null,role:a.role??null,custom_name:a.customName??null,sub_panes:a.subPanes??[],provider:a.provider??null,transport:a.transport??"tmux",skill:a.skill??null,team:a.team??null,tmux_window:a.window??null,native_agent_id:a.nativeAgentId??null,native_color:a.nativeColor??null,native_team_enabled:a.nativeTeamEnabled??!1,parent_session_id:a.parentSessionId??null,suspended_at:a.suspendedAt??null,auto_resume:a.autoResume??!1,resume_attempts:a.resumeAttempts??0,last_resume_attempt:a.lastResumeAttempt??null,max_resume_attempts:a.maxResumeAttempts??3,pane_color:null}}async function upsertAgent(sql,a){let r=toAgentRow(a);await sql`
|
|
577
577
|
INSERT INTO agents (
|
|
578
578
|
id, pane_id, session, worktree, task_id, task_title,
|
|
579
579
|
wish_slug, group_number, started_at, state, last_state_change,
|
|
@@ -2208,7 +2208,7 @@ ${body}`;writeFileSync14(filePath,output,"utf-8")}function serializeSdkConfig(sd
|
|
|
2208
2208
|
'scheduler',
|
|
2209
2209
|
${tx.json({agent_id:worker.id,reason,outcome:"clean_exit_unverified"})}
|
|
2210
2210
|
)
|
|
2211
|
-
`,{terminalized:!0,executorId}})}catch(err){let message=err instanceof Error?err.message:String(err);return deps.log({timestamp:deps.now().toISOString(),level:"error",event:"terminalize_clean_exit_unverified_failed",agent_id:worker.id,error:message}),{terminalized:!1,executorId:null}}}async function reconcileUnresumable(deps){let workers=await deps.listWorkers(),flipped=0;for(let worker of workers){if(worker.state!=="error")continue;if(worker.autoResume===!1)continue;if(worker.currentSessionId)continue;try{await deps.updateAgent(worker.id,{autoResume:!1}),deps.log({timestamp:deps.now().toISOString(),level:"warn",event:"agent_marked_unresumable",agent_id:worker.id,reason:"no_session_id",state:worker.state}),flipped++}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"warn",event:"agent_marked_unresumable_failed",agent_id:worker.id,error:message})}}return flipped}async function countActiveWorkers(workers,isPaneAlive2){let count=0;for(let w of workers){if(w.state==null||INACTIVE_WORKER_STATES.has(w.state))continue;if(/^%\d+$/.test(w.paneId))try{if(!await isPaneAlive2(w.paneId))continue}catch{}count++}return count}function telemetryState(raw){return raw&&TELEMETRY_STATES.has(raw)?raw:"unknown"}function recordResumeTelemetry(eventType,payload,actor){recordAuditEvent("agent.resume",payload.entity_id,eventType,actor,payload).catch(()=>{});try{let v2Payload={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:payload.trigger};if(payload.last_error)v2Payload.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2Payload.exhausted=payload.exhausted??!1;emitEvent(eventType,v2Payload,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"scheduler.auto-resume"})}catch{}}async function attemptAgentResume(deps,config,agent){let now=deps.now(),agentId=agent.id;if(agent.autoResume===!1)return deps.log({timestamp:now.toISOString(),level:"debug",event:"agent_resume_skipped",agent_id:agentId,reason:"auto_resume_disabled"}),"skipped";if(!agent.currentSessionId)
|
|
2211
|
+
`,{terminalized:!0,executorId}})}catch(err){let message=err instanceof Error?err.message:String(err);return deps.log({timestamp:deps.now().toISOString(),level:"error",event:"terminalize_clean_exit_unverified_failed",agent_id:worker.id,error:message}),{terminalized:!1,executorId:null}}}async function reconcileUnresumable(deps){let workers=await deps.listWorkers(),flipped=0;for(let worker of workers){if(worker.state!=="error")continue;if(worker.autoResume===!1)continue;if(worker.currentSessionId)continue;try{await deps.updateAgent(worker.id,{autoResume:!1}),deps.log({timestamp:deps.now().toISOString(),level:"warn",event:"agent_marked_unresumable",agent_id:worker.id,reason:"no_session_id",state:worker.state}),flipped++}catch(err){let message=err instanceof Error?err.message:String(err);deps.log({timestamp:deps.now().toISOString(),level:"warn",event:"agent_marked_unresumable_failed",agent_id:worker.id,error:message})}}return flipped}async function countActiveWorkers(workers,isPaneAlive2){let count=0;for(let w of workers){if(w.state==null||INACTIVE_WORKER_STATES.has(w.state))continue;if(/^%\d+$/.test(w.paneId))try{if(!await isPaneAlive2(w.paneId))continue}catch{}count++}return count}function telemetryState(raw){return raw&&TELEMETRY_STATES.has(raw)?raw:"unknown"}function recordResumeTelemetry(eventType,payload,actor){recordAuditEvent("agent.resume",payload.entity_id,eventType,actor,payload).catch(()=>{});try{let v2Payload={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:payload.trigger};if(payload.last_error)v2Payload.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2Payload.exhausted=payload.exhausted??!1;emitEvent(eventType,v2Payload,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"scheduler.auto-resume"})}catch{}}async function attemptAgentResume(deps,config,agent){let now=deps.now(),agentId=agent.id;if(agent.autoResume===!1)return deps.log({timestamp:now.toISOString(),level:"debug",event:"agent_resume_skipped",agent_id:agentId,reason:"auto_resume_disabled"}),"skipped";let maxAttempts=agent.maxResumeAttempts??DEFAULT_MAX_RESUME_ATTEMPTS,attempts=agent.resumeAttempts??0;if(!agent.currentSessionId){let newAttempts2=attempts+1;if(await deps.updateAgent(agentId,{resumeAttempts:newAttempts2,lastResumeAttempt:now.toISOString()}),deps.log({timestamp:now.toISOString(),level:"debug",event:"agent_resume_skipped",agent_id:agentId,reason:"no_session_id",resume_attempts:newAttempts2,max_resume_attempts:maxAttempts}),newAttempts2>=maxAttempts)return await deps.updateAgent(agentId,{autoResume:!1}),deps.log({timestamp:now.toISOString(),level:"warn",event:"agent_resume_exhausted",agent_id:agentId,resume_attempts:newAttempts2,max_resume_attempts:maxAttempts,reason:"no_session_id_orphan"}),"exhausted";return"skipped"}if(attempts>=maxAttempts)return await deps.updateAgent(agentId,{autoResume:!1}),deps.log({timestamp:now.toISOString(),level:"warn",event:"agent_resume_exhausted",agent_id:agentId,resume_attempts:attempts,max_resume_attempts:maxAttempts}),"exhausted";if(agent.lastResumeAttempt){let lastAttempt=new Date(agent.lastResumeAttempt).getTime();if(now.getTime()-lastAttempt<RESUME_COOLDOWN_MS)return deps.log({timestamp:now.toISOString(),level:"debug",event:"agent_resume_skipped",agent_id:agentId,reason:"cooldown",last_attempt:agent.lastResumeAttempt}),"skipped"}let workers=await deps.listWorkers(),activeCount=await countActiveWorkers(workers,deps.isPaneAlive);if(activeCount>=config.maxConcurrent)return deps.log({timestamp:now.toISOString(),level:"debug",event:"agent_resume_skipped",agent_id:agentId,reason:"concurrency_cap",active:activeCount,max:config.maxConcurrent}),"skipped";let newAttempts=attempts+1;await deps.updateAgent(agentId,{resumeAttempts:newAttempts,lastResumeAttempt:now.toISOString()}),deps.log({timestamp:now.toISOString(),level:"info",event:"agent_resume_attempted",agent_id:agentId,resume_attempts:newAttempts,max_resume_attempts:maxAttempts});let stateBefore=telemetryState(agent.state);if(recordResumeTelemetry("agent.resume.attempted",{entity_id:agentId,attempt_number:newAttempts,state_before:stateBefore,state_after:stateBefore,trigger:"scheduler"},"scheduler"),await deps.resumeAgent(agentId))return await deps.updateAgent(agentId,{resumeAttempts:0}),deps.log({timestamp:now.toISOString(),level:"info",event:"agent_resume_succeeded",agent_id:agentId,resume_attempts:newAttempts}),recordResumeTelemetry("agent.resume.succeeded",{entity_id:agentId,attempt_number:newAttempts,state_before:stateBefore,state_after:"spawning",trigger:"scheduler"},"scheduler"),"resumed";deps.log({timestamp:now.toISOString(),level:"warn",event:"agent_resume_failed",agent_id:agentId,resume_attempts:newAttempts,max_resume_attempts:maxAttempts});let willExhaust=newAttempts>=maxAttempts;if(recordResumeTelemetry("agent.resume.failed",{entity_id:agentId,attempt_number:newAttempts,state_before:stateBefore,state_after:stateBefore,trigger:"scheduler",exhausted:willExhaust},"scheduler"),newAttempts>=maxAttempts)return await deps.updateAgent(agentId,{autoResume:!1}),deps.log({timestamp:now.toISOString(),level:"warn",event:"agent_resume_exhausted",agent_id:agentId,resume_attempts:newAttempts,max_resume_attempts:maxAttempts}),"exhausted";return"skipped"}async function collectHeartbeats(deps){let sql=await deps.getConnection(),now=deps.now(),activeRuns=await sql`
|
|
2212
2212
|
SELECT id, worker_id, status, trigger_id FROM runs WHERE status = 'running'
|
|
2213
2213
|
`,workers=await deps.listWorkers(),workerById=new Map(workers.map((w)=>[w.id,w])),collected=0;for(let run of activeRuns){let{alive,isPid}=await checkWorkerAlive(deps,run.worker_id),heartbeatStatus=alive?isPid?"busy":"alive":"dead",worker=workerById.get(run.worker_id),context={alive,pid_check:isPid,worker_id:run.worker_id,team:worker?.team??null,wish_slug:worker?.wishSlug??null,group_number:worker?.groupNumber??null,state:worker?.state??null},heartbeatId=deps.generateId();await sql`
|
|
2214
2214
|
INSERT INTO heartbeats (id, worker_id, run_id, status, context, last_seen_at, created_at)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260429.
|
|
3
|
+
"version": "4.260429.22",
|
|
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.260429.
|
|
3
|
+
"version": "4.260429.22",
|
|
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"
|