@automagik/genie 4.260507.7 → 4.260508.1
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
|
@@ -204,7 +204,7 @@ PANE_ID="$1"
|
|
|
204
204
|
COLOR=$(${bin} display-message -p -t "$PANE_ID" '#{@genie_color}' 2>/dev/null)
|
|
205
205
|
[ -z "$COLOR" ] && COLOR="default"
|
|
206
206
|
${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
207
|
-
`),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 existsSync7(join9(`/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`
|
|
207
|
+
`),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 existsSync7(join9(`/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.name??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`
|
|
208
208
|
SELECT team FROM agents WHERE id = ${agent.id} LIMIT 1
|
|
209
209
|
`;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??null}, ${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), native_team_enabled = EXCLUDED.native_team_enabled, native_agent_id = COALESCE(EXCLUDED.native_agent_id, agents.native_agent_id), native_color = COALESCE(EXCLUDED.native_color, agents.native_color), parent_session_id = COALESCE(EXCLUDED.parent_session_id, agents.parent_session_id), provider = COALESCE(EXCLUDED.provider, agents.provider), transport = COALESCE(EXCLUDED.transport, agents.transport, 'tmux'), 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`
|
|
210
210
|
UPDATE agents
|
|
@@ -2849,9 +2849,22 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2849
2849
|
WHERE p.proname IN ('notify_trigger', 'pg_notify')
|
|
2850
2850
|
)
|
|
2851
2851
|
ORDER BY source_table, trigger_name
|
|
2852
|
-
`}),reply(sub.settings.get(ORG_ID),async()=>{let genieHome4=process.env.GENIE_HOME??join65(homedir43(),".genie"),configPath2=join65(genieHome4,"config.json"),wsConfigPath=join65(process.cwd(),".genie","workspace.json"),config={},wsConfig={};try{if(existsSync53(configPath2))config=JSON.parse(readFileSync35(configPath2,"utf-8"))}catch{}try{if(existsSync53(wsConfigPath))wsConfig=JSON.parse(readFileSync35(wsConfigPath,"utf-8"))}catch{}return{config,workspace:wsConfig}}),reply(sub.settings.set(ORG_ID),async(params)=>{let genieHome4=process.env.GENIE_HOME??join65(homedir43(),".genie"),configPath2=join65(genieHome4,"config.json"),config={};try{if(existsSync53(configPath2))config=JSON.parse(readFileSync35(configPath2,"utf-8"))}catch{}return config[params.key]=params.value,writeFileSync21(configPath2,JSON.stringify(config,null,2)),{ok:!0}}),reply(sub.settings.templates(ORG_ID),async()=>{return sql`
|
|
2852
|
+
`}),reply(sub.settings.get(ORG_ID),async()=>{let genieHome4=process.env.GENIE_HOME??join65(homedir43(),".genie"),configPath2=join65(genieHome4,"config.json"),wsConfigPath=join65(process.cwd(),".genie","workspace.json"),config={},wsConfig={};try{if(existsSync53(configPath2))config=JSON.parse(readFileSync35(configPath2,"utf-8"))}catch{}try{if(existsSync53(wsConfigPath))wsConfig=JSON.parse(readFileSync35(wsConfigPath,"utf-8"))}catch{}return{config,workspace:wsConfig}}),reply(sub.settings.set(ORG_ID),async(params)=>{let genieHome4=process.env.GENIE_HOME??join65(homedir43(),".genie"),configPath2=join65(genieHome4,"config.json"),config={};try{if(existsSync53(configPath2))config=JSON.parse(readFileSync35(configPath2,"utf-8"))}catch{}return config[params.key]=params.value,writeFileSync21(configPath2,JSON.stringify(config,null,2)),{ok:!0}}),reply(sub.settings.templates(ORG_ID),async()=>{return sql`
|
|
2853
|
+
SELECT
|
|
2854
|
+
name AS id,
|
|
2855
|
+
provider,
|
|
2856
|
+
team,
|
|
2857
|
+
role,
|
|
2858
|
+
skill,
|
|
2859
|
+
cwd,
|
|
2860
|
+
extra_args,
|
|
2861
|
+
native_team_enabled,
|
|
2862
|
+
last_spawned_at
|
|
2863
|
+
FROM agent_templates
|
|
2864
|
+
ORDER BY last_spawned_at DESC
|
|
2865
|
+
`}),reply(sub.settings.skills(ORG_ID),async()=>{let skillsDir=findSkillsDir();if(!skillsDir)return(await sql`SELECT DISTINCT skill FROM agents WHERE skill IS NOT NULL ORDER BY skill`).map((r)=>({name:r.skill,description:"",path:""}));return scanSkillsDirectory(skillsDir)}),reply(sub.settings.rules(ORG_ID),async()=>{return scanRuleDirs()}),reply(sub.settings.templateSave(ORG_ID),async(params)=>{if(!params.id)return{error:"id is required"};return await sql`
|
|
2853
2866
|
INSERT INTO agent_templates (
|
|
2854
|
-
|
|
2867
|
+
name, provider, team, role, skill, cwd,
|
|
2855
2868
|
extra_args, native_team_enabled, last_spawned_at
|
|
2856
2869
|
) VALUES (
|
|
2857
2870
|
${params.id},
|
|
@@ -2860,18 +2873,18 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2860
2873
|
${params.role??null},
|
|
2861
2874
|
${params.skill??null},
|
|
2862
2875
|
${params.cwd??""},
|
|
2863
|
-
${
|
|
2876
|
+
${sql.json(params.extraArgs??[])},
|
|
2864
2877
|
${params.nativeTeamEnabled??!1},
|
|
2865
2878
|
${new Date().toISOString()}
|
|
2866
2879
|
)
|
|
2867
|
-
ON CONFLICT (
|
|
2880
|
+
ON CONFLICT (name, team) WHERE name IS NOT NULL AND team IS NOT NULL DO UPDATE SET
|
|
2868
2881
|
provider = EXCLUDED.provider,
|
|
2869
|
-
team = EXCLUDED.team,
|
|
2870
2882
|
role = EXCLUDED.role,
|
|
2871
2883
|
skill = EXCLUDED.skill,
|
|
2872
2884
|
cwd = EXCLUDED.cwd,
|
|
2873
2885
|
extra_args = EXCLUDED.extra_args,
|
|
2874
|
-
native_team_enabled = EXCLUDED.native_team_enabled
|
|
2886
|
+
native_team_enabled = EXCLUDED.native_team_enabled,
|
|
2887
|
+
last_spawned_at = EXCLUDED.last_spawned_at
|
|
2875
2888
|
`,{ok:!0}}),reply(sub.settings.testPg(ORG_ID),async()=>{try{return await sql`SELECT 1 AS ok`,{ok:!0,message:"Connection successful"}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}),reply(sub.pty.create(ORG_ID),async(params)=>{if(params.agentName){let session2=await spawnForAgent(params.agentName,{cwd:params.cwd,cols:params.cols,rows:params.rows});return{sessionId:session2.id,agentId:session2.agentId,executorId:session2.executorId}}return{sessionId:spawnBash(params.cwd).id,agentId:null,executorId:null}}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.input`,async(sessionId,params)=>{writeTerminal(sessionId,params.data)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.resize`,async(sessionId,params)=>{resizeTerminal(sessionId,params.cols,params.rows)}),subscribePtyWildcard(`khal.${ORG_ID}.genie.pty.*.kill`,async(sessionId)=>{await killTerminal(sessionId)}),reply(sub.fs.list(ORG_ID),async(params)=>{let targetPath=resolve12(params.path);if(!existsSync53(targetPath))return{error:"not_found"};try{return readdirSync13(targetPath,{withFileTypes:!0}).map((e)=>({name:e.name,isDirectory:e.isDirectory(),path:join65(targetPath,e.name)}))}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.read(ORG_ID),async(params)=>{let targetPath=resolve12(params.path);if(!existsSync53(targetPath))return{error:"not_found"};try{return{content:readFileSync35(targetPath,"utf-8")}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.fs.write(ORG_ID),async(params)=>{let targetPath=resolve12(params.path);try{return writeFileSync21(targetPath,params.content),{ok:!0}}catch(err){return{error:err instanceof Error?err.message:String(err)}}}),reply(sub.approval.resolve(ORG_ID),async(params)=>{return{ok:await resolveApproval(params.id,params.decision,params.decided_by)}}),reply(sub.approval.list(ORG_ID),async(params)=>{return listPendingApprovals(params.agent_name)}),console.log("[genie-app] All request/reply handlers registered")}function decodeParams(data){if(data.length===0)return{};try{return JSON.parse(sc2.decode(data))}catch{return{}}}function subscribePtyWildcard(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);(async()=>{for await(let msg of subscription)try{let params=decodeParams(msg.data),sessionId=msg.subject.split(".")[4];if(sessionId)await handler(sessionId,params)}catch(err){console.error(`[genie-app] PTY wildcard error on ${msg.subject}:`,err instanceof Error?err.message:String(err))}})()}function reply(subject,handler){if(!nc)return;let subscription=nc.subscribe(subject);processSubscription(subscription,subject,handler)}async function processSubscription(subscription,subject,handler){for await(let msg of subscription)try{let params=decodeParams(msg.data),result2=await handler(params);if(msg.reply)msg.respond(sc2.encode(JSON.stringify(result2)))}catch(err){let errorMsg=err instanceof Error?err.message:String(err);if(console.error(`[genie-app] Handler error on ${subject}:`,errorMsg),msg.reply)msg.respond(sc2.encode(JSON.stringify({error:errorMsg})))}}async function shutdown2(){if(shutdownRequested)return;if(shutdownRequested=!0,console.log("[genie-app] Shutting down..."),await killAll(),await stopListening(),nc){try{await nc.drain()}catch{}nc=null}console.log("[genie-app] Shutdown complete"),process.exit(0)}var import_nats6,NATS_URL,ORG_ID,_PG_URL,sc2,nc=null,shutdownRequested=!1;var init_src_backend=__esm(()=>{init_agent_observability();init_db();init_claude_sdk_remote_approval();init_subjects();init_pg_bridge();init_pty();import_nats6=__toESM(require_mod4(),1);process.env.GENIE_APP="1";NATS_URL=process.env.GENIE_NATS_URL??"nats://localhost:4222",ORG_ID=process.env.GENIE_ORG_ID??"default",_PG_URL=process.env.GENIE_PG_URL??"postgresql://localhost:19642/genie",sc2=import_nats6.StringCodec();process.on("SIGTERM",()=>void shutdown2());process.on("SIGINT",()=>void shutdown2());start2().catch((err)=>{console.error("[genie-app] Fatal error:",err),process.exit(1)})});var exports_event_renderer={};__export(exports_event_renderer,{renderRuntimeEvent:()=>renderRuntimeEvent,renderAuditEvent:()=>renderAuditEvent,formatEventLine:()=>formatEventLine});function renderAuditEvent(input){let details=input.details;if(typeof details==="string")try{details=JSON.parse(details)}catch{details={}}let normalized={...input,details:details??{}},renderer=auditRenderers[input.event_type];if(renderer)return renderer(normalized);let event=normalized,json2=JSON.stringify(event.details);return{indicator:color("dim","\u25CB"),content:`${color("gray",event.event_type)} ${json2==="{}"?"":color("dim",json2)}`,context:`${event.entity_type}:${event.entity_id}`}}function renderRuntimeEvent(input){let renderer=runtimeRenderers[input.kind];if(renderer)return renderer(input);return{indicator:color("dim","\u25CB"),content:`${color("gray",input.kind)} ${input.text}`,context:input.agent}}function formatEventLine(timeStr,rendered){let termWidth=process.stdout.columns||120,timeCol=color("gray",timeStr),timeWidth=stripAnsi2(timeCol).length,iconWidth=stripAnsi2(rendered.indicator).length,prefixWidth=timeWidth+2+iconWidth+2,contextStr=rendered.context?color("dim",rendered.context):"",contextWidth=contextStr?stripAnsi2(contextStr).length+2:0,contentWidth=Math.max(20,termWidth-prefixWidth-contextWidth),content=stripAnsi2(rendered.content).replace(/[\r\n]+/g," ");if(content.length<=contentWidth){let paddedContent=padToWidth(rendered.content,contentWidth);return`${timeCol} ${rendered.indicator} ${paddedContent}${contextStr?` ${contextStr}`:""}`}let chunks=wrapText(content,contentWidth),indent2=" ".repeat(prefixWidth),firstLine=`${timeCol} ${rendered.indicator} ${padToWidth(chunks[0],contentWidth)}${contextStr?` ${contextStr}`:""}`,rest=chunks.slice(1).map((c)=>`${indent2}${c}`);return[firstLine,...rest].join(`
|
|
2876
2889
|
`)}function padToWidth(coloredText,width){let visible=stripAnsi2(coloredText).length,pad=Math.max(0,width-visible);return coloredText+" ".repeat(pad)}function wrapText(text,width){let chunks=[],remaining=text;while(remaining.length>width){let breakAt=remaining.lastIndexOf(" ",width);if(breakAt<=0||breakAt<width/2)breakAt=width;chunks.push(remaining.slice(0,breakAt).trimEnd()),remaining=remaining.slice(breakAt).trimStart()}if(remaining.length>0)chunks.push(remaining);return chunks}function formatDuration3(ms){if(ms<1000)return`${ms}ms`;if(ms<60000)return`${(ms/1000).toFixed(1)}s`;let s2=Math.floor(ms/1000);return`${Math.floor(s2/60)}m${s2%60}s`}var shortEntity=(id)=>id.split(":")[0]??id,auditRenderers,runtimeRenderers;var init_event_renderer=__esm(()=>{init_term_format();auditRenderers={"sdk.assistant.message":(e)=>({indicator:color("brightCyan","\uD83D\uDCAC"),content:color("brightCyan",`"${e.details.textPreview??""}"`),context:shortEntity(e.entity_id)}),"sdk.user.message":(e)=>{let d=e.details,text=d.textPreview;if(typeof text==="string"&&text.length>0){let tag=d.isReplay?color("dim"," (replay)"):d.isSynthetic?color("dim"," (synthetic)"):"";return{indicator:color("cyan","\uD83D\uDC64"),content:`${color("cyan",`"${text}"`)}${tag}`,context:shortEntity(e.entity_id)}}let kind=d.isReplay?"replay":d.isSynthetic?"synthetic":"turn";return{indicator:color("cyan","\uD83D\uDC64"),content:color("dim",`user ${kind}`),context:shortEntity(e.entity_id)}},"sdk.hook.started":(e)=>({indicator:color("yellow","\uD83E\uDE9D"),content:`${e.details.hookName??"?"} ${color("dim","started")}`,context:shortEntity(e.entity_id)}),"sdk.hook.response":(e)=>{let outcome=e.details.outcome==="success"?color("green","\u2713"):color("red","\u2717"),hookId=e.details.hookId?color("dim",` ${String(e.details.hookId).slice(0,8)}`):"";return{indicator:color("yellow","\uD83E\uDE9D"),content:`${e.details.hookName??"?"} ${outcome}${hookId}`,context:shortEntity(e.entity_id)}},"sdk.system":(e)=>{let model=String(e.details.model??"?").replace(/^claude-/,""),tools=e.details.tools??0,session=e.details.sessionId?` \xB7 ${color("dim",String(e.details.sessionId).slice(0,8))}`:"";return{indicator:color("gray","\u2699"),content:`init ${color("cyan",model)} \xB7 ${tools} tools${session}`,context:shortEntity(e.entity_id)}},"sdk.result.success":(e)=>{let d=e.details,dur=d.durationMs?formatDuration3(d.durationMs):"",cost=typeof d.totalCostUsd==="number"?`$${d.totalCostUsd.toFixed(4)}`:"",preview=d.resultPreview?` \xB7 "${d.resultPreview}"`:"";return{indicator:color("green","\u2728"),content:`${color("brightGreen",dur)} \xB7 ${color("yellow",cost)}${color("dim",preview)}`,context:shortEntity(e.entity_id)}},"sdk.rate_limit":(e)=>({indicator:color("gray","\u23F1"),content:color("dim",`rate limit: ${e.details.status??"?"}`),context:shortEntity(e.entity_id)}),"sdk.api.retry":(e)=>{let d=e.details,attempt=d.attempt??"?",max=d.maxRetries??"?",delay=d.retryDelayMs?`${Math.round(d.retryDelayMs)}ms`:"";return{indicator:color("yellow","\u21BB"),content:color("yellow",`retry ${attempt}/${max} \xB7 ${d.error??"?"}${delay?` \xB7 ${delay}`:""}`),context:shortEntity(e.entity_id)}},"session.created_fresh":(e)=>({indicator:color("green","\u2726"),content:color("green",`session created \xB7 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"session.resumed":(e)=>({indicator:color("cyan","\u21BB"),content:color("cyan",`session resumed \xB7 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"deliver.start":(e)=>({indicator:color("blue","\u25B8"),content:color("dim",`deliver \u2192 ${e.details.agent_id??"?"}`),context:shortEntity(e.entity_id)}),"deliver.end":(e)=>{let turns=e.details.turn_count?` \xB7 turn ${e.details.turn_count}`:"";return{indicator:color("blue","\u25C2"),content:color("dim",`deliver done${turns}`),context:shortEntity(e.entity_id)}},state_changed:(e)=>({indicator:color("gray","\u25C9"),content:color("dim",`state \u2192 ${e.details.state??"?"}`),context:shortEntity(e.entity_id)}),"executor.spawn":(e)=>({indicator:color("green","\u25B6"),content:color("green",`spawn ${e.details.source??""}`),context:e.entity_id}),"executor.terminate":(e)=>({indicator:color("yellow","\u25A0"),content:color("yellow",`terminate ${e.details.source??""}`),context:e.entity_id}),"executor.deliver":(e)=>{let d=e.details,dur=d.durationMs?formatDuration3(d.durationMs):"",tokens2=d.tokens,tokStr=tokens2?`${tokens2.input??0}\u2192${tokens2.output??0}`:"",parts=[dur,tokStr].filter(Boolean);return{indicator:color("blue","\u2192"),content:parts.join(" \xB7 "),context:e.entity_id}},"task.error":(e)=>({indicator:color("red","\u2717"),content:color("red",String(e.details.error??"unknown error")),context:e.entity_id}),command_success:(e)=>({indicator:color("dim","\xB7"),content:color("dim",`${e.details.duration_ms??0}ms`),context:e.entity_id})};runtimeRenderers={user:(e)=>({indicator:color("brightCyan","\uD83D\uDC64"),content:e.text,context:e.agent}),assistant:(e)=>({indicator:color("cyan","\uD83E\uDD16"),content:color("cyan",e.text),context:e.agent}),message:(e)=>({indicator:color("magenta","\u2709"),content:e.text,context:e.agent}),tool_call:(e)=>({indicator:color("yellow","\uD83D\uDD27"),content:color("yellow",e.text),context:e.agent}),tool_result:(e)=>({indicator:color("gray","\u2B90"),content:color("dim",e.text),context:e.agent}),state:(e)=>({indicator:color("gray","\u25C9"),content:color("dim",e.text),context:e.agent}),system:(e)=>({indicator:color("gray","\u2699"),content:color("dim",e.text),context:e.agent}),qa:(e)=>({indicator:color("magenta","\uD83E\uDDEA"),content:e.text,context:e.agent})}});var exports_board_service={};__export(exports_board_service,{updateColumn:()=>updateColumn,updateBoard:()=>updateBoard,reorderColumns:()=>reorderColumns,removeColumn:()=>removeColumn,reconcileBoard:()=>reconcileBoard,listBoards:()=>listBoards,importBoard:()=>importBoard,getColumns:()=>getColumns,getBoard:()=>getBoard,exportBoard:()=>exportBoard,deleteBoard:()=>deleteBoard,createBoard:()=>createBoard,addColumn:()=>addColumn});function str3(v){return v!=null?String(v):null}function strOrDefault2(v,def){return v!=null?String(v):def}function parseJsonb(val,fallback){if(val==null)return fallback;if(typeof val==="string")try{return JSON.parse(val)}catch{return fallback}return val}function mapBoard(row2){return{id:row2.id,name:row2.name,projectId:str3(row2.project_id),description:str3(row2.description),status:strOrDefault2(row2.status,"active"),archivedAt:str3(row2.archived_at),columns:parseJsonb(row2.columns,[]),config:parseJsonb(row2.config,{}),createdAt:strOrDefault2(row2.created_at,""),updatedAt:strOrDefault2(row2.updated_at,"")}}function generateColumnId(){return crypto.randomUUID()}function fillColumnDefaults(col,position){return{id:col.id??generateColumnId(),name:col.name??`column-${position}`,label:col.label??col.name??`Column ${position}`,gate:col.gate??"human",action:col.action??null,auto_advance:col.auto_advance??!1,transitions:col.transitions??[],roles:col.roles??["*"],color:col.color??palette.textDim,parallel:col.parallel??!1,on_fail:col.on_fail??null,position}}async function createBoard(input){let sql=await getConnection(),columns=[];if(input.fromTemplate){let tmplRows=await sql`
|
|
2877
2890
|
SELECT columns FROM board_templates WHERE name = ${input.fromTemplate} OR id = ${input.fromTemplate} LIMIT 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260508.1",
|
|
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.
|
|
3
|
+
"version": "4.260508.1",
|
|
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"
|
|
@@ -23,48 +23,70 @@
|
|
|
23
23
|
|
|
24
24
|
BEGIN;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
--
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
DO $$
|
|
27
|
+
DECLARE
|
|
28
|
+
builtin_names text[] := ARRAY[
|
|
29
|
+
-- BUILTIN_ROLES (plugins/genie/agents/*/AGENTS.md, category=role)
|
|
30
|
+
'docs',
|
|
31
|
+
'engineer',
|
|
32
|
+
'fix',
|
|
33
|
+
'pm',
|
|
34
|
+
'qa',
|
|
35
|
+
'refactor',
|
|
36
|
+
'reviewer',
|
|
37
|
+
'team-lead',
|
|
38
|
+
'trace',
|
|
39
|
+
-- BUILTIN_COUNCIL_MEMBERS (plugins/genie/agents/council*, category=council)
|
|
40
|
+
'council',
|
|
41
|
+
'council--architect',
|
|
42
|
+
'council--benchmarker',
|
|
43
|
+
'council--deployer',
|
|
44
|
+
'council--ergonomist',
|
|
45
|
+
'council--measurer',
|
|
46
|
+
'council--operator',
|
|
47
|
+
'council--questioner',
|
|
48
|
+
'council--sentinel',
|
|
49
|
+
'council--simplifier',
|
|
50
|
+
'council--tracer'
|
|
51
|
+
];
|
|
52
|
+
has_name_column boolean;
|
|
53
|
+
BEGIN
|
|
54
|
+
SELECT EXISTS (
|
|
55
|
+
SELECT 1
|
|
56
|
+
FROM information_schema.columns
|
|
57
|
+
WHERE table_schema = current_schema()
|
|
58
|
+
AND table_name = 'agent_templates'
|
|
59
|
+
AND column_name = 'name'
|
|
60
|
+
) INTO has_name_column;
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'council--tracer'
|
|
68
|
-
);
|
|
62
|
+
IF has_name_column THEN
|
|
63
|
+
-- Post-061 schema: agent_templates.id is a UUID PK; name is the text
|
|
64
|
+
-- template key used by roles, built-ins, and parent/child hierarchy.
|
|
65
|
+
EXECUTE $sql$
|
|
66
|
+
UPDATE agent_templates AS child
|
|
67
|
+
SET team = parent.team,
|
|
68
|
+
updated_at = now()
|
|
69
|
+
FROM agent_templates AS parent
|
|
70
|
+
WHERE child.name LIKE parent.name || '/%'
|
|
71
|
+
AND parent.name NOT LIKE '%/%'
|
|
72
|
+
AND child.team IS DISTINCT FROM parent.team
|
|
73
|
+
$sql$;
|
|
74
|
+
|
|
75
|
+
EXECUTE 'DELETE FROM agent_templates WHERE name = ANY ($1)' USING builtin_names;
|
|
76
|
+
ELSE
|
|
77
|
+
-- Pre-061 / fresh-install ordering: id is still the text template key.
|
|
78
|
+
EXECUTE $sql$
|
|
79
|
+
UPDATE agent_templates AS child
|
|
80
|
+
SET team = parent.team,
|
|
81
|
+
updated_at = now()
|
|
82
|
+
FROM agent_templates AS parent
|
|
83
|
+
WHERE child.id LIKE parent.id || '/%'
|
|
84
|
+
AND parent.id NOT LIKE '%/%'
|
|
85
|
+
AND child.team IS DISTINCT FROM parent.team
|
|
86
|
+
$sql$;
|
|
87
|
+
|
|
88
|
+
EXECUTE 'DELETE FROM agent_templates WHERE id = ANY ($1)' USING builtin_names;
|
|
89
|
+
END IF;
|
|
90
|
+
END $$;
|
|
69
91
|
|
|
70
92
|
COMMIT;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { type Sql, getConnection } from '../../lib/db.js';
|
|
5
|
+
import { DB_AVAILABLE, setupTestDatabase } from '../../lib/test-db.js';
|
|
6
|
+
|
|
7
|
+
const MIGRATION_PATH = join(import.meta.dir, '054_fix_subagent_team_inheritance.sql');
|
|
8
|
+
|
|
9
|
+
async function applyMigration(): Promise<void> {
|
|
10
|
+
const sql = await getConnection();
|
|
11
|
+
const migration = await readFile(MIGRATION_PATH, 'utf-8');
|
|
12
|
+
await sql.begin(async (tx: Sql) => {
|
|
13
|
+
await tx.unsafe(migration);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe.skipIf(!DB_AVAILABLE)('migration 054 — subagent team inheritance', () => {
|
|
18
|
+
let cleanup: () => Promise<void>;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
cleanup = await setupTestDatabase();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterAll(async () => {
|
|
25
|
+
await cleanup();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
const sql = await getConnection();
|
|
30
|
+
await sql`DROP TABLE IF EXISTS agent_templates CASCADE`;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('heals pre-061 text-id schema during fresh install ordering', async () => {
|
|
34
|
+
const sql = await getConnection();
|
|
35
|
+
await sql`
|
|
36
|
+
CREATE TABLE agent_templates (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
39
|
+
team TEXT NOT NULL,
|
|
40
|
+
cwd TEXT NOT NULL DEFAULT '/tmp',
|
|
41
|
+
last_spawned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
42
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
43
|
+
)
|
|
44
|
+
`;
|
|
45
|
+
await sql`
|
|
46
|
+
INSERT INTO agent_templates (id, team)
|
|
47
|
+
VALUES
|
|
48
|
+
('genie-omni', 'genie'),
|
|
49
|
+
('genie-omni/dog-fooder', 'felipe'),
|
|
50
|
+
('engineer', 'felipe')
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
await applyMigration();
|
|
54
|
+
|
|
55
|
+
const child = await sql<{ team: string }[]>`
|
|
56
|
+
SELECT team FROM agent_templates WHERE id = 'genie-omni/dog-fooder'
|
|
57
|
+
`;
|
|
58
|
+
expect(child[0].team).toBe('genie');
|
|
59
|
+
|
|
60
|
+
const builtin = await sql`SELECT 1 FROM agent_templates WHERE id = 'engineer'`;
|
|
61
|
+
expect(builtin.length).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('heals post-061 UUID-id schema via template name', async () => {
|
|
65
|
+
const sql = await getConnection();
|
|
66
|
+
await sql`CREATE EXTENSION IF NOT EXISTS pgcrypto`;
|
|
67
|
+
await sql`
|
|
68
|
+
CREATE TABLE agent_templates (
|
|
69
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
70
|
+
name TEXT NOT NULL,
|
|
71
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
72
|
+
team TEXT NOT NULL,
|
|
73
|
+
cwd TEXT NOT NULL DEFAULT '/tmp',
|
|
74
|
+
last_spawned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
75
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
76
|
+
)
|
|
77
|
+
`;
|
|
78
|
+
await sql`
|
|
79
|
+
INSERT INTO agent_templates (name, team)
|
|
80
|
+
VALUES
|
|
81
|
+
('genie-omni', 'genie'),
|
|
82
|
+
('genie-omni/dog-fooder', 'felipe'),
|
|
83
|
+
('engineer', 'felipe')
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
await applyMigration();
|
|
87
|
+
|
|
88
|
+
const child = await sql<{ team: string }[]>`
|
|
89
|
+
SELECT team FROM agent_templates WHERE name = 'genie-omni/dog-fooder'
|
|
90
|
+
`;
|
|
91
|
+
expect(child[0].team).toBe('genie');
|
|
92
|
+
|
|
93
|
+
const builtin = await sql`SELECT 1 FROM agent_templates WHERE name = 'engineer'`;
|
|
94
|
+
expect(builtin.length).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
});
|