@automagik/genie 4.260504.8 → 4.260504.10
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 @@ PANE_ID="$1"
|
|
|
306
306
|
COLOR=$(${bin} display-message -p -t "$PANE_ID" '#{@genie_color}' 2>/dev/null)
|
|
307
307
|
[ -z "$COLOR" ] && COLOR="default"
|
|
308
308
|
${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
309
|
-
`),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,resolveAgentId:()=>resolveAgentId,removeSubPane:()=>removeSubPane,register:()=>register,reconcileStaleSpawns:()=>reconcileStaleSpawns,listTemplates:()=>listTemplates,listForRender:()=>listForRender,listExhaustedZombies:()=>listExhaustedZombies,listAllExhaustedErrored:()=>listAllExhaustedErrored,listAgentsForRender:()=>listAgentsForRender,listAgents:()=>listAgents,list:()=>list,getTeamLeadEntry:()=>getTeamLeadEntry,getPane:()=>getPane,getElapsedTime:()=>getElapsedTime,getAgentResolverCounters:()=>getAgentResolverCounters,getAgentEffectiveState:()=>getAgentEffectiveState,getAgentByName:()=>getAgentByName,getAgent:()=>getAgent,get:()=>get,findOrCreateAgent:()=>findOrCreateAgent,findByWindow:()=>findByWindow,findByTask:()=>findByTask,findByPane:()=>findByPane,filterBySession:()=>filterBySession,auditAgentKind:()=>auditAgentKind,archiveExhaustedZombies:()=>archiveExhaustedZombies,archiveAllExhaustedErrored:()=>archiveAllExhaustedErrored,addSubPane:()=>addSubPane,_resetAgentResolverCountersForTests:()=>_resetAgentResolverCountersForTests});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}`}function assertRegisterableId(id){if(!REGISTER_ID_RE.test(id))throw Error(`register: refusing to insert non-UUID/non-dir agent id ${JSON.stringify(id)}. Spawn callers must resolve the durable identity row via findOrCreateAgent first and pass that UUID \u2014 the bare-name shadow path was retired in wish retire-session-names-id-only Group 3 (migration 061 enforces the same shape at the DB).`)}async function register(agent){assertRegisterableId(agent.id);let sql=await getConnection(),now=new Date().toISOString();if(agent.team){let existing=await sql`
|
|
309
|
+
`),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,resolveAgentIdStrict:()=>resolveAgentIdStrict,resolveAgentId:()=>resolveAgentId,removeSubPane:()=>removeSubPane,register:()=>register,reconcileStaleSpawns:()=>reconcileStaleSpawns,listTemplates:()=>listTemplates,listForRender:()=>listForRender,listExhaustedZombies:()=>listExhaustedZombies,listAllExhaustedErrored:()=>listAllExhaustedErrored,listAgentsForRender:()=>listAgentsForRender,listAgents:()=>listAgents,list:()=>list,getTeamLeadEntry:()=>getTeamLeadEntry,getPane:()=>getPane,getElapsedTime:()=>getElapsedTime,getAgentResolverCounters:()=>getAgentResolverCounters,getAgentEffectiveState:()=>getAgentEffectiveState,getAgentByName:()=>getAgentByName,getAgent:()=>getAgent,get:()=>get,findOrCreateAgent:()=>findOrCreateAgent,findByWindow:()=>findByWindow,findByTask:()=>findByTask,findByPane:()=>findByPane,filterBySession:()=>filterBySession,auditAgentKind:()=>auditAgentKind,archiveExhaustedZombies:()=>archiveExhaustedZombies,archiveAllExhaustedErrored:()=>archiveAllExhaustedErrored,addSubPane:()=>addSubPane,_resetAgentResolverCountersForTests:()=>_resetAgentResolverCountersForTests});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}`}function assertRegisterableId(id){if(!REGISTER_ID_RE.test(id))throw Error(`register: refusing to insert non-UUID/non-dir agent id ${JSON.stringify(id)}. Spawn callers must resolve the durable identity row via findOrCreateAgent first and pass that UUID \u2014 the bare-name shadow path was retired in wish retire-session-names-id-only Group 3 (migration 061 enforces the same shape at the DB).`)}async function register(agent){assertRegisterableId(agent.id);let sql=await getConnection(),now=new Date().toISOString();if(agent.team){let existing=await sql`
|
|
310
310
|
SELECT team FROM agents WHERE id = ${agent.id} LIMIT 1
|
|
311
311
|
`;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`
|
|
312
312
|
UPDATE agents
|
|
@@ -419,9 +419,9 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
419
419
|
native_team_enabled, parent_session_id, current_executor_id, reports_to, title, kind, created_at, updated_at
|
|
420
420
|
FROM agents WHERE custom_name = ${name} AND team = ${team}
|
|
421
421
|
LIMIT 1
|
|
422
|
-
`;return rows.length>0?rowToAgentIdentity(rows[0]):null}function getAgentResolverCounters(){return{...resolverCounters}}function _resetAgentResolverCountersForTests(){resolverCounters.uuid=0,resolverCounters.dir=0,resolverCounters.customName=0,resolverCounters.role=0,resolverCounters.miss=0}function emitAgentResolved(resolvedId,tier,team){recordAuditEvent("agent",resolvedId,"agent_resolved",process.env.GENIE_AGENT_NAME??"resolver",{matched_tier:tier,team:team??null}).catch(()=>{})}function emitAgentResolvedAmbiguous(tier,candidateIds,team){recordAuditEvent("agent",candidateIds[0],"agent_resolved_ambiguous",process.env.GENIE_AGENT_NAME??"resolver",{matched_tier:tier,candidate_ids:candidateIds,team:team??null}).catch(()=>{})}function emitAgentNotResolved(team){recordAuditEvent("agent","unresolved","agent_not_resolved",process.env.GENIE_AGENT_NAME??"resolver",{team:team??null}).catch(()=>{})}async function resolveAgentId(nameOrId,team){if(!nameOrId)return null;let sql=await getConnection()
|
|
422
|
+
`;return rows.length>0?rowToAgentIdentity(rows[0]):null}function getAgentResolverCounters(){return{...resolverCounters}}function _resetAgentResolverCountersForTests(){resolverCounters.uuid=0,resolverCounters.dir=0,resolverCounters.customName=0,resolverCounters.role=0,resolverCounters.miss=0}function emitAgentResolved(resolvedId,tier,team){recordAuditEvent("agent",resolvedId,"agent_resolved",process.env.GENIE_AGENT_NAME??"resolver",{matched_tier:tier,team:team??null}).catch(()=>{})}function emitAgentResolvedAmbiguous(tier,candidateIds,team){recordAuditEvent("agent",candidateIds[0],"agent_resolved_ambiguous",process.env.GENIE_AGENT_NAME??"resolver",{matched_tier:tier,candidate_ids:candidateIds,team:team??null}).catch(()=>{})}function emitAgentNotResolved(team){recordAuditEvent("agent","unresolved","agent_not_resolved",process.env.GENIE_AGENT_NAME??"resolver",{team:team??null}).catch(()=>{})}async function resolveAgentId(nameOrId,team){if(!nameOrId)return null;let sql;try{sql=await getConnection()}catch{return null}let exactRows=await sql`SELECT id FROM agents WHERE id = ${nameOrId} LIMIT 1`;if(exactRows.length>0){let id=exactRows[0].id,tier=id.startsWith("dir:")?"dir":"uuid";if(tier==="dir")resolverCounters.dir++;else resolverCounters.uuid++;return emitAgentResolved(id,tier,team),id}let dirId=`dir:${nameOrId}`,dirRows=await sql`SELECT id FROM agents WHERE id = ${dirId} LIMIT 1`;if(dirRows.length>0)return resolverCounters.dir++,emitAgentResolved(dirRows[0].id,"dir",team),dirRows[0].id;if(team){let cnRows=await sql`
|
|
423
423
|
SELECT id FROM agents WHERE custom_name = ${nameOrId} AND team = ${team} LIMIT 2
|
|
424
|
-
`;if(cnRows.length===1)return resolverCounters.customName++,emitAgentResolved(cnRows[0].id,"customName",team),cnRows[0].id;if(cnRows.length>1)return emitAgentResolvedAmbiguous("customName",cnRows.map((r)=>r.id),team),resolverCounters.miss++,null}let roleRows=await sql`SELECT id FROM agents WHERE role = ${nameOrId} LIMIT 2`;if(roleRows.length===1)return resolverCounters.role++,emitAgentResolved(roleRows[0].id,"role",team),roleRows[0].id;if(roleRows.length>1)return emitAgentResolvedAmbiguous("role",roleRows.map((r)=>r.id),team),resolverCounters.miss++,null;return resolverCounters.miss++,emitAgentNotResolved(team),null}async function setCurrentExecutor(agentId,executorId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function getAgentEffectiveState(agentId){let rows=await(await getConnection())`
|
|
424
|
+
`;if(cnRows.length===1)return resolverCounters.customName++,emitAgentResolved(cnRows[0].id,"customName",team),cnRows[0].id;if(cnRows.length>1)return emitAgentResolvedAmbiguous("customName",cnRows.map((r)=>r.id),team),resolverCounters.miss++,null}let roleRows=await sql`SELECT id FROM agents WHERE role = ${nameOrId} LIMIT 2`;if(roleRows.length===1)return resolverCounters.role++,emitAgentResolved(roleRows[0].id,"role",team),roleRows[0].id;if(roleRows.length>1)return emitAgentResolvedAmbiguous("role",roleRows.map((r)=>r.id),team),resolverCounters.miss++,null;return resolverCounters.miss++,emitAgentNotResolved(team),null}async function resolveAgentIdStrict(nameOrId,team){let id=await resolveAgentId(nameOrId,team);if(id)return id;let teamHint=team?` (team scope: ${team})`:" (no team scope \u2014 pass --team to disambiguate)";throw Error(`No agent matches "${nameOrId}"${teamHint}. Resolution checked: exact id, dir:${nameOrId}, custom_name+team, role. Run \`genie ls\` to see live agents.`)}async function setCurrentExecutor(agentId,executorId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function getAgentEffectiveState(agentId){let rows=await(await getConnection())`
|
|
425
425
|
SELECT e.state FROM executors e
|
|
426
426
|
JOIN agents a ON a.current_executor_id = e.id
|
|
427
427
|
WHERE a.id = ${agentId}
|
|
@@ -1642,7 +1642,7 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
|
|
|
1642
1642
|
FROM agents a
|
|
1643
1643
|
WHERE a.id = ${agentId}
|
|
1644
1644
|
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync39}from"fs";import{join as join45}from"path";function getSystemPromptFile(workingDir){let agentsPath=join45(workingDir,"AGENTS.md");if(existsSync39(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null;if(priorSessionId!==null)priorSessionId=await acquireResumeSessionForAttempt(leaderAgent.id).catch(()=>null)??priorSessionId;let sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,isCliSender:()=>isCliSender,handleBroadcast:()=>handleBroadcast,fanoutBroadcastToNativeInboxes:()=>fanoutBroadcastToNativeInboxes,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir32}from"os";import{join as join46}from"path";async function getRegistry2(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}function isCliSender(sender){return sender==="cli"||sender.startsWith("cli:")}async function detectSenderIdentity(teamName){let envId=process.env.GENIE_AGENT_ID;if(envId&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(envId))return envId;let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry2(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join46(homedir32(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join46(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(resolvedAgent),conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1645
|
-
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;let registryMod=await getRegistry2(),senderId=await registryMod.resolveAgentId(from);if(!senderId)return null;return(await registryMod.get(senderId))?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}function broadcastAgentName(agent){return agent.customName??agent.role??agent.id}function mergeBroadcastRosterEntry(bySanitized,sanitized,patch){if(!sanitized)return;let existing=bySanitized.get(sanitized);bySanitized.set(sanitized,{agent:existing?.agent??patch.agent,sanitized,inboxName:patch.inboxName??existing?.inboxName??sanitized,agentId:existing?.agentId??patch.agentId,nativeColor:existing?.nativeColor??patch.nativeColor,nativeActive:existing?.nativeActive??patch.nativeActive})}async function resolveBroadcastRoster(teamName,deps){let[agents,nativeConfig]=await Promise.all([deps.listAgents({team:teamName}).catch(()=>[]),deps.loadConfig(teamName).catch(()=>null)]),bySanitized=new Map;for(let agent of agents){let name=broadcastAgentName(agent),sanitized=deps.sanitizeTeamName(name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:name,inboxName:sanitized,agentId:agent.id,nativeColor:agent.nativeColor})}for(let member of nativeConfig?.members??[]){let sanitized=deps.sanitizeTeamName(member.name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:member.name,inboxName:member.name,nativeColor:member.color,nativeActive:member.isActive})}return[...bySanitized.values()]}function makeBroadcastNativeMessage(from,body,timestamp2,color2){return{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:timestamp2,color:color2,read:!1}}async function broadcastSkipReason(entry2,deps){if(entry2.nativeActive===!1)return"offline";if(!entry2.agentId)return null;let state=await deps.getAgentEffectiveState(entry2.agentId).catch(()=>"offline");if(state==="offline")return"offline";if(state==="terminated")return"terminated";if(state==="error")return"dead";if(state==="done")return"done";return null}async function fanoutBroadcastToNativeInboxes(teamName,from,body,deps){let sender=deps.sanitizeTeamName(from),roster=await resolveBroadcastRoster(teamName,deps),timestamp2=(deps.now??(()=>new Date))().toISOString(),recipients=[];for(let entry2 of roster){if(entry2.sanitized===sender)continue;let skipReason=await broadcastSkipReason(entry2,deps);if(skipReason){recipients.push({agent:entry2.agent,delivered:!1,reason:skipReason});continue}try{await deps.writeNativeInbox(teamName,entry2.inboxName,makeBroadcastNativeMessage(from,body,timestamp2,entry2.nativeColor??"blue")),recipients.push({agent:entry2.agent,delivered:!0})}catch(err){recipients.push({agent:entry2.agent,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return recipients}async function handleBroadcast(body,options,deps={}){let ts3=deps.taskService??await getTaskService(),registry=deps.registry??await getRegistry2(),nativeTeams=deps.nativeTeams??await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),repoPath=deps.repoPath??process.cwd(),from=options.from??await detectSenderIdentity(options.team),teamName=await resolveTeamName2(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body),recipients=await fanoutBroadcastToNativeInboxes(teamName,from,body,{listAgents:registry.listAgents,getAgentEffectiveState:registry.getAgentEffectiveState,loadConfig:nativeTeams.loadConfig,sanitizeTeamName:nativeTeams.sanitizeTeamName,writeNativeInbox:nativeTeams.writeNativeInbox,now:deps.now});try{await(deps.publishSubjectEvent??(await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events))).publishSubjectEvent)(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,team:teamName,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName,recipients},source:"mailbox"})}catch{}let deliveredCount=recipients.filter((r)=>r.delivered).length;console.log(`Broadcast sent to team "${teamName}" (delivered to ${deliveredCount} of ${recipients.length} members).`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);for(let recipient of recipients)if(!recipient.delivered)console.log(` ${recipient.agent}: ${recipient.reason??"delivery failed"}`);return{messageId:msg.id,conversationId:conv.id,team:teamName,recipients}}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1645
|
+
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;let registryMod=await getRegistry2(),senderId=await registryMod.resolveAgentId(from);if(!senderId)return null;return(await registryMod.get(senderId))?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}function broadcastAgentName(agent){return agent.customName??agent.role??agent.id}function mergeBroadcastRosterEntry(bySanitized,sanitized,patch){if(!sanitized)return;let existing=bySanitized.get(sanitized);bySanitized.set(sanitized,{agent:existing?.agent??patch.agent,sanitized,inboxName:patch.inboxName??existing?.inboxName??sanitized,agentId:existing?.agentId??patch.agentId,nativeColor:existing?.nativeColor??patch.nativeColor,nativeActive:existing?.nativeActive??patch.nativeActive})}async function resolveBroadcastRoster(teamName,deps){let[agents,nativeConfig]=await Promise.all([deps.listAgents({team:teamName}).catch(()=>[]),deps.loadConfig(teamName).catch(()=>null)]),bySanitized=new Map;for(let agent of agents){let name=broadcastAgentName(agent),sanitized=deps.sanitizeTeamName(name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:name,inboxName:sanitized,agentId:agent.id,nativeColor:agent.nativeColor})}for(let member of nativeConfig?.members??[]){let sanitized=deps.sanitizeTeamName(member.name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:member.name,inboxName:member.name,nativeColor:member.color,nativeActive:member.isActive})}return[...bySanitized.values()]}function makeBroadcastNativeMessage(from,body,timestamp2,color2){return{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:timestamp2,color:color2,read:!1}}async function broadcastSkipReason(entry2,deps){if(entry2.nativeActive===!1)return"offline";if(!entry2.agentId)return null;let state=await deps.getAgentEffectiveState(entry2.agentId).catch(()=>"offline");if(state==="offline")return"offline";if(state==="terminated")return"terminated";if(state==="error")return"dead";if(state==="done")return"done";return null}async function fanoutBroadcastToNativeInboxes(teamName,from,body,deps){let sender=deps.sanitizeTeamName(from),roster=await resolveBroadcastRoster(teamName,deps),timestamp2=(deps.now??(()=>new Date))().toISOString(),recipients=[];for(let entry2 of roster){if(entry2.sanitized===sender)continue;let skipReason=await broadcastSkipReason(entry2,deps);if(skipReason){recipients.push({agent:entry2.agent,delivered:!1,reason:skipReason});continue}try{await deps.writeNativeInbox(teamName,entry2.inboxName,makeBroadcastNativeMessage(from,body,timestamp2,entry2.nativeColor??"blue")),recipients.push({agent:entry2.agent,delivered:!0})}catch(err){recipients.push({agent:entry2.agent,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return recipients}async function handleBroadcast(body,options,deps={}){let ts3=deps.taskService??await getTaskService(),registry=deps.registry??await getRegistry2(),nativeTeams=deps.nativeTeams??await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),repoPath=deps.repoPath??process.cwd(),from=options.from??await detectSenderIdentity(options.team),teamName=await resolveTeamName2(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body),recipients=await fanoutBroadcastToNativeInboxes(teamName,from,body,{listAgents:registry.listAgents,getAgentEffectiveState:registry.getAgentEffectiveState,loadConfig:nativeTeams.loadConfig,sanitizeTeamName:nativeTeams.sanitizeTeamName,writeNativeInbox:nativeTeams.writeNativeInbox,now:deps.now});try{await(deps.publishSubjectEvent??(await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events))).publishSubjectEvent)(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,team:teamName,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName,recipients},source:"mailbox"})}catch{}let deliveredCount=recipients.filter((r)=>r.delivered).length;console.log(`Broadcast sent to team "${teamName}" (delivered to ${deliveredCount} of ${recipients.length} members).`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);for(let recipient of recipients)if(!recipient.delivered)console.log(` ${recipient.agent}: ${recipient.reason??"delivery failed"}`);return{messageId:msg.id,conversationId:conv.id,team:teamName,recipients}}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let registryMod=await getRegistry2(),teamScope=options.team??process.env.GENIE_TEAM,toAgentId=await registryMod.resolveAgentIdStrict(to,teamScope),senderActor=localActor(from),recipientActor=localActor(toAgentId),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,toAgentId,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,toAgentId,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1646
1646
|
Examples:
|
|
1647
1647
|
genie send 'start task #3' --to engineer # Message a specific agent
|
|
1648
1648
|
genie send 'status update' --to team-lead # Report to team lead
|
|
@@ -1807,11 +1807,11 @@ ${otherList}
|
|
|
1807
1807
|
`,await sql`
|
|
1808
1808
|
DELETE FROM task_actors WHERE task_id = ANY(${ids}) AND role = 'assignee'
|
|
1809
1809
|
`,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema,WishStateMismatchError;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()});WishStateMismatchError=class WishStateMismatchError extends Error{slug;added;removed;changed;constructor(slug,added,removed,changed){let lines=[`Wish "${slug}" group structure has changed since state was created.`];if(added.length>0)lines.push(` + added: ${added.join(", ")}`);if(removed.length>0)lines.push(` - removed: ${removed.join(", ")}`);if(changed.length>0)lines.push(` ~ changed deps: ${changed.join(", ")}`);lines.push(""),lines.push(`Run \`genie reset ${slug}\` to recreate state from the current WISH.md.`);super(lines.join(`
|
|
1810
|
-
`));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps3});import{exec as exec2}from"child_process";import{readFile as readFile9}from"fs/promises";import{join as join48}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let leaderName=await resolveLeaderName(team),sanitized=sanitizeTeamName(team),leaderAgent=await getAgentByName(leaderName,sanitized).catch(()=>null);if(leaderAgent){let decision=await shouldResume(leaderAgent.id).catch(()=>null);if(decision?.sessionId)return decision.sessionId}return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let liveStates=new Set(["running","idle","working","permission","question"]);if(currentExec.tmuxPaneId&&liveStates.has(currentExec.state)&&await isPaneAlive(currentExec.tmuxPaneId)){recordAuditEvent("executor",currentExec.id,"tried_to_kill_live_executor",process.env.GENIE_AGENT_NAME??"cli",{agent_id:agentId,state:currentExec.state,pane_id:currentExec.tmuxPaneId,reason:"live_pane_guard"}).catch(()=>{});return}let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps3.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),teamConfig=await getTeam(team),session=(Boolean(process.env.TMUX)?await getCurrentSessionName():null)??teamConfig?.tmuxSessionName??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,
|
|
1810
|
+
`));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps3});import{exec as exec2}from"child_process";import{readFile as readFile9}from"fs/promises";import{join as join48}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let leaderName=await resolveLeaderName(team),sanitized=sanitizeTeamName(team),leaderAgent=await getAgentByName(leaderName,sanitized).catch(()=>null);if(leaderAgent){let decision=await shouldResume(leaderAgent.id).catch(()=>null);if(decision?.sessionId)return decision.sessionId}return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let liveStates=new Set(["running","idle","working","permission","question"]);if(currentExec.tmuxPaneId&&liveStates.has(currentExec.state)&&await isPaneAlive(currentExec.tmuxPaneId)){recordAuditEvent("executor",currentExec.id,"tried_to_kill_live_executor",process.env.GENIE_AGENT_NAME??"cli",{agent_id:agentId,state:currentExec.state,pane_id:currentExec.tmuxPaneId,reason:"live_pane_guard"}).catch(()=>{});return}let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps3.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),teamConfig=await getTeam(team),session=(Boolean(process.env.TMUX)?await getCurrentSessionName():null)??teamConfig?.tmuxSessionName??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,autoSpawnIdentity=await findOrCreateAgent(agentName,team,template.role),workerEntry={id:autoSpawnIdentity.id,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,customName:workerId,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(await registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId),spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,autoSpawnIdentity.id,workerId,agentName,team),await tryAutoBrain(workerId,repoPath),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,agentId,workerId,agentName,_team){try{let match=await _deps3.findAnyGroupByAssignee(agentId,repoPath)??await _deps3.findAnyGroupByAssignee(workerId,repoPath)??await _deps3.findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join48(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile9(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
|
|
1811
1811
|
${groupSection}`:"","",gitLog?`Last git log:
|
|
1812
1812
|
${gitLog}`:"","",gitStatus?`Uncommitted changes:
|
|
1813
1813
|
${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
|
|
1814
|
-
`);await _deps3.mailboxSend(repoPath,"genie",workerId,resumePrompt)}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Resume context injection failed: ${msg}`)}}var execAsync,_deps3;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_audit();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_provider_adapters();init_registry2();init_should_resume();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2),_deps3={findAnyGroupByAssignee,mailboxSend:send,writeNativeInbox}});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}async function waitForExecutorReady(executorId,opts){let timeoutMs=opts?.timeoutMs??DEFAULT_SPAWN_TIMEOUT_MS,start=Date.now();if(!await _pgDeps.isAvailable())return{ready:!1,elapsedMs:0};try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))return{ready:!0,elapsedMs:Date.now()-start}}catch{return{ready:!1,elapsedMs:Date.now()-start}}let sql;try{sql=await _pgDeps.getConnection()}catch{return{ready:!1,elapsedMs:Date.now()-start}}return new Promise((resolve7)=>{let resolved=!1,listener=null,pollInterval=null,timeout=null,cleanup=async()=>{if(pollInterval)clearInterval(pollInterval);if(timeout)clearTimeout(timeout);if(listener)try{await listener.unlisten()}catch{}},finish=(ready)=>{if(resolved)return;resolved=!0,cleanup().then(()=>resolve7({ready,elapsedMs:Date.now()-start}))};timeout=setTimeout(()=>finish(!1),timeoutMs),sql.listen("genie_executor_state",(payload)=>{let parts=payload.split(":");if(parts.length<4)return;let[notifyExecId,,,newState]=parts;if(notifyExecId===executorId&&(newState==="running"||newState==="idle"))finish(!0)}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollInterval=setInterval(async()=>{if(resolved)return;try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))finish(!0)}catch{}},READINESS_POLL_INTERVAL_MS)})}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000,_pgDeps;var init_spawn_command=__esm(()=>{init_db();init_executor_registry();init_orchestrator();init_tmux();_pgDeps={isAvailable,getConnection,getExecutor}});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,resolveResumeSessionId:()=>resolveResumeSessionId,getInbox:()=>getInbox,deliverToPane:()=>deliverToPane,_deps:()=>_deps4,MissingResumeSessionError:()=>MissingResumeSessionError});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){try{let executor=await findExecutorByPane(paneId);if(executor&&executor.state!=="terminated"&&executor.state!=="error"){if((await waitForExecutorReady(executor.id,{timeoutMs})).ready)return!0}}catch{}let start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isExecutorCompleted(worker){if(!worker?.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);return executor!=null&&(executor.state==="done"||executor.state==="terminated")}async function isExecutorResumable(worker){if(!worker.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);if(!executor)return!1;return!["done","error","terminated"].includes(executor.state)}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await _deps4.isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}function dedupShadowsForSend(workers){let groups=new Map;for(let w of workers){let key=`${w.customName??w.id}\x00${w.team??""}`,arr=groups.get(key);if(arr)arr.push(w);else groups.set(key,[w])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b2)=>{let aExec=a.currentExecutorId!=null?1:0,bExec=b2.currentExecutorId!=null?1:0;if(aExec!==bExec)return bExec-aExec;return(b2.startedAt??"").localeCompare(a.startedAt??"")}),out.push(arr[0])}return out}async function findLiveWorkerFuzzy(recipientId){let matches=dedupShadowsForSend(await resolveRecipient(recipientId));return matches.length===1?matches[0]:null}async function findSpawnTemplate(worker){if(!worker||!worker.team||!worker.role)return null;return(await listTemplates()).find((t)=>t.team===worker.team&&t.role===worker.role)??null}async function lockedSpawnWorker(recipientId,worker,template,resumeSessionId){let lockResult=await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${recipientId}))`;let postLockLive=await findLiveWorkerFuzzy(recipientId);if(postLockLive)return{type:"existing",worker:postLockLive};if(worker)await cleanupDeadWorkers(worker.id),await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn));return{type:"spawned",...await spawnWorkerFromTemplate2(template,resumeSessionId)}});if(lockResult.type==="existing")return{worker:lockResult.worker,respawned:!1};if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await(_deps4.waitForWorkerReady??waitForWorkerReady)(lockResult.paneId),!await _deps4.isPaneAlive(lockResult.paneId))return await unregister(lockResult.worker.id),null;return{worker:lockResult.worker,respawned:!0}}async function resolveResumeSessionId(worker,template){if(template.provider!=="claude")return;if(!worker.id)throw Error("resolveResumeSessionId: worker.id is required");let decision=await shouldResume(worker.id);if(await isExecutorResumable(worker)){if(!decision.sessionId)throw new MissingResumeSessionError(worker.id)}if(!decision.sessionId)return;let{acquireResumeSessionForAttempt:acquireResumeSessionForAttempt2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await acquireResumeSessionForAttempt2(worker.id).catch(()=>null)??decision.sessionId}async function handleSpawnError(err,worker,recipientId){if(err instanceof MissingResumeSessionError)throw err;let msg=err instanceof Error?err.message:String(err);if(console.error(`[protocol-router] Spawn failed for "${recipientId}": ${msg}`),worker)await update(worker.id,{state:"error"}).catch(()=>{});return null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await _deps4.isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(await isExecutorCompleted(worker))return null;if(!process.env.TMUX)return null;if(!worker)return null;let template=await findSpawnTemplate(worker);if(!template)return null;let resumeSessionId=await resolveResumeSessionId(worker,template);try{return await lockedSpawnWorker(recipientId,worker,template,resumeSessionId)}catch(err){return handleSpawnError(err,worker,recipientId)}}async function cleanupDeadWorkers(agentId){let w=await get(agentId);if(!w)return;if(await _deps4.isPaneAlive(w.paneId))return;await unregister(w.id)}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);else console.error(`[protocol-router] Delivery failed: all paths exhausted (worker=${worker.id}, pane=${worker.paneId}, msg="${body.slice(0,50)}")`);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to;try{let message=await send(repoPath,from,to,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,to,message.id),{messageId:message.id,workerId:to,delivered:!0}}catch{return null}}async function deliverAfterPaneRecheck(repoPath,from,worker,body,paneDeadReason){if(!await _deps4.isPaneAlive(worker.paneId)){let message=await send(repoPath,from,worker.id,body);return console.error(`[protocol-router] Delivery failed: ${paneDeadReason} (worker=${worker.id}, msg="${body.slice(0,50)}")`),{messageId:message.id,workerId:worker.id,delivered:!1,reason:paneDeadReason}}return deliverToWorker(repoPath,from,worker,body)}async function findKnownWorker(to){let worker=await get(to);if(worker)return worker;let allWorkers=await list();return allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}async function attemptAutoSpawnDelivery(repoPath,from,to,body,worker){let alive;try{alive=await ensureWorkerAlive(worker,to)}catch(err){if(err instanceof MissingResumeSessionError)return console.error(`[protocol-router] ${err.message}`),{messageId:"",workerId:worker?.id??to,delivered:!1,reason:err.message};throw err}if(!alive)return null;return deliverAfterPaneRecheck(repoPath,from,alive.worker,body,"pane dead after spawn")}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let liveMatches=await resolveRecipient(to);if(liveMatches.length===1)return deliverAfterPaneRecheck(repoPath,from,liveMatches[0],body,"Pane died before delivery");if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve7}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve7(to),worker=await findKnownWorker(to);if(dirResolved||worker){let result2=await attemptAutoSpawnDelivery(repoPath,from,to,body,worker);if(result2)return result2}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;if(!await _deps4.isPaneAlive(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve7)=>setTimeout(resolve7,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function deliverToPane(toWorker,messageId){let worker=await get(toWorker);if(!worker||!worker.paneId)return await markFailed(messageId),!1;if(!await _deps4.isPaneAlive(worker.paneId))return await markFailed(messageId),!1;let message=await getById(messageId);if(!message||message.deliveredAt)return!1;let injected=await injectToTmuxPane(worker,message);if(injected&&worker.repoPath)await markDelivered(worker.repoPath,worker.id,messageId);else await markFailed(messageId);return injected}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var MissingResumeSessionError,_deps4,AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_orchestrator();init_should_resume();init_spawn_command();init_tmux();MissingResumeSessionError=class MissingResumeSessionError extends Error{workerId;entityId;recipientId;reason;constructor(workerId,recipientId,reason="null_session"){let suffix=recipientId?` (recipient "${recipientId}")`:"";super(`Cannot resume worker "${workerId}"${suffix}: executor has no claude_session_id recorded (reason: ${reason}). This usually means the worker predates the session-sync hook. Run \`genie reset ${workerId}\` or re-spawn the worker to recover.`);this.name="MissingResumeSessionError",this.workerId=workerId,this.entityId=workerId,this.recipientId=recipientId,this.reason=reason}};_deps4={isPaneAlive,waitForWorkerReady:null}});var exports_sdk_session_capture={};__export(exports_sdk_session_capture,{updateTurnCount:()=>updateTurnCount,startSession:()=>startSession,recordTurn:()=>recordTurn,endSession:()=>endSession});async function startSession(safePgCall,executorId,claudeSessionId,agentId,team,role,wishSlug){let sessionId=claudeSessionId??`sdk-${executorId}-${Date.now()}`;if(!await safePgCall("sdk-session-start",(sql)=>sql`INSERT INTO sessions (id, agent_id, executor_id, team, role, wish_slug, status, jsonl_path, project_path)
|
|
1814
|
+
`);await _deps3.mailboxSend(repoPath,"genie",agentId,resumePrompt)}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Resume context injection failed: ${msg}`)}}var execAsync,_deps3;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_audit();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_provider_adapters();init_registry2();init_should_resume();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2),_deps3={findAnyGroupByAssignee,mailboxSend:send,writeNativeInbox}});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}async function waitForExecutorReady(executorId,opts){let timeoutMs=opts?.timeoutMs??DEFAULT_SPAWN_TIMEOUT_MS,start=Date.now();if(!await _pgDeps.isAvailable())return{ready:!1,elapsedMs:0};try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))return{ready:!0,elapsedMs:Date.now()-start}}catch{return{ready:!1,elapsedMs:Date.now()-start}}let sql;try{sql=await _pgDeps.getConnection()}catch{return{ready:!1,elapsedMs:Date.now()-start}}return new Promise((resolve7)=>{let resolved=!1,listener=null,pollInterval=null,timeout=null,cleanup=async()=>{if(pollInterval)clearInterval(pollInterval);if(timeout)clearTimeout(timeout);if(listener)try{await listener.unlisten()}catch{}},finish=(ready)=>{if(resolved)return;resolved=!0,cleanup().then(()=>resolve7({ready,elapsedMs:Date.now()-start}))};timeout=setTimeout(()=>finish(!1),timeoutMs),sql.listen("genie_executor_state",(payload)=>{let parts=payload.split(":");if(parts.length<4)return;let[notifyExecId,,,newState]=parts;if(notifyExecId===executorId&&(newState==="running"||newState==="idle"))finish(!0)}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollInterval=setInterval(async()=>{if(resolved)return;try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))finish(!0)}catch{}},READINESS_POLL_INTERVAL_MS)})}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000,_pgDeps;var init_spawn_command=__esm(()=>{init_db();init_executor_registry();init_orchestrator();init_tmux();_pgDeps={isAvailable,getConnection,getExecutor}});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,resolveResumeSessionId:()=>resolveResumeSessionId,getInbox:()=>getInbox,deliverToPane:()=>deliverToPane,_deps:()=>_deps4,MissingResumeSessionError:()=>MissingResumeSessionError});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){try{let executor=await findExecutorByPane(paneId);if(executor&&executor.state!=="terminated"&&executor.state!=="error"){if((await waitForExecutorReady(executor.id,{timeoutMs})).ready)return!0}}catch{}let start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isExecutorCompleted(worker){if(!worker?.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);return executor!=null&&(executor.state==="done"||executor.state==="terminated")}async function isExecutorResumable(worker){if(!worker.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);if(!executor)return!1;return!["done","error","terminated"].includes(executor.state)}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await _deps4.isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}function dedupShadowsForSend(workers){let groups=new Map;for(let w of workers){let key=`${w.customName??w.id}\x00${w.team??""}`,arr=groups.get(key);if(arr)arr.push(w);else groups.set(key,[w])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b2)=>{let aExec=a.currentExecutorId!=null?1:0,bExec=b2.currentExecutorId!=null?1:0;if(aExec!==bExec)return bExec-aExec;return(b2.startedAt??"").localeCompare(a.startedAt??"")}),out.push(arr[0])}return out}async function findLiveWorkerFuzzy(recipientId){let matches=dedupShadowsForSend(await resolveRecipient(recipientId));return matches.length===1?matches[0]:null}async function findSpawnTemplate(worker){if(!worker||!worker.team||!worker.role)return null;return(await listTemplates()).find((t)=>t.team===worker.team&&t.role===worker.role)??null}async function lockedSpawnWorker(recipientId,worker,template,resumeSessionId){let lockResult=await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${recipientId}))`;let postLockLive=await findLiveWorkerFuzzy(recipientId);if(postLockLive)return{type:"existing",worker:postLockLive};if(worker)await cleanupDeadWorkers(worker.id),await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn));return{type:"spawned",...await spawnWorkerFromTemplate2(template,resumeSessionId)}});if(lockResult.type==="existing")return{worker:lockResult.worker,respawned:!1};if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await(_deps4.waitForWorkerReady??waitForWorkerReady)(lockResult.paneId),!await _deps4.isPaneAlive(lockResult.paneId))return await unregister(lockResult.worker.id),null;return{worker:lockResult.worker,respawned:!0}}async function resolveResumeSessionId(worker,template){if(template.provider!=="claude")return;if(!worker.id)throw Error("resolveResumeSessionId: worker.id is required");let decision=await shouldResume(worker.id);if(await isExecutorResumable(worker)){if(!decision.sessionId)throw new MissingResumeSessionError(worker.id)}if(!decision.sessionId)return;let{acquireResumeSessionForAttempt:acquireResumeSessionForAttempt2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await acquireResumeSessionForAttempt2(worker.id).catch(()=>null)??decision.sessionId}async function handleSpawnError(err,worker,recipientId){if(err instanceof MissingResumeSessionError)throw err;let msg=err instanceof Error?err.message:String(err);if(console.error(`[protocol-router] Spawn failed for "${recipientId}": ${msg}`),worker)await update(worker.id,{state:"error"}).catch(()=>{});return null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await _deps4.isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(await isExecutorCompleted(worker))return null;if(!process.env.TMUX)return null;if(!worker)return null;let template=await findSpawnTemplate(worker);if(!template)return null;let resumeSessionId=await resolveResumeSessionId(worker,template);try{return await lockedSpawnWorker(recipientId,worker,template,resumeSessionId)}catch(err){return handleSpawnError(err,worker,recipientId)}}async function cleanupDeadWorkers(agentId){let w=await get(agentId);if(!w)return;if(await _deps4.isPaneAlive(w.paneId))return;await unregister(w.id)}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);else console.error(`[protocol-router] Delivery failed: all paths exhausted (worker=${worker.id}, pane=${worker.paneId}, msg="${body.slice(0,50)}")`);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName,resolvedToId){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to,mailboxTo=resolvedToId??await resolveAgentId(to,resolvedTeam).catch(()=>null);if(!mailboxTo)return null;try{let message=await send(repoPath,from,mailboxTo,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,mailboxTo,message.id),{messageId:message.id,workerId:mailboxTo,delivered:!0}}catch{return null}}async function deliverAfterPaneRecheck(repoPath,from,worker,body,paneDeadReason){if(!await _deps4.isPaneAlive(worker.paneId)){let message=await send(repoPath,from,worker.id,body);return console.error(`[protocol-router] Delivery failed: ${paneDeadReason} (worker=${worker.id}, msg="${body.slice(0,50)}")`),{messageId:message.id,workerId:worker.id,delivered:!1,reason:paneDeadReason}}return deliverToWorker(repoPath,from,worker,body)}async function findKnownWorker(to){let worker=await get(to);if(worker)return worker;let allWorkers=await list();return allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}async function attemptAutoSpawnDelivery(repoPath,from,to,body,worker){let alive;try{alive=await ensureWorkerAlive(worker,to)}catch(err){if(err instanceof MissingResumeSessionError)return console.error(`[protocol-router] ${err.message}`),{messageId:"",workerId:worker?.id??to,delivered:!1,reason:err.message};throw err}if(!alive)return null;return deliverAfterPaneRecheck(repoPath,from,alive.worker,body,"pane dead after spawn")}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let resolvedTo=await resolveAgentId(to,teamName).catch(()=>null),effectiveTo=resolvedTo??to,liveMatches=await resolveRecipient(effectiveTo);if(liveMatches.length===1)return deliverAfterPaneRecheck(repoPath,from,liveMatches[0],body,"Pane died before delivery");if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve7}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve7(to),worker=await findKnownWorker(effectiveTo);if(dirResolved||worker){let result2=await attemptAutoSpawnDelivery(repoPath,from,effectiveTo,body,worker);if(result2)return result2}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName,resolvedTo);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;if(!await _deps4.isPaneAlive(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve7)=>setTimeout(resolve7,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function deliverToPane(toWorker,messageId){let worker=await get(toWorker);if(!worker||!worker.paneId)return await markFailed(messageId),!1;if(!await _deps4.isPaneAlive(worker.paneId))return await markFailed(messageId),!1;let message=await getById(messageId);if(!message||message.deliveredAt)return!1;let injected=await injectToTmuxPane(worker,message);if(injected&&worker.repoPath)await markDelivered(worker.repoPath,worker.id,messageId);else await markFailed(messageId);return injected}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var MissingResumeSessionError,_deps4,AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_orchestrator();init_should_resume();init_spawn_command();init_tmux();MissingResumeSessionError=class MissingResumeSessionError extends Error{workerId;entityId;recipientId;reason;constructor(workerId,recipientId,reason="null_session"){let suffix=recipientId?` (recipient "${recipientId}")`:"";super(`Cannot resume worker "${workerId}"${suffix}: executor has no claude_session_id recorded (reason: ${reason}). This usually means the worker predates the session-sync hook. Run \`genie reset ${workerId}\` or re-spawn the worker to recover.`);this.name="MissingResumeSessionError",this.workerId=workerId,this.entityId=workerId,this.recipientId=recipientId,this.reason=reason}};_deps4={isPaneAlive,waitForWorkerReady:null}});var exports_sdk_session_capture={};__export(exports_sdk_session_capture,{updateTurnCount:()=>updateTurnCount,startSession:()=>startSession,recordTurn:()=>recordTurn,endSession:()=>endSession});async function startSession(safePgCall,executorId,claudeSessionId,agentId,team,role,wishSlug){let sessionId=claudeSessionId??`sdk-${executorId}-${Date.now()}`;if(!await safePgCall("sdk-session-start",(sql)=>sql`INSERT INTO sessions (id, agent_id, executor_id, team, role, wish_slug, status, jsonl_path, project_path)
|
|
1815
1815
|
VALUES (${sessionId}, ${agentId}, ${executorId}, ${team??null}, ${role??null}, ${wishSlug??null}, 'active', '', '')
|
|
1816
1816
|
ON CONFLICT (id) DO UPDATE SET
|
|
1817
1817
|
agent_id = COALESCE(sessions.agent_id, EXCLUDED.agent_id),
|
|
@@ -2164,7 +2164,7 @@ Owner identities are exclusive \u2014 to spawn a separate worker under the same
|
|
|
2164
2164
|
LIMIT 1
|
|
2165
2165
|
`;if(anchor.length>0)await relinkExecutorToAgent(anchor[0].id,agentId),createdAnchor=!0,decision=await shouldResume(agentId)}}}return await recordAuditEvent("agent",agentId,"recover.surgery_applied",getActor(),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}).catch(()=>{}),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}}async function jsonlHeadMatchesCustomName(filePath,customName){let{open:open4}=await import("fs/promises"),handle=null;try{handle=await open4(filePath,"r");let buffer2=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer2,0,buffer2.length,0),head=buffer2.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
2166
2166
|
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry2=JSON.parse(trimmed);if(typeof entry2.agentName==="string"&&entry2.agentName===customName)return!0}catch{}}return!1}catch{return!1}finally{await handle?.close().catch(()=>{})}}async function resolveClaudeProjectDir(cwd){let{join:join51}=await import("path"),{homedir:homedir34}=await import("os"),sanitize=(p)=>p.replace(/[^a-zA-Z0-9]/g,"-"),claudeConfigDir5=process.env.CLAUDE_CONFIG_DIR??join51(homedir34(),".claude");return join51(claudeConfigDir5,"projects",sanitize(cwd))}async function listJsonlsByMtime(projectDir){let{readdir:readdir6,stat:stat5}=await import("fs/promises"),{join:join51}=await import("path"),entries;try{entries=await readdir6(projectDir)}catch{return[]}let jsonls=entries.filter((e)=>e.endsWith(".jsonl")),candidates=[];for(let name of jsonls){let full=join51(projectDir,name);try{let s=await stat5(full);candidates.push({name,full,mtime:s.mtimeMs})}catch{}}return candidates.sort((a,b2)=>b2.mtime-a.mtime),candidates}async function scanJsonlByCustomName(cwd,customName){let projectDir=await resolveClaudeProjectDir(cwd),candidates=await listJsonlsByMtime(projectDir);for(let candidate of candidates)if(await jsonlHeadMatchesCustomName(candidate.full,customName))return candidate.name.replace(/\.jsonl$/,"");return null}async function confirmRecover(agentId){if(!process.stdin.isTTY)return console.error(`Refusing to recover "${agentId}" without --yes in a non-interactive shell. Re-run with \`--yes\` for unattended use.`),!1;let{createInterface:createInterface3}=await import("readline"),rl=createInterface3({input:process.stdin,output:process.stdout}),answer=await new Promise((resolve7)=>{rl.question(`About to recover agent "${agentId}" (flip auto_resume, terminate stale spawning executors, resume). Continue? [y/N] `,(a)=>{rl.close(),resolve7(a.trim().toLowerCase())})});return answer==="y"||answer==="yes"}async function handleWorkerRecover(name,options){let agent=await resolveAgentForRecover(name);if(!options.yes){if(!await confirmRecover(agent.id)){console.log("Recovery cancelled.");return}}console.log(`Recovering agent "${agent.id}"...`);let surgery=await recoverSurgery(agent.id);if(surgery.flippedAutoResume)console.log(" \u2713 auto_resume re-enabled");if(surgery.staleSpawningTerminated>0)console.log(` \u2713 terminated ${surgery.staleSpawningTerminated} stale spawning executor(s)`);if(surgery.createdAnchor)console.log(" \u2713 created executor anchor from JSONL on disk");if(surgery.sessionId)console.log(` \u2713 session UUID located: ${surgery.sessionId}`);else console.error(` \u2717 no recoverable session UUID for "${agent.id}". JSONL scan in ${agent.repoPath??"<no repo_path>"} did not match custom_name="${agent.customName??agent.role??"<none>"}".`),process.exit(1);let resumeName=agent.customName??agent.role??agent.id;await handleWorkerResume(resumeName,{all:!1,noResetAttempts:!1});let post=await get(agent.id);if(post?.paneId&&post.session)console.log(` pane: ${post.paneId}`),console.log(` attach: tmux attach -t ${post.session}`)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
|
|
2167
|
-
`)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let sessionId=await acquireResumeSessionForAttempt(agent.id)??decision.sessionId,params=await buildResumeParams(agent,template,sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"worker.resume.attempted",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,claude_session_id:params.resume,pane_id:paneId}).catch(()=>{});let resumedPid=null;try{if(paneId!=="inline"){let{execSync:execSync10}=__require("child_process"),pidOutput=execSync10(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),parsedPid=Number.parseInt(pidOutput,10);if(resumedPid=parsedPid>0?parsedPid:null,!execSync10(genieTmuxCmd("list-panes -a -F '#{pane_id}'"),{encoding:"utf-8"}).split(`
|
|
2167
|
+
`)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let sessionId=await acquireResumeSessionForAttempt(agent.id)??decision.sessionId,params=await buildResumeParams(agent,template,sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"worker.resume.attempted",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,claude_session_id:params.resume,pane_id:paneId}).catch(()=>{});let resumedPid=null;try{if(paneId!=="inline"){let{execSync:execSync10}=__require("child_process"),pidOutput=execSync10(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),parsedPid=Number.parseInt(pidOutput,10);if(resumedPid=parsedPid>0?parsedPid:null,!execSync10(genieTmuxCmd("list-panes -a -F '#{pane_id}'"),{encoding:"utf-8"}).split(`
|
|
2168
2168
|
`).map((line)=>line.trim()).includes(paneId))throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pane_not_in_list");if(resumedPid!==null)try{process.kill(resumedPid,0)}catch(err){if(err.code==="ESRCH")throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pid_dead")}}}catch(err){if(err instanceof ResumePaneVanishedError)throw recordAuditEvent("worker",agent.id,"worker.resume.skipped",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,reason:err.reason,error:err.message}).catch(()=>{}),err;throw err}recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordAuditEvent("worker",agent.id,"worker.resume.completed",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,claude_session_id:params.resume}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await listForRender(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status2,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status2.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var SpawnPaneVanishedError,AgentReadinessTimeoutError,OwnerSpawnCollisionError,_spawnAutoSyncDeps,UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES,ResumePaneVanishedError;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_agent_sync();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_frontmatter();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();SpawnPaneVanishedError=class SpawnPaneVanishedError extends Error{paneId;expectedPid;reason;constructor(paneId,expectedPid,reason){super(`Spawn pane "${paneId}" (pid=${expectedPid??"unknown"}) vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched script likely failed to exec the worker binary; check ~/.genie/spawn-scripts/ and tmux server log.`);this.name="SpawnPaneVanishedError",this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}};AgentReadinessTimeoutError=class AgentReadinessTimeoutError extends Error{role;paneId;elapsedMs;constructor(role,paneId,elapsedMs){super(`Agent "${role}" did not become ready within ${Math.round(elapsedMs/1000)}s (pane ${paneId}). This likely means the worker process exited before the readiness probe succeeded. Pass tolerateReadinessTimeout:true on SpawnOptions to fall back to warn-and-proceed.`);this.name="AgentReadinessTimeoutError",this.role=role,this.paneId=paneId,this.elapsedMs=elapsedMs}};OwnerSpawnCollisionError=class OwnerSpawnCollisionError extends Error{ownerName;team;conflictId;conflictPaneId;conflictState;name="OwnerSpawnCollisionError";constructor(ownerName,team,conflictId,conflictPaneId,conflictState){super(`owner agent "${ownerName}" already alive in team "${team}"`);this.ownerName=ownerName;this.team=team;this.conflictId=conflictId;this.conflictPaneId=conflictPaneId;this.conflictState=conflictState}};_spawnAutoSyncDeps={resolveAgent:resolve6,loadIdentity,syncSingleAgentByName,findWorkspace,parseFrontmatter,existsSync:existsSync42,readFileSync:readFileSync27,stderr:(line)=>console.error(line)};UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"]);ResumePaneVanishedError=class ResumePaneVanishedError extends Error{agentId;paneId;expectedPid;reason;constructor(agentId,paneId,expectedPid,reason){super(`Resumed pane "${paneId}" (pid=${expectedPid??"unknown"}) for agent "${agentId}" vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched resume script likely failed to exec the worker binary.`);this.name="ResumePaneVanishedError",this.agentId=agentId,this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}}});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile11,readdir as readdir6}from"fs/promises";import{homedir as homedir34}from"os";import{join as join51}from"path";function getCodexDir(){return join51(homedir34(),".codex")}function getSessionsDir(){return join51(getCodexDir(),"sessions")}function getStateDbPath(){return join51(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join51(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join51(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join51(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join51(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile11(filePath,"utf-8"),nlIdx=content.indexOf(`
|
|
2169
2169
|
`),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry2=JSON.parse(firstLine);if(entry2.type==="session_meta"&&entry2.payload?.cwd)return{cwd:entry2.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query2=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query2.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query:query2}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile11(logPath,"utf-8")}catch{return[]}return content.split(`
|
|
2170
2170
|
`).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result2=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result2=result2.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
|
|
@@ -4068,7 +4068,7 @@ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);i
|
|
|
4068
4068
|
Or run the canonical wizard from a Claude session:
|
|
4069
4069
|
/genie:omni
|
|
4070
4070
|
|
|
4071
|
-
`);return}await handleOmniRegistration(name,{...options,roles})}function registerAgentRegister(parent){parent.command("register <name>").description("Register an agent locally and auto-register in Omni when configured").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").option("--skip-omni","Skip Omni auto-registration (prints a stderr warning explaining the cost)").option("--allow-symlink","Accept a symlinked AGENTS.md (default: rejected \u2014 usually indicates --dir was pointed at the wrong folder)").action(async(name,options)=>{try{await handleAgentRegister(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentResume(parent){parent.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").option("--no-reset-attempts","Preserve resumeAttempts counter (scheduler auto-resume use)").action(async(name,options)=>{try{await handleWorkerResume(name,{all:options.all,noResetAttempts:options.resetAttempts===!1})}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_msg();function registerAgentSend(parent){parent.command("send <body>").description("Send a direct message to an agent (hierarchy-enforced)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--broadcast","Send to all direct reports").action(async(body,options)=>{try{let from=options.from??await detectSenderIdentity(options.team);if(options.broadcast){await handleBroadcast2(from,body,options.team);return}await handleDirectMessage(from,options.to,body,options.team)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}async function isTeamLeader(agentName,teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName);return agentName===leaderName}catch{return!1}}async function checkHierarchy(from,to){if(isCliSender(from))return{allowed:!0};if(from===to)return{allowed:!0};try{let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),team=process.env.GENIE_TEAM,[senderId,recipientId]=await Promise.all([registry.resolveAgentId(from,team),registry.resolveAgentId(to,team)]),sender=senderId?await registry.getAgent(senderId):null,recipient=recipientId?await registry.getAgent(recipientId):null;if(!sender||!recipient)return{allowed:!0};if(recipient.reportsTo===from||recipient.reportsTo===sender.id)return{allowed:!0};if(sender.reportsTo===to||sender.reportsTo===recipient.id)return{allowed:!0};if(sender.reportsTo&&sender.reportsTo===recipient.reportsTo)return{allowed:!0};if(sender.team&&sender.team===recipient.team&&await isTeamLeader(from,sender.team))return{allowed:!0};if(!sender.reportsTo&&sender.team&&sender.team===recipient.team)return{allowed:!0};let manager=sender.reportsTo??"your manager";return{allowed:!1,reason:`Cannot reach "${to}". Escalate to ${manager}.`}}catch{return{allowed:!0}}}async function handleDirectMessage(from,to,body,team){let{allowed,reason}=await checkHierarchy(from,to);if(!allowed)console.error(`Error: ${reason}`),process.exit(1);let{checkSendScope:checkSendScope2}=await Promise.resolve().then(() => (init_msg(),exports_msg)),repoPath=process.cwd(),scopeError=await checkSendScope2(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox)),senderActor={actorType:"local",actorId:from},recipientActor={actorType:"local",actorId:to},conv=await taskService.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await taskService.addMember(conv.id,senderActor),await taskService.addMember(conv.id,recipientActor),await mailbox.send(repoPath,from,to,body);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}try{let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=team??process.env.GENIE_TEAM;if(currentTeam){let nativeName=await nativeTeams.resolveNativeMemberName(currentTeam,to);if(nativeName)await nativeTeams.writeNativeInbox(currentTeam,nativeName,nativeMsg)}}catch{}console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}async function deliverBroadcastToMembers(deps,repoPath,from,teamName,body){let members=await deps.listMembers(teamName)??[],results=[];for(let member of members){if(member===from)continue;try{let r=await deps.sendMessage(repoPath,from,member,body,teamName);results.push({member,delivered:r.delivered,reason:r.reason})}catch(err){results.push({member,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return results}async function handleBroadcast2(from,body,team){let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),repoPath=process.cwd(),teamName=team??process.env.GENIE_TEAM;if(!teamName)console.error("Error: Could not detect team. Use --team <name>."),process.exit(1);let senderActor={actorType:"local",actorId:from},conv=await taskService.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await taskService.addMember(conv.id,senderActor);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}let{listMembers:listMembers2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),fanoutResults=await deliverBroadcastToMembers({listMembers:listMembers2,sendMessage:protocolRouter.sendMessage},repoPath,from,teamName,body);console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);let deliveredCount=fanoutResults.filter((r)=>r.delivered).length;console.log(` Fan-out: ${deliveredCount}/${fanoutResults.length} members reached`);for(let r of fanoutResults)if(!r.delivered)console.log(` \u26A0 ${r.member}: ${r.reason??"delivery failed"}`)}init_term_format();function printAgentFields(agent){if(console.log(""),console.log(`AGENT: ${agent.customName??agent.role??agent.id}`),console.log("\u2500".repeat(60)),console.log(` ${padRight("ID:",20)} ${agent.id}`),agent.role)console.log(` ${padRight("Role:",20)} ${agent.role}`);if(agent.customName)console.log(` ${padRight("Name:",20)} ${agent.customName}`);if(agent.team)console.log(` ${padRight("Team:",20)} ${agent.team}`);console.log(` ${padRight("Started:",20)} ${agent.startedAt}`)}function printExecutorFields(executor){if(console.log(""),console.log("Current Executor:"),console.log("\u2500".repeat(60)),console.log(` ${padRight("Executor ID:",20)} ${executor.id}`),console.log(` ${padRight("Provider:",20)} ${executor.provider}`),console.log(` ${padRight("Transport:",20)} ${executor.transport}`),console.log(` ${padRight("State:",20)} ${executor.state}`),executor.pid)console.log(` ${padRight("PID:",20)} ${executor.pid}`);if(executor.tmuxSession)console.log(` ${padRight("Tmux Session:",20)} ${executor.tmuxSession}`);if(executor.tmuxPaneId)console.log(` ${padRight("Tmux Pane:",20)} ${executor.tmuxPaneId}`);if(executor.worktree)console.log(` ${padRight("Worktree:",20)} ${executor.worktree}`);if(console.log(` ${padRight("Started:",20)} ${executor.startedAt}`),executor.endedAt)console.log(` ${padRight("Ended:",20)} ${executor.endedAt}`)}async function showAgent(name,json2){let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),team=process.env.GENIE_TEAM,id=await registry.resolveAgentId(name,team),agent=id?await registry.getAgent(id):null;if(!agent)console.error(`Agent "${name}" not found.`),process.exit(1);if(json2){let executor=agent.currentExecutorId?await executorRegistry.getExecutor(agent.currentExecutorId):null;console.log(JSON.stringify({agent,executor},null,2));return}if(printAgentFields(agent),agent.currentExecutorId){let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(executor)printExecutorFields(executor)}else console.log(`
|
|
4071
|
+
`);return}await handleOmniRegistration(name,{...options,roles})}function registerAgentRegister(parent){parent.command("register <name>").description("Register an agent locally and auto-register in Omni when configured").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").option("--skip-omni","Skip Omni auto-registration (prints a stderr warning explaining the cost)").option("--allow-symlink","Accept a symlinked AGENTS.md (default: rejected \u2014 usually indicates --dir was pointed at the wrong folder)").action(async(name,options)=>{try{await handleAgentRegister(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentResume(parent){parent.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").option("--no-reset-attempts","Preserve resumeAttempts counter (scheduler auto-resume use)").action(async(name,options)=>{try{await handleWorkerResume(name,{all:options.all,noResetAttempts:options.resetAttempts===!1})}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_msg();function registerAgentSend(parent){parent.command("send <body>").description("Send a direct message to an agent (hierarchy-enforced)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--broadcast","Send to all direct reports").action(async(body,options)=>{try{let from=options.from??await detectSenderIdentity(options.team);if(options.broadcast){await handleBroadcast2(from,body,options.team);return}await handleDirectMessage(from,options.to,body,options.team)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}async function isTeamLeader(agentName,teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName);return agentName===leaderName}catch{return!1}}async function checkHierarchy(from,to){if(isCliSender(from))return{allowed:!0};if(from===to)return{allowed:!0};try{let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),team=process.env.GENIE_TEAM,[senderId,recipientId]=await Promise.all([registry.resolveAgentId(from,team),registry.resolveAgentId(to,team)]),sender=senderId?await registry.getAgent(senderId):null,recipient=recipientId?await registry.getAgent(recipientId):null;if(!sender||!recipient)return{allowed:!0};if(recipient.reportsTo===from||recipient.reportsTo===sender.id)return{allowed:!0};if(sender.reportsTo===to||sender.reportsTo===recipient.id)return{allowed:!0};if(sender.reportsTo&&sender.reportsTo===recipient.reportsTo)return{allowed:!0};if(sender.team&&sender.team===recipient.team&&await isTeamLeader(from,sender.team))return{allowed:!0};if(!sender.reportsTo&&sender.team&&sender.team===recipient.team)return{allowed:!0};let manager=sender.reportsTo??"your manager";return{allowed:!1,reason:`Cannot reach "${to}". Escalate to ${manager}.`}}catch{return{allowed:!0}}}async function handleDirectMessage(from,to,body,team){let{allowed,reason}=await checkHierarchy(from,to);if(!allowed)console.error(`Error: ${reason}`),process.exit(1);let{checkSendScope:checkSendScope2}=await Promise.resolve().then(() => (init_msg(),exports_msg)),repoPath=process.cwd(),scopeError=await checkSendScope2(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox)),registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),teamScope=team??process.env.GENIE_TEAM,toAgentId=await registry.resolveAgentIdStrict(to,teamScope),senderActor={actorType:"local",actorId:from},recipientActor={actorType:"local",actorId:toAgentId},conv=await taskService.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await taskService.addMember(conv.id,senderActor),await taskService.addMember(conv.id,recipientActor),await mailbox.send(repoPath,from,toAgentId,body);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}try{let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=team??process.env.GENIE_TEAM;if(currentTeam){let nativeName=await nativeTeams.resolveNativeMemberName(currentTeam,to);if(nativeName)await nativeTeams.writeNativeInbox(currentTeam,nativeName,nativeMsg)}}catch{}console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}async function deliverBroadcastToMembers(deps,repoPath,from,teamName,body){let members=await deps.listMembers(teamName)??[],results=[];for(let member of members){if(member===from)continue;try{let r=await deps.sendMessage(repoPath,from,member,body,teamName);results.push({member,delivered:r.delivered,reason:r.reason})}catch(err){results.push({member,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return results}async function handleBroadcast2(from,body,team){let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),repoPath=process.cwd(),teamName=team??process.env.GENIE_TEAM;if(!teamName)console.error("Error: Could not detect team. Use --team <name>."),process.exit(1);let senderActor={actorType:"local",actorId:from},conv=await taskService.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await taskService.addMember(conv.id,senderActor);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}let{listMembers:listMembers2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),fanoutResults=await deliverBroadcastToMembers({listMembers:listMembers2,sendMessage:protocolRouter.sendMessage},repoPath,from,teamName,body);console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);let deliveredCount=fanoutResults.filter((r)=>r.delivered).length;console.log(` Fan-out: ${deliveredCount}/${fanoutResults.length} members reached`);for(let r of fanoutResults)if(!r.delivered)console.log(` \u26A0 ${r.member}: ${r.reason??"delivery failed"}`)}init_term_format();function printAgentFields(agent){if(console.log(""),console.log(`AGENT: ${agent.customName??agent.role??agent.id}`),console.log("\u2500".repeat(60)),console.log(` ${padRight("ID:",20)} ${agent.id}`),agent.role)console.log(` ${padRight("Role:",20)} ${agent.role}`);if(agent.customName)console.log(` ${padRight("Name:",20)} ${agent.customName}`);if(agent.team)console.log(` ${padRight("Team:",20)} ${agent.team}`);console.log(` ${padRight("Started:",20)} ${agent.startedAt}`)}function printExecutorFields(executor){if(console.log(""),console.log("Current Executor:"),console.log("\u2500".repeat(60)),console.log(` ${padRight("Executor ID:",20)} ${executor.id}`),console.log(` ${padRight("Provider:",20)} ${executor.provider}`),console.log(` ${padRight("Transport:",20)} ${executor.transport}`),console.log(` ${padRight("State:",20)} ${executor.state}`),executor.pid)console.log(` ${padRight("PID:",20)} ${executor.pid}`);if(executor.tmuxSession)console.log(` ${padRight("Tmux Session:",20)} ${executor.tmuxSession}`);if(executor.tmuxPaneId)console.log(` ${padRight("Tmux Pane:",20)} ${executor.tmuxPaneId}`);if(executor.worktree)console.log(` ${padRight("Worktree:",20)} ${executor.worktree}`);if(console.log(` ${padRight("Started:",20)} ${executor.startedAt}`),executor.endedAt)console.log(` ${padRight("Ended:",20)} ${executor.endedAt}`)}async function showAgent(name,json2){let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),team=process.env.GENIE_TEAM,id=await registry.resolveAgentId(name,team),agent=id?await registry.getAgent(id):null;if(!agent)console.error(`Agent "${name}" not found.`),process.exit(1);if(json2){let executor=agent.currentExecutorId?await executorRegistry.getExecutor(agent.currentExecutorId):null;console.log(JSON.stringify({agent,executor},null,2));return}if(printAgentFields(agent),agent.currentExecutorId){let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(executor)printExecutorFields(executor)}else console.log(`
|
|
4072
4072
|
No active executor.`);console.log("")}function registerAgentShow(parent){parent.command("show <name>").description("Show agent identity and current executor detail").option("--json","Output as JSON").action(async(name,options)=>{try{await showAgent(name,options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_provider_models();init_agents();function parseNumericFlag(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}function registerAgentSpawn(parent){parent.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude, codex, or claude-sdk").option("--team <team>","Team name").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--no-auto-sync","Disable auto-registration from workspace agents directory").option("--prompt <prompt>","Initial prompt (first user message)").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").action(async(name,options)=>{if(options.prompt)options.initialPrompt=options.prompt;if(options.autoSync===!1)options.noAutoSync=!0;try{validateProviderModel({provider:options.provider??null,model:options.model??null})}catch(error2){if(error2 instanceof CrossProviderModelError)console.error(`Error: ${error2.message}`),process.exit(1);throw error2}try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentStop(parent){parent.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}function registerAgentCommands(program2){let agent=program2.command("agent").description("Agent lifecycle management");registerAgentSpawn(agent),registerAgentStop(agent),registerAgentResume(agent),registerAgentRecover(agent),registerAgentKill(agent),registerAgentList(agent),registerAgentShow(agent),registerAgentObserve(agent),registerAgentAnswer(agent),registerAgentRegister(agent),registerAgentDirectory(agent),registerAgentInbox(agent),registerAgentBrief(agent),registerAgentLog(agent),registerAgentSend(agent),agent.on("command:*",(operands)=>{let cmd=operands[0],available=agent.commands.map((c)=>c.name()).join(", ");agent.error(`Unknown agent command '${cmd}'. Available: ${available}`)})}init_agents();async function handleTuiMode(){let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2()}async function findTauriBinary(){let{existsSync:existsSync53}=await import("fs"),{join:join65,dirname:dirname22}=await import("path"),{execSync:execSync11}=await import("child_process"),appName="genie-desktop",rootDir=join65(dirname22(new URL(import.meta.url).pathname),"..",".."),localBin=[join65(rootDir,"packages","genie-app","src-tauri","target","release","genie-desktop"),join65(rootDir,"packages","genie-app","src-tauri","target","debug","genie-desktop"),join65(rootDir,"dist","app","genie-desktop"),"/usr/local/bin/genie-desktop"].find((p)=>existsSync53(p));if(localBin)return localBin;try{return execSync11("which genie-desktop",{stdio:"ignore"}),"genie-desktop"}catch{return}}function registerAppCommand(program2){program2.command("app").description("Launch Genie desktop app (backend sidecar + views)").option("--backend-only","Start only the backend sidecar (IPC on stdin/stdout)").option("--tui","Fall back to terminal UI mode").option("--dev","Development mode").action(async(options)=>{if(options.tui){await handleTuiMode();return}if(options.backendOnly){await Promise.resolve().then(() => (init_src_backend(),exports_src_backend));return}let tauriBin=await findTauriBinary();if(tauriBin){console.log("\x1B[35m\u25C6 Genie App\x1B[0m Launching desktop...");let{execFileSync:execFileSync3}=await import("child_process");try{execFileSync3(tauriBin,[],{stdio:"inherit"})}catch{}return}console.log("\x1B[35m\u25C6 Genie App\x1B[0m Starting backend sidecar..."),console.log("\x1B[2mDesktop binary not found \u2014 running in sidecar mode.\x1B[0m"),console.log("\x1B[2mPG bridge + PTY manager + IPC on stdin/stdout\x1B[0m"),console.log(`\x1B[2mUse --tui for terminal UI, or pipe to a frontend shell.\x1B[0m
|
|
4073
4073
|
`),await Promise.resolve().then(() => (init_src_backend(),exports_src_backend))})}init_claude_sdk_remote_approval();init_workspace();async function handleRequest(options){let ws=findWorkspace(),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0,timeoutSec=options.timeout?Number(options.timeout):permissions?.timeout??300,defaultAction=permissions?.defaultAction??"deny",timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(`cli-${process.pid}`,options.agent,options.tool,options.input,timeoutAt);if(console.log(`Approval created: ${approvalId}`),options.wait){console.log(`Waiting for resolution (timeout: ${timeoutSec}s, default: ${defaultAction})...`);let decision=await waitForResolution(approvalId,timeoutAt,defaultAction);if(console.log(`Decision: ${decision}`),decision==="deny")process.exit(1)}}async function handleResolve(id,options){if(options.decision!=="allow"&&options.decision!=="deny")console.error('Error: --decision must be "allow" or "deny"'),process.exit(1);let actor=process.env.GENIE_AGENT_NAME||options.by;if(await resolveApproval(id,options.decision,actor))console.log(`Approval ${id} resolved: ${options.decision} by ${options.by}`);else console.error(`Error: Approval ${id} not found or already resolved`),process.exit(1)}async function handleList(options){let rows=await listPendingApprovals(options.agent);if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No pending approvals.");return}console.log(` ${"ID".padEnd(38)} ${"AGENT".padEnd(20)} ${"TOOL".padEnd(15)} TIMEOUT`),console.log(` ${"\u2500".repeat(85)}`);for(let row2 of rows){let timeout=new Date(row2.timeout_at).toLocaleTimeString();console.log(` ${String(row2.id).padEnd(38)} ${String(row2.agent_name).padEnd(20)} ${String(row2.tool_name).padEnd(15)} ${timeout}`)}console.log(`
|
|
4074
4074
|
${rows.length} pending approval${rows.length===1?"":"s"}`)}function registerApprovalCommands(program2){let approval=program2.command("approval").description("Remote approval queue management");approval.command("request").description("Create an approval request (for tmux-path agents)").requiredOption("--tool <name>","Tool name requiring approval").requiredOption("--input <preview>","Tool input preview text").requiredOption("--agent <name>","Agent name requesting approval").option("--wait","Block until the approval is resolved").option("--timeout <seconds>","Timeout in seconds (overrides workspace config)").action(async(options)=>{try{await handleRequest(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),approval.command("resolve <id>").description("Resolve a pending approval").requiredOption("--decision <decision>","Decision: allow or deny").option("--by <actor>",'Display label for decision maker (defaults to GENIE_AGENT_NAME or "cli")',"cli").action(async(id,options)=>{try{await handleResolve(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),approval.command("list").description("List pending approvals").option("--agent <name>","Filter by agent name").option("--json","Output as JSON").action(async(options)=>{try{await handleList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_audit();init_db();var V2_SELECT=`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260504.
|
|
3
|
+
"version": "4.260504.10",
|
|
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.260504.
|
|
3
|
+
"version": "4.260504.10",
|
|
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"
|