@automagik/genie 4.260331.7 → 4.260331.9
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/.claude-plugin/marketplace.json +1 -1
- package/dist/genie.js +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
- package/src/lib/task-service.ts +4 -0
- package/src/term-commands/team.ts +14 -11
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.260331.
|
|
13
|
+
"version": "4.260331.9",
|
|
14
14
|
"source": "./plugins/genie",
|
|
15
15
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
|
|
16
16
|
}
|
package/dist/genie.js
CHANGED
|
@@ -583,7 +583,7 @@ Run 'genie agent list' to list agents.`)}async function resolveTarget(target,opt
|
|
|
583
583
|
LIMIT 100
|
|
584
584
|
`).map((r)=>({id:r.id,requestType:r.request_type,senderId:r.sender_id,body:r.body,createdAt:r.created_at instanceof Date?r.created_at.toISOString():String(r.created_at)}))}async function generateBrief(options){let teamConfig=await getTeam(options.team);if(!teamConfig)throw Error(`Team not found: ${options.team}`);let since=await resolveSince(options),repoPath=options.repoPath??teamConfig.repo,agentName=options.agent??teamConfig.leader??null,agentIdentifiers=agentName?[agentName]:[],[unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster]=await Promise.all([agentIdentifiers.length>0?getUnread(repoPath,agentIdentifiers):Promise.resolve([]),getTaskMessages(options.team,since),listRuntimeEvents({team:options.team,since,limit:100}),getPendingRequestMessages(options.team),getTeamRoster(options.team)]);return{team:options.team,agent:agentName,since,unreadMessages,taskMessages,recentEvents,pendingRequests,teamRoster}}function truncate(text,max){return text.length>max?`${text.slice(0,max)}...`:text}function formatUnreadSection(messages2){if(messages2.length===0)return[];let lines=[`## Unread Messages (${messages2.length})`];for(let msg of messages2)lines.push(`- **${msg.from}**: ${truncate(msg.body,120)}`);return lines.push(""),lines}function formatTaskMessagesSection(messages2){if(messages2.length===0)return[];let lines=[`## Task Updates (${messages2.length})`],byTask=new Map;for(let msg of messages2){let existing=byTask.get(msg.taskId)??[];existing.push(msg),byTask.set(msg.taskId,existing)}for(let[taskId,msgs]of byTask){lines.push(`### ${taskId}: ${msgs[0].taskTitle}`);for(let msg of msgs)lines.push(`- [${msg.senderType}:${msg.senderId}] ${truncate(msg.body,100)}`)}return lines.push(""),lines}function formatRequestsSection(requests){if(requests.length===0)return[];let lines=[`## Pending Requests (${requests.length})`];for(let req of requests)lines.push(`- [${req.requestType}] ${req.senderId}: ${truncate(req.body,80)}`);return lines.push(""),lines}function formatRosterSection(roster){if(roster.length===0)return[];let lines=[`## Team Roster (${roster.length})`];for(let member of roster){let state=member.executorState??"offline",icon=STATE_ICONS[state]??"\u25CC";lines.push(`- ${icon} **${member.agentId}** (${member.role??"unassigned"}): ${state}`)}return lines.push(""),lines}function formatEventsSection(events){if(events.length===0)return[];let lines=[`## Recent Events (${events.length})`],tail=events.slice(-10);for(let evt of tail){let ts3=evt.timestamp.slice(11,16);lines.push(`- ${ts3} [${evt.kind}] ${evt.agent}: ${truncate(evt.text,80)}`)}if(events.length>10)lines.push(` _(${events.length-10} more events)_`);return lines.push(""),lines}function formatBrief(brief){let lines=[`# BRIEF \u2014 ${brief.team}${brief.agent?` ${brief.agent}`:""}`,`Since: ${brief.since}`,"",...formatUnreadSection(brief.unreadMessages),...formatTaskMessagesSection(brief.taskMessages),...formatRequestsSection(brief.pendingRequests),...formatRosterSection(brief.teamRoster),...formatEventsSection(brief.recentEvents)];if(!(brief.unreadMessages.length+brief.taskMessages.length+brief.pendingRequests.length+brief.recentEvents.length>0))lines.push("_No activity since last session._","");return lines.join(`
|
|
585
585
|
`)}var STATE_ICONS;var init_brief=__esm(()=>{init_db();init_mailbox();init_runtime_events();init_team_manager();STATE_ICONS={working:"\u25CF",idle:"\u25CB",error:"\u2718"}});var exports_agent_sync={};__export(exports_agent_sync,{watchAgentDirectory:()=>watchAgentDirectory,syncAgentDirectory:()=>syncAgentDirectory,printSyncResult:()=>printSyncResult});import{execSync as execSync4}from"child_process";import{existsSync as existsSync18,watch as fsWatch,readdirSync as readdirSync5,realpathSync as realpathSync2}from"fs";import{join as join19}from"path";function getGitRemoteUrl(dir){try{return execSync4(`git -C "${dir}" config --get remote.origin.url`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function extractOrgRepo(remoteUrl){let sshMatch=remoteUrl.match(/[^/:]+\/[^/]+?(?:\.git)?$/);if(sshMatch)return sshMatch[0].replace(/\.git$/,"");return null}function getRepoPathForAgent(agentDir){let reposLink=join19(agentDir,"repos");try{if(!existsSync18(reposLink))return null;let target=realpathSync2(reposLink);if(!existsSync18(target))return null;return target}catch{return null}}function discoverAgents(workspaceRoot){let agentsDir=join19(workspaceRoot,"agents");if(!existsSync18(agentsDir))return[];let agents=[];try{let entries=readdirSync5(agentsDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let agentDir=join19(agentsDir,entry.name);if(!existsSync18(join19(agentDir,"AGENTS.md")))continue;agents.push({name:entry.name,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)})}}catch{}return agents}function discoverSingleAgent(workspaceRoot,agentName){let agentDir=join19(workspaceRoot,"agents",agentName);if(!existsSync18(join19(agentDir,"AGENTS.md")))return null;return{name:agentName,dir:agentDir,repoUrl:getGitRemoteUrl(agentDir),productRepo:getRepoPathForAgent(agentDir)}}async function syncAgentDirectory(workspaceRoot){let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]},agents=discoverAgents(workspaceRoot),discoveredNames=new Set(agents.map((a)=>a.name));for(let agent of agents)try{await syncSingleAgent(agent,result)}catch(err){result.errors.push({name:agent.name,error:err instanceof Error?err.message:String(err)})}return await removeMissingAgents(discoveredNames,result),result}function printSyncResult(result){if(result.registered.length>0)console.log(` Registered: ${result.registered.join(", ")}`);if(result.updated.length>0)console.log(` Updated: ${result.updated.join(", ")}`);if(result.reactivated.length>0)console.log(` Reactivated: ${result.reactivated.join(", ")}`);if(result.archived.length>0)console.log(` Removed: ${result.archived.join(", ")}`);if(result.unchanged.length>0)console.log(` Unchanged: ${result.unchanged.join(", ")}`);for(let err of result.errors)console.error(` Error (${err.name}): ${err.error}`);let total=result.registered.length+result.updated.length+result.unchanged.length+result.reactivated.length;console.log(`
|
|
586
|
-
Sync complete: ${total} active agent(s), ${result.archived.length} removed.`)}async function removeMissingAgents(discoveredNames,result){try{let entries=await ls();for(let entry of entries){if(discoveredNames.has(entry.name))continue;if(entry.scope==="built-in")continue;if(!entry.dir||!entry.dir.includes("/agents/"))continue;if(await rm3(entry.name))result.archived.push(entry.name)}}catch{}}async function syncSingleAgent(agent,result){let repoPath=(agent.repoUrl?extractOrgRepo(agent.repoUrl):null)??agent.repoUrl??agent.dir,existing=await get2(agent.name);if(!existing){await add({name:agent.name,dir:agent.dir,repo:repoPath,promptMode:"append"}),result.registered.push(agent.name);return}if(existing.repo!==repoPath||existing.dir!==agent.dir)await edit(agent.name,{dir:agent.dir,repo:repoPath}),result.updated.push(agent.name);else result.unchanged.push(agent.name)}async function syncSingleAgentByName(workspaceRoot,agentName){let agent=discoverSingleAgent(workspaceRoot,agentName);if(!agent)return"not-found";let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]};if(await syncSingleAgent(agent,result),result.registered.length>0)return"registered";if(result.updated.length>0)return"updated";return"unchanged"}function watchAgentDirectory(workspaceRoot,options){let agentsDir=join19(workspaceRoot,"agents");if(!existsSync18(agentsDir))return null;let debounceTimer=null,pendingChanges=new Set,processChanges=async()=>{let names=[...pendingChanges];pendingChanges.clear();for(let name of names)try{let action=await processWatchedAgent(workspaceRoot,agentsDir,name);if(action)options?.onSync?.(name,action)}catch{}},watcher=fsWatch(agentsDir,{persistent:!1},(_event,filename)=>{if(!filename)return;let name=filename.split("/")[0];if(!name||name.startsWith("."))return;if(pendingChanges.add(name),debounceTimer)clearTimeout(debounceTimer);debounceTimer=setTimeout(processChanges,2000)});return{close:()=>{if(debounceTimer)clearTimeout(debounceTimer);watcher.close()}}}async function processWatchedAgent(workspaceRoot,agentsDir,name){let agentDir=join19(agentsDir,name);if(existsSync18(agentDir)&&existsSync18(join19(agentDir,"AGENTS.md"))){let action=await syncSingleAgentByName(workspaceRoot,name);return action!=="unchanged"&&action!=="not-found"?action:null}if(!existsSync18(agentDir)){if(await rm3(name))return"removed"}return null}var init_agent_sync=__esm(()=>{init_agent_directory()});var exports_workspace={};__export(exports_workspace,{scanAgents:()=>scanAgents2,getWorkspaceConfig:()=>getWorkspaceConfig,findWorkspace:()=>findWorkspace});import{existsSync as existsSync19,readFileSync as readFileSync9,readdirSync as readdirSync6}from"fs";import{dirname as dirname5,join as join20,resolve as resolve4,sep}from"path";function findWorkspace(cwd){let startDir=resolve4(cwd??process.cwd()),current=startDir;while(!0){let candidate=join20(current,WORKSPACE_MARKER);if(existsSync19(candidate)){let agent=detectAgent(startDir,current);return{root:current,agent:agent??void 0}}let parent=dirname5(current);if(parent===current)break;current=parent}return null}function detectAgent(startDir,workspaceRoot){let agentsDir=join20(workspaceRoot,"agents"),relative=startDir.slice(agentsDir.length);if(!startDir.startsWith(agentsDir)||relative.length>0&&relative[0]!==sep)return null;let parts=relative.split(sep).filter(Boolean);if(parts.length===0)return null;let agentName=parts[0],agentsMd=join20(agentsDir,agentName,"AGENTS.md");if(existsSync19(agentsMd))return agentName;return null}function getWorkspaceConfig(root){let configPath2=join20(root,WORKSPACE_MARKER),raw=readFileSync9(configPath2,"utf-8");return JSON.parse(raw)}function scanAgents2(root){let agentsDir=join20(root,"agents");if(!existsSync19(agentsDir))return[];try{return readdirSync6(agentsDir,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&existsSync19(join20(agentsDir,d.name,"AGENTS.md"))).map((d)=>d.name).sort()}catch{return[]}}var WORKSPACE_MARKER=".genie/workspace.json";var init_workspace=()=>{};function padRight(str2,len){return str2.length>=len?str2:str2+" ".repeat(len-str2.length)}function truncate2(str2,len){return str2.length<=len?str2:`${str2.slice(0,len-1)}\u2026`}function formatDate(iso){if(!iso)return"-";return new Date(iso).toLocaleDateString("en-US",{month:"short",day:"numeric"})}function formatRelativeTimestamp(ts3){let d=new Date(ts3),diffMs=Date.now()-d.getTime();if(diffMs<60000)return`${Math.floor(diffMs/1000)}s ago`;if(diffMs<3600000)return`${Math.floor(diffMs/60000)}m ago`;if(diffMs<86400000)return`${Math.floor(diffMs/3600000)}h ago`;return d.toISOString().replace("T"," ").slice(0,19)}function formatTimestamp(iso,opts){if(!iso)return opts?.fallback??"-";let d=iso instanceof Date?iso:new Date(iso),fmt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return d.toLocaleString("en-US",fmt)}function formatTime(iso,opts){try{let date=new Date(iso),fmt={hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return date.toLocaleTimeString("en-US",fmt)}catch{return opts?.fallback??"??:??"}}var exports_task_service={};__export(exports_task_service,{updateTask:()=>updateTask,updateMessage:()=>updateMessage,untagTask:()=>untagTask,unblockTask:()=>unblockTask,unarchiveTask:()=>unarchiveTask,unarchiveProject:()=>unarchiveProject,tagTask:()=>tagTask,setRelease:()=>setRelease,setPreference:()=>setPreference,sendMessage:()=>sendMessage,resolveTaskId:()=>resolveTaskId,resolveChannels:()=>resolveChannels,removeMember:()=>removeMember,removeDependency:()=>removeDependency,removeActor:()=>removeActor,releaseTask:()=>releaseTask,moveTask:()=>moveTask,markDone:()=>markDone,listTypes:()=>listTypes,listTasksForActor:()=>listTasksForActor,listTasks:()=>listTasks,listTags:()=>listTags,listReleases:()=>listReleases,listProjectsFiltered:()=>listProjectsFiltered,listProjects:()=>listProjects,listConversations:()=>listConversations,linkTask:()=>linkTask,getType:()=>getType,getTaskTags:()=>getTaskTags,getTaskActors:()=>getTaskActors,getTask:()=>getTask,getStageLog:()=>getStageLog,getProjectByRepoPath:()=>getProjectByRepoPath,getProjectByName:()=>getProjectByName,getPreferences:()=>getPreferences,getMessages:()=>getMessages,getMessage:()=>getMessage,getMembers:()=>getMembers,getDependents:()=>getDependents,getConversation:()=>getConversation,getCheckoutOwner:()=>getCheckoutOwner,getBlockingDependencies:()=>getBlockingDependencies,getBlockers:()=>getBlockers,forceUnlockTask:()=>forceUnlockTask,findOrCreateConversation:()=>findOrCreateConversation,expireStaleCheckouts:()=>expireStaleCheckouts,ensureProject:()=>ensureProject,deletePreference:()=>deletePreference,createType:()=>createType,createTask:()=>createTask,createTag:()=>createTag,createProject:()=>createProject,commentOnTask:()=>commentOnTask,checkoutTask:()=>checkoutTask,blockTask:()=>blockTask,assignTask:()=>assignTask,archiveTask:()=>archiveTask,archiveProject:()=>archiveProject,archiveBoard:()=>archiveBoard,addMember:()=>addMember,addDependency:()=>addDependency});import{execSync as execSync5}from"child_process";function str2(v){return v!=null?String(v):null}function strOrDefault(v,def){return v!=null?String(v):def}function mapTask(row){return{id:row.id,seq:row.seq,parentId:str2(row.parent_id),repoPath:row.repo_path,projectId:str2(row.project_id),genieOsFolderId:str2(row.genie_os_folder_id),wishFile:str2(row.wish_file),groupName:str2(row.group_name),title:row.title,description:str2(row.description),acceptanceCriteria:str2(row.acceptance_criteria),typeId:row.type_id,stage:row.stage,status:row.status,priority:row.priority,startDate:str2(row.start_date),dueDate:str2(row.due_date),estimatedEffort:str2(row.estimated_effort),startedAt:str2(row.started_at),endedAt:str2(row.ended_at),blockedReason:str2(row.blocked_reason),releaseId:str2(row.release_id),checkoutRunId:str2(row.checkout_run_id),executionLockedAt:str2(row.execution_locked_at),checkoutTimeoutMs:row.checkout_timeout_ms??600000,sessionId:str2(row.session_id),paneId:str2(row.pane_id),traceId:str2(row.trace_id),boardId:str2(row.board_id),columnId:str2(row.column_id),externalId:str2(row.external_id),externalUrl:str2(row.external_url),archivedAt:str2(row.archived_at),metadata:row.metadata??{},createdAt:strOrDefault(row.created_at,""),updatedAt:strOrDefault(row.updated_at,"")}}function mapConversation(row){return{id:row.id,parentMessageId:row.parent_message_id!=null?Number(row.parent_message_id):null,name:row.name??null,type:row.type,linkedEntity:row.linked_entity??null,linkedEntityId:row.linked_entity_id??null,createdByType:row.created_by_type??null,createdById:row.created_by_id??null,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapMessage(row){return{id:Number(row.id),conversationId:row.conversation_id,replyToId:row.reply_to_id!=null?Number(row.reply_to_id):null,senderType:row.sender_type,senderId:row.sender_id,body:row.body,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapTaskActor(row){return{taskId:row.task_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,permissions:row.permissions??{},createdAt:String(row.created_at)}}function mapDependency(row){return{taskId:row.task_id,dependsOnId:row.depends_on_id,depType:row.dep_type,createdAt:String(row.created_at)}}function mapTag(row){return{id:row.id,name:row.name,color:row.color??"#9ca3af",typeId:row.type_id??null,createdAt:String(row.created_at)}}function mapTaskType(row){return{id:row.id,name:row.name,description:row.description??null,icon:row.icon??null,stages:row.stages,isBuiltin:row.is_builtin,createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapNotificationPref(row){return{actorType:row.actor_type,actorId:row.actor_id,channel:row.channel,priorityThreshold:row.priority_threshold,isDefault:row.is_default,enabled:row.enabled,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapStageLog(row){return{id:Number(row.id),taskId:row.task_id,fromStage:row.from_stage??null,toStage:row.to_stage,actorType:row.actor_type??null,actorId:row.actor_id??null,runId:row.run_id??null,gateType:row.gate_type??null,createdAt:String(row.created_at)}}function mapConversationMember(row){return{conversationId:row.conversation_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,joinedAt:String(row.joined_at)}}function mapProject(row){return{id:row.id,name:row.name,repoPath:str2(row.repo_path),description:str2(row.description),status:strOrDefault(row.status,"active"),archivedAt:str2(row.archived_at),createdAt:String(row.created_at)}}function getRepoPath(){try{return execSync5("git rev-parse --show-toplevel",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return process.cwd()}}async function resolveTaskId(idOrSeq,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),projectSeqMatch=idOrSeq.match(/^([^#]+)#(\d+)$/);if(projectSeqMatch&&!idOrSeq.startsWith("#")){let[,projectName,seqStr]=projectSeqMatch,seq2=Number.parseInt(seqStr,10);if(Number.isNaN(seq2))return null;let rows2=await sql`
|
|
586
|
+
Sync complete: ${total} active agent(s), ${result.archived.length} removed.`)}async function removeMissingAgents(discoveredNames,result){try{let entries=await ls();for(let entry of entries){if(discoveredNames.has(entry.name))continue;if(entry.scope==="built-in")continue;if(!entry.dir||!entry.dir.includes("/agents/"))continue;if(await rm3(entry.name))result.archived.push(entry.name)}}catch{}}async function syncSingleAgent(agent,result){let repoPath=(agent.repoUrl?extractOrgRepo(agent.repoUrl):null)??agent.repoUrl??agent.dir,existing=await get2(agent.name);if(!existing){await add({name:agent.name,dir:agent.dir,repo:repoPath,promptMode:"append"}),result.registered.push(agent.name);return}if(existing.repo!==repoPath||existing.dir!==agent.dir)await edit(agent.name,{dir:agent.dir,repo:repoPath}),result.updated.push(agent.name);else result.unchanged.push(agent.name)}async function syncSingleAgentByName(workspaceRoot,agentName){let agent=discoverSingleAgent(workspaceRoot,agentName);if(!agent)return"not-found";let result={registered:[],updated:[],unchanged:[],archived:[],reactivated:[],errors:[]};if(await syncSingleAgent(agent,result),result.registered.length>0)return"registered";if(result.updated.length>0)return"updated";return"unchanged"}function watchAgentDirectory(workspaceRoot,options){let agentsDir=join19(workspaceRoot,"agents");if(!existsSync18(agentsDir))return null;let debounceTimer=null,pendingChanges=new Set,processChanges=async()=>{let names=[...pendingChanges];pendingChanges.clear();for(let name of names)try{let action=await processWatchedAgent(workspaceRoot,agentsDir,name);if(action)options?.onSync?.(name,action)}catch{}},watcher=fsWatch(agentsDir,{persistent:!1},(_event,filename)=>{if(!filename)return;let name=filename.split("/")[0];if(!name||name.startsWith("."))return;if(pendingChanges.add(name),debounceTimer)clearTimeout(debounceTimer);debounceTimer=setTimeout(processChanges,2000)});return{close:()=>{if(debounceTimer)clearTimeout(debounceTimer);watcher.close()}}}async function processWatchedAgent(workspaceRoot,agentsDir,name){let agentDir=join19(agentsDir,name);if(existsSync18(agentDir)&&existsSync18(join19(agentDir,"AGENTS.md"))){let action=await syncSingleAgentByName(workspaceRoot,name);return action!=="unchanged"&&action!=="not-found"?action:null}if(!existsSync18(agentDir)){if(await rm3(name))return"removed"}return null}var init_agent_sync=__esm(()=>{init_agent_directory()});var exports_workspace={};__export(exports_workspace,{scanAgents:()=>scanAgents2,getWorkspaceConfig:()=>getWorkspaceConfig,findWorkspace:()=>findWorkspace});import{existsSync as existsSync19,readFileSync as readFileSync9,readdirSync as readdirSync6}from"fs";import{dirname as dirname5,join as join20,resolve as resolve4,sep}from"path";function findWorkspace(cwd){let startDir=resolve4(cwd??process.cwd()),current=startDir;while(!0){let candidate=join20(current,WORKSPACE_MARKER);if(existsSync19(candidate)){let agent=detectAgent(startDir,current);return{root:current,agent:agent??void 0}}let parent=dirname5(current);if(parent===current)break;current=parent}return null}function detectAgent(startDir,workspaceRoot){let agentsDir=join20(workspaceRoot,"agents"),relative=startDir.slice(agentsDir.length);if(!startDir.startsWith(agentsDir)||relative.length>0&&relative[0]!==sep)return null;let parts=relative.split(sep).filter(Boolean);if(parts.length===0)return null;let agentName=parts[0],agentsMd=join20(agentsDir,agentName,"AGENTS.md");if(existsSync19(agentsMd))return agentName;return null}function getWorkspaceConfig(root){let configPath2=join20(root,WORKSPACE_MARKER),raw=readFileSync9(configPath2,"utf-8");return JSON.parse(raw)}function scanAgents2(root){let agentsDir=join20(root,"agents");if(!existsSync19(agentsDir))return[];try{return readdirSync6(agentsDir,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&existsSync19(join20(agentsDir,d.name,"AGENTS.md"))).map((d)=>d.name).sort()}catch{return[]}}var WORKSPACE_MARKER=".genie/workspace.json";var init_workspace=()=>{};function padRight(str2,len){return str2.length>=len?str2:str2+" ".repeat(len-str2.length)}function truncate2(str2,len){return str2.length<=len?str2:`${str2.slice(0,len-1)}\u2026`}function formatDate(iso){if(!iso)return"-";return new Date(iso).toLocaleDateString("en-US",{month:"short",day:"numeric"})}function formatRelativeTimestamp(ts3){let d=new Date(ts3),diffMs=Date.now()-d.getTime();if(diffMs<60000)return`${Math.floor(diffMs/1000)}s ago`;if(diffMs<3600000)return`${Math.floor(diffMs/60000)}m ago`;if(diffMs<86400000)return`${Math.floor(diffMs/3600000)}h ago`;return d.toISOString().replace("T"," ").slice(0,19)}function formatTimestamp(iso,opts){if(!iso)return opts?.fallback??"-";let d=iso instanceof Date?iso:new Date(iso),fmt={month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return d.toLocaleString("en-US",fmt)}function formatTime(iso,opts){try{let date=new Date(iso),fmt={hour:"2-digit",minute:"2-digit",hour12:!1};if(opts?.seconds)fmt.second="2-digit";return date.toLocaleTimeString("en-US",fmt)}catch{return opts?.fallback??"??:??"}}var exports_task_service={};__export(exports_task_service,{updateTask:()=>updateTask,updateMessage:()=>updateMessage,untagTask:()=>untagTask,unblockTask:()=>unblockTask,unarchiveTask:()=>unarchiveTask,unarchiveProject:()=>unarchiveProject,tagTask:()=>tagTask,setRelease:()=>setRelease,setPreference:()=>setPreference,sendMessage:()=>sendMessage,resolveTaskId:()=>resolveTaskId,resolveChannels:()=>resolveChannels,removeMember:()=>removeMember,removeDependency:()=>removeDependency,removeActor:()=>removeActor,releaseTask:()=>releaseTask,moveTask:()=>moveTask,markDone:()=>markDone,listTypes:()=>listTypes,listTasksForActor:()=>listTasksForActor,listTasks:()=>listTasks,listTags:()=>listTags,listReleases:()=>listReleases,listProjectsFiltered:()=>listProjectsFiltered,listProjects:()=>listProjects,listConversations:()=>listConversations,linkTask:()=>linkTask,getType:()=>getType,getTaskTags:()=>getTaskTags,getTaskActors:()=>getTaskActors,getTask:()=>getTask,getStageLog:()=>getStageLog,getProjectByRepoPath:()=>getProjectByRepoPath,getProjectByName:()=>getProjectByName,getPreferences:()=>getPreferences,getMessages:()=>getMessages,getMessage:()=>getMessage,getMembers:()=>getMembers,getDependents:()=>getDependents,getConversation:()=>getConversation,getCheckoutOwner:()=>getCheckoutOwner,getBlockingDependencies:()=>getBlockingDependencies,getBlockers:()=>getBlockers,forceUnlockTask:()=>forceUnlockTask,findOrCreateConversation:()=>findOrCreateConversation,expireStaleCheckouts:()=>expireStaleCheckouts,ensureProject:()=>ensureProject,deletePreference:()=>deletePreference,createType:()=>createType,createTask:()=>createTask,createTag:()=>createTag,createProject:()=>createProject,commentOnTask:()=>commentOnTask,checkoutTask:()=>checkoutTask,blockTask:()=>blockTask,assignTask:()=>assignTask,archiveTask:()=>archiveTask,archiveProject:()=>archiveProject,archiveBoard:()=>archiveBoard,addMember:()=>addMember,addDependency:()=>addDependency});import{execSync as execSync5}from"child_process";function str2(v){return v!=null?String(v):null}function strOrDefault(v,def){return v!=null?String(v):def}function mapTask(row){return{id:row.id,seq:row.seq,parentId:str2(row.parent_id),repoPath:row.repo_path,projectId:str2(row.project_id),genieOsFolderId:str2(row.genie_os_folder_id),wishFile:str2(row.wish_file),groupName:str2(row.group_name),title:row.title,description:str2(row.description),acceptanceCriteria:str2(row.acceptance_criteria),typeId:row.type_id,stage:row.stage,status:row.status,priority:row.priority,startDate:str2(row.start_date),dueDate:str2(row.due_date),estimatedEffort:str2(row.estimated_effort),startedAt:str2(row.started_at),endedAt:str2(row.ended_at),blockedReason:str2(row.blocked_reason),releaseId:str2(row.release_id),checkoutRunId:str2(row.checkout_run_id),executionLockedAt:str2(row.execution_locked_at),checkoutTimeoutMs:row.checkout_timeout_ms??600000,sessionId:str2(row.session_id),paneId:str2(row.pane_id),traceId:str2(row.trace_id),boardId:str2(row.board_id),columnId:str2(row.column_id),externalId:str2(row.external_id),externalUrl:str2(row.external_url),archivedAt:str2(row.archived_at),metadata:row.metadata??{},createdAt:strOrDefault(row.created_at,""),updatedAt:strOrDefault(row.updated_at,"")}}function mapConversation(row){return{id:row.id,parentMessageId:row.parent_message_id!=null?Number(row.parent_message_id):null,name:row.name??null,type:row.type,linkedEntity:row.linked_entity??null,linkedEntityId:row.linked_entity_id??null,createdByType:row.created_by_type??null,createdById:row.created_by_id??null,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapMessage(row){return{id:Number(row.id),conversationId:row.conversation_id,replyToId:row.reply_to_id!=null?Number(row.reply_to_id):null,senderType:row.sender_type,senderId:row.sender_id,body:row.body,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapTaskActor(row){return{taskId:row.task_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,permissions:row.permissions??{},createdAt:String(row.created_at)}}function mapDependency(row){return{taskId:row.task_id,dependsOnId:row.depends_on_id,depType:row.dep_type,createdAt:String(row.created_at)}}function mapTag(row){return{id:row.id,name:row.name,color:row.color??"#9ca3af",typeId:row.type_id??null,createdAt:String(row.created_at)}}function mapTaskType(row){return{id:row.id,name:row.name,description:row.description??null,icon:row.icon??null,stages:row.stages,isBuiltin:row.is_builtin,createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapNotificationPref(row){return{actorType:row.actor_type,actorId:row.actor_id,channel:row.channel,priorityThreshold:row.priority_threshold,isDefault:row.is_default,enabled:row.enabled,metadata:row.metadata??{},createdAt:String(row.created_at),updatedAt:String(row.updated_at)}}function mapStageLog(row){return{id:Number(row.id),taskId:row.task_id,fromStage:row.from_stage??null,toStage:row.to_stage,actorType:row.actor_type??null,actorId:row.actor_id??null,runId:row.run_id??null,gateType:row.gate_type??null,createdAt:String(row.created_at)}}function mapConversationMember(row){return{conversationId:row.conversation_id,actorType:row.actor_type,actorId:row.actor_id,role:row.role,joinedAt:String(row.joined_at)}}function mapProject(row){return{id:row.id,name:row.name,repoPath:str2(row.repo_path),description:str2(row.description),leaderAgent:str2(row.leader_agent),tmuxSession:str2(row.tmux_session),status:strOrDefault(row.status,"active"),archivedAt:str2(row.archived_at),createdAt:String(row.created_at)}}function getRepoPath(){try{return execSync5("git rev-parse --show-toplevel",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return process.cwd()}}async function resolveTaskId(idOrSeq,repoPath){let sql=await getConnection(),repo=repoPath??getRepoPath(),projectSeqMatch=idOrSeq.match(/^([^#]+)#(\d+)$/);if(projectSeqMatch&&!idOrSeq.startsWith("#")){let[,projectName,seqStr]=projectSeqMatch,seq2=Number.parseInt(seqStr,10);if(Number.isNaN(seq2))return null;let rows2=await sql`
|
|
587
587
|
SELECT t.id FROM tasks t
|
|
588
588
|
JOIN projects p ON t.project_id = p.id
|
|
589
589
|
WHERE p.name = ${projectName} AND t.seq = ${seq2}
|
|
@@ -1584,7 +1584,7 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
1584
1584
|
Examples:
|
|
1585
1585
|
genie team create my-feature --repo . # Create team in current repo
|
|
1586
1586
|
genie team create my-feature --repo . --wish my-feature-slug # Create team with a wish
|
|
1587
|
-
genie team create hotfix --repo . --branch main # Create from main branch`).action(async(name,options)=>{try{let merged={...options,tmuxSession:options.tmuxSession??options.session};await handleTeamCreate(name,merged)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--all","Include archived teams").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("archive <name>").description("Archive a team (preserves all data, kills members)").action(async(name)=>{try{if(await archiveTeam(name))console.log(`Team "${name}" archived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("unarchive <name>").description("Restore an archived team").action(async(name)=>{try{if(await unarchiveTeam(name))console.log(`Team "${name}" unarchived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team (archives \u2014 preserves data). Use `genie team archive` directly.").action(async(name)=>{try{if(await disbandTeam(name))console.log("Note: disband now archives the team. Use `genie team archive` directly."),console.log(`Team "${name}" disbanded (archived).`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("cleanup").description("Kill tmux windows for done/archived teams").option("--dry-run","Show what would be cleaned without doing it").action(async(options)=>{try{await handleTeamCleanup(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve5(options.repo);if(options.wish){let wishPath=join39(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync30(wishPath)){let cwdWishDir=join39(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join39(cwdWishDir,"WISH.md");if(existsSync30(cwdWishPath)){let destDir=join39(resolvedRepo,".genie","wishes",options.wish);await mkdir5(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve5(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config),config.leader=slug,config.spawner=process.env.GENIE_AGENT_NAME||"cli",await updateTeamConfig(config.name,config);let sourceWishPath=join39(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync30(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join39(config.worktreePath,".genie","wishes",slug);await mkdir5(destWishDir,{recursive:!0});let destWishPath=join39(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let leaderName=config.leader||"team-lead",standardTeam=[leaderName,"engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!==leaderName).join(", "),spawner=config.spawner||"cli",kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2(leaderName,{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli",leaderName,kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${leaderName} failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2,includeArchived){let teams=await listTeams2(includeArchived);if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress",dimmed=status==="archived"?"\x1B[90m":"",reset2=status==="archived"?"\x1B[0m":"";console.log(` ${dimmed}${t.name} [${status}]${reset2}`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}async function findTeamWindow(sessionName,teamName){let tmuxLib=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(!await tmuxLib.findSessionByName(sessionName))return null;try{return(await tmuxLib.listWindows(sessionName)).find((w)=>w.name===teamName||w.name===teamName.replace(/\./g,"_"))??null}catch{return null}}async function cleanupTeamWindow(t,dryRun){if(!t.tmuxSessionName)return null;let match=await findTeamWindow(t.tmuxSessionName,t.name);if(!match)return null;if(dryRun)return` [dry-run] Would kill window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}" [${t.status}])`;if(!await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).killWindow(t.tmuxSessionName,match.name))return null;return` Killed window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}")`}async function handleTeamCleanup(options){let cleanable=(await listTeams2(!0)).filter((t)=>t.status==="done"||t.status==="archived");if(cleanable.length===0){console.log("No done/archived teams to clean up.");return}let cleaned=0;for(let t of cleanable){let msg=await cleanupTeamWindow(t,options.dryRun===!0);if(msg)console.log(msg),cleaned++}let verb=options.dryRun?"Would clean":"Cleaned";if(cleaned===0)console.log("No tmux windows found for done/archived teams.");else console.log(`
|
|
1587
|
+
genie team create hotfix --repo . --branch main # Create from main branch`).action(async(name,options)=>{try{let merged={...options,tmuxSession:options.tmuxSession??options.session};await handleTeamCreate(name,merged)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--all","Include archived teams").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("archive <name>").description("Archive a team (preserves all data, kills members)").action(async(name)=>{try{if(await archiveTeam(name))console.log(`Team "${name}" archived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("unarchive <name>").description("Restore an archived team").action(async(name)=>{try{if(await unarchiveTeam(name))console.log(`Team "${name}" unarchived.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team (archives \u2014 preserves data). Use `genie team archive` directly.").action(async(name)=>{try{if(await disbandTeam(name))console.log("Note: disband now archives the team. Use `genie team archive` directly."),console.log(`Team "${name}" disbanded (archived).`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("cleanup").description("Kill tmux windows for done/archived teams").option("--dry-run","Show what would be cleaned without doing it").action(async(options)=>{try{await handleTeamCleanup(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){let resolvedRepo=resolve5(options.repo);if(options.wish){let wishPath=join39(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync30(wishPath)){let cwdWishDir=join39(process.cwd(),".genie","wishes",options.wish),cwdWishPath=join39(cwdWishDir,"WISH.md");if(existsSync30(cwdWishPath)){let destDir=join39(resolvedRepo,".genie","wishes",options.wish);await mkdir5(destDir,{recursive:!0}),await cp(cwdWishDir,destDir,{recursive:!0}),console.log(`Wish: copied ${options.wish}/WISH.md to repo`)}else console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}}let config=await createTeam(name,options.repo,options.branch),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(config.tmuxSessionName=options.tmuxSession??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo),options.wish)config.wishSlug=options.wish;if(await updateTeamConfig(name,config),console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish&&options.spawn!==!1)await spawnLeaderWithWish(config,options.wish,options.repo,options.tmuxSession)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{findSessionByRepo:findSessionByRepo2}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),{resolveRepoSession:resolveRepoSession2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve5(repoPath),tmuxSession=sessionOverride??config.tmuxSessionName??await findSessionByRepo2(resolvedRepo)??await resolveRepoSession2(resolvedRepo);config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let{getProjectByRepoPath:getProjectByRepoPath2}=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),leaderAgent=(await getProjectByRepoPath2(resolvedRepo))?.leaderAgent||slug;config.leader=leaderAgent,config.spawner=process.env.GENIE_AGENT_NAME||"cli",await updateTeamConfig(config.name,config);let sourceWishPath=join39(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync30(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join39(config.worktreePath,".genie","wishes",slug);await mkdir5(destWishDir,{recursive:!0});let destWishPath=join39(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardTeam=[leaderAgent,"engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!==leaderAgent).join(", "),spawner=config.spawner||"cli",kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2(leaderAgent,{provider:"claude",team:config.name,role:slug,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli",leaderAgent,kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${leaderAgent} failed: ${result.reason??"unknown"}`);console.log(` Leader: ${leaderAgent} spawned as ${slug}`)}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2,includeArchived){let teams=await listTeams2(includeArchived);if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress",dimmed=status==="archived"?"\x1B[90m":"",reset2=status==="archived"?"\x1B[0m":"";console.log(` ${dimmed}${t.name} [${status}]${reset2}`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}async function findTeamWindow(sessionName,teamName){let tmuxLib=await Promise.resolve().then(() => (init_tmux(),exports_tmux));if(!await tmuxLib.findSessionByName(sessionName))return null;try{return(await tmuxLib.listWindows(sessionName)).find((w)=>w.name===teamName||w.name===teamName.replace(/\./g,"_"))??null}catch{return null}}async function cleanupTeamWindow(t,dryRun){if(!t.tmuxSessionName)return null;let match=await findTeamWindow(t.tmuxSessionName,t.name);if(!match)return null;if(dryRun)return` [dry-run] Would kill window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}" [${t.status}])`;if(!await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).killWindow(t.tmuxSessionName,match.name))return null;return` Killed window "${match.name}" in session "${t.tmuxSessionName}" (team "${t.name}")`}async function handleTeamCleanup(options){let cleanable=(await listTeams2(!0)).filter((t)=>t.status==="done"||t.status==="archived");if(cleanable.length===0){console.log("No done/archived teams to clean up.");return}let cleaned=0;for(let t of cleanable){let msg=await cleanupTeamWindow(t,options.dryRun===!0);if(msg)console.log(msg),cleaned++}let verb=options.dryRun?"Would clean":"Cleaned";if(cleaned===0)console.log("No tmux windows found for done/archived teams.");else console.log(`
|
|
1588
1588
|
${verb} ${cleaned} window${cleaned===1?"":"s"}.`)}var init_team=__esm(()=>{init_team_manager()});var exports_wish_sync={};__export(exports_wish_sync,{syncWishes:()=>syncWishes,parseWishStatus:()=>parseWishStatus,listWishes:()=>listWishes,getWish:()=>getWish});import{existsSync as existsSync31,readFileSync as readFileSync15,readdirSync as readdirSync7}from"fs";import{basename as basename6,join as join40}from"path";function parseWishStatus(content){let match=content.match(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)/i);if(match)return match[1].trim();return"DRAFT"}function scanRepoWishes(repoPath){let wishesDir=join40(repoPath,".genie","wishes");if(!existsSync31(wishesDir))return[];let results=[],namespace=basename6(repoPath);try{let entries=readdirSync7(wishesDir,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;let wishPath=join40(wishesDir,entry.name,"WISH.md");if(!existsSync31(wishPath))continue;try{let content=readFileSync15(wishPath,"utf-8");results.push({slug:entry.name,repo:repoPath,namespace,status:parseWishStatus(content),filePath:wishPath})}catch{}}}catch{}return results}function discoverWishes(repoPath){if(repoPath)return scanRepoWishes(repoPath);if(!existsSync31(REPOS_BASE2))return[];let results=[];try{let entries=readdirSync7(REPOS_BASE2,{withFileTypes:!0});for(let entry of entries){if(!entry.isDirectory())continue;results.push(...scanRepoWishes(join40(REPOS_BASE2,entry.name)))}}catch{}return results}async function syncWishes(repoPath){let wishes=discoverWishes(repoPath);if(wishes.length===0)return 0;let sql=await getConnection();for(let wish of wishes)await sql`
|
|
1589
1589
|
INSERT INTO wishes (slug, repo, namespace, status, file_path)
|
|
1590
1590
|
VALUES (${wish.slug}, ${wish.repo}, ${wish.namespace}, ${wish.status}, ${wish.filePath})
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260331.
|
|
3
|
+
"version": "4.260331.9",
|
|
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"
|
package/src/lib/task-service.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface ProjectRow {
|
|
|
48
48
|
name: string;
|
|
49
49
|
repoPath: string | null;
|
|
50
50
|
description: string | null;
|
|
51
|
+
leaderAgent: string | null;
|
|
52
|
+
tmuxSession: string | null;
|
|
51
53
|
status: string;
|
|
52
54
|
archivedAt: string | null;
|
|
53
55
|
createdAt: string;
|
|
@@ -391,6 +393,8 @@ function mapProject(row: Record<string, unknown>): ProjectRow {
|
|
|
391
393
|
name: row.name as string,
|
|
392
394
|
repoPath: str(row.repo_path),
|
|
393
395
|
description: str(row.description),
|
|
396
|
+
leaderAgent: str(row.leader_agent),
|
|
397
|
+
tmuxSession: str(row.tmux_session),
|
|
394
398
|
status: strOrDefault(row.status, 'active'),
|
|
395
399
|
archivedAt: str(row.archived_at),
|
|
396
400
|
createdAt: String(row.created_at),
|
|
@@ -336,8 +336,11 @@ async function spawnLeaderWithWish(
|
|
|
336
336
|
config.tmuxSessionName = tmuxSession;
|
|
337
337
|
await teamManager.updateTeamConfig(config.name, config);
|
|
338
338
|
|
|
339
|
-
//
|
|
340
|
-
|
|
339
|
+
// Resolve leader from project's leader_agent, spawner = caller identity
|
|
340
|
+
const { getProjectByRepoPath } = await import('../lib/task-service.js');
|
|
341
|
+
const project = await getProjectByRepoPath(resolvedRepo);
|
|
342
|
+
const leaderAgent = project?.leaderAgent || slug;
|
|
343
|
+
config.leader = leaderAgent;
|
|
341
344
|
config.spawner = process.env.GENIE_AGENT_NAME || 'cli';
|
|
342
345
|
await teamManager.updateTeamConfig(config.name, config);
|
|
343
346
|
|
|
@@ -355,21 +358,21 @@ async function spawnLeaderWithWish(
|
|
|
355
358
|
await copyFile(sourceWishPath, destWishPath);
|
|
356
359
|
console.log(` Wish: copied ${slug}/WISH.md into worktree`);
|
|
357
360
|
|
|
358
|
-
// Hire the standard team: leader + engineer + reviewer + qa + fix
|
|
359
|
-
const
|
|
360
|
-
const standardTeam = [leaderName, 'engineer', 'reviewer', 'qa', 'fix'];
|
|
361
|
+
// Hire the standard team: leader (by agent name) + engineer + reviewer + qa + fix
|
|
362
|
+
const standardTeam = [leaderAgent, 'engineer', 'reviewer', 'qa', 'fix'];
|
|
361
363
|
for (const role of standardTeam) {
|
|
362
364
|
await teamManager.hireAgent(config.name, role);
|
|
363
365
|
}
|
|
364
366
|
console.log(` Team: hired ${standardTeam.join(', ')}`);
|
|
365
367
|
|
|
366
|
-
// Spawn leader —
|
|
367
|
-
const members = standardTeam.filter((r) => r !==
|
|
368
|
+
// Spawn leader — resolve agent definition from leaderAgent, use slug as role identity
|
|
369
|
+
const members = standardTeam.filter((r) => r !== leaderAgent).join(', ');
|
|
368
370
|
const spawner = config.spawner || 'cli';
|
|
369
371
|
const kickoffPrompt = `Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired — genie work will spawn them automatically). Report completion to: ${spawner} (via genie send --to ${spawner}). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;
|
|
370
|
-
await handleWorkerSpawn(
|
|
372
|
+
await handleWorkerSpawn(leaderAgent, {
|
|
371
373
|
provider: 'claude',
|
|
372
374
|
team: config.name,
|
|
375
|
+
role: slug,
|
|
373
376
|
cwd: config.worktreePath,
|
|
374
377
|
session: tmuxSession,
|
|
375
378
|
initialPrompt: kickoffPrompt,
|
|
@@ -377,11 +380,11 @@ async function spawnLeaderWithWish(
|
|
|
377
380
|
|
|
378
381
|
// Deliver kickoff prompt via mailbox as backup (durable, queued to disk)
|
|
379
382
|
const protocolRouter = await import('../lib/protocol-router.js');
|
|
380
|
-
const result = await protocolRouter.sendMessage(config.worktreePath, 'cli',
|
|
383
|
+
const result = await protocolRouter.sendMessage(config.worktreePath, 'cli', leaderAgent, kickoffPrompt);
|
|
381
384
|
if (!result.delivered) {
|
|
382
|
-
console.warn(`⚠ Backup delivery to ${
|
|
385
|
+
console.warn(`⚠ Backup delivery to ${leaderAgent} failed: ${result.reason ?? 'unknown'}`);
|
|
383
386
|
}
|
|
384
|
-
console.log(
|
|
387
|
+
console.log(` Leader: ${leaderAgent} spawned as ${slug}`);
|
|
385
388
|
}
|
|
386
389
|
|
|
387
390
|
// ============================================================================
|