@automagik/genie 4.260328.5 → 4.260328.6

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260328.5",
13
+ "version": "4.260328.6",
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
@@ -1487,7 +1487,7 @@ ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVer
1487
1487
  SELECT * FROM team_chat
1488
1488
  WHERE team = ${teamName} AND repo_path = ${repoPath}
1489
1489
  ORDER BY created_at ASC
1490
- `).map(rowToMessage2)}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),team?readMessages(repoPath,team):Promise.resolve([])]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName)]),events=[];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startNatsFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startNatsFollow(agents,repoPath,teamName,filter,onEvent)}async function startNatsFollow(_agents,_repoPath,_team,filter,onEvent){let nats=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client));if(!await nats.isAvailable())throw Error("NATS is not available. Install nats package and ensure NATS server is running.");let kindsFilter=filter?.kinds?new Set(filter.kinds):null,subs=[],seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,handleNatsEvent=(_subject,data)=>{let event=data;if(!event?.timestamp||!event?.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},allSub=await nats.subscribe("genie.>",handleNatsEvent);return subs.push(allSub),{mode:"nats",stop:async()=>{for(let sub of subs)sub.unsubscribe()}}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
1490
+ `).map(rowToMessage2)}function isSystemNoise(text){let trimmed=text.trimStart();return trimmed.startsWith("<command-name>")||trimmed.startsWith("<command-message>")||trimmed.startsWith("Base directory for this skill:")||trimmed.startsWith("<system-reminder>")||trimmed.startsWith("<local-command")}function transcriptToLogEvent(entry,agent,team){let kindMap={user:"user",assistant:"assistant",system:"system",tool_call:"tool_call",tool_result:"tool_result"},text=entry.text.trim();if(isSystemNoise(text))return null;if(!text)return null;return{timestamp:entry.timestamp,kind:kindMap[entry.role]??"assistant",agent,team,text,data:{role:entry.role,...entry.toolCall?{toolCall:entry.toolCall}:{},...entry.model?{model:entry.model}:{},...entry.usage?{usage:entry.usage}:{}},source:"provider"}}function inboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"in",peer:msg.from,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to,read:msg.read},source:"mailbox"}}function outboxMessageToLogEvent(msg,agent,team){return{timestamp:msg.createdAt,kind:"message",agent,team,direction:"out",peer:msg.to,text:msg.body,data:{messageId:msg.id,from:msg.from,to:msg.to},source:"mailbox"}}function chatMessageToLogEvent(msg,team){return{timestamp:msg.timestamp,kind:"message",agent:msg.sender,team,text:msg.body,data:{chatId:msg.id,sender:msg.sender},source:"chat"}}function applyLogFilter(events,filter){if(!filter)return events;let result=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result=result.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result=result.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result=result.slice(-filter.last);return result}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}async function readPgMessages(agentName){try{let ts2=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),actor={actorType:"local",actorId:agentName},conversations=await ts2.listConversations(actor),events=[];for(let conv of conversations){let messages2=await ts2.getMessages(conv.id,{limit:100}),peerMember=(await ts2.getMembers(conv.id)).find((m)=>m.actorId!==agentName);for(let msg of messages2){let isOutgoing=msg.senderId===agentName,peer=isOutgoing?peerMember?.actorId:msg.senderId;events.push({timestamp:msg.createdAt,kind:"message",agent:agentName,direction:isOutgoing?"out":"in",peer,text:msg.body,data:{messageId:msg.id,conversationId:msg.conversationId,senderId:msg.senderId},source:"send"})}}return events}catch{return[]}}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,[transcriptEntries,inboxMessages,outboxMessages,chatMessages,pgMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),team?readMessages(repoPath,team):Promise.resolve([]),readPgMessages(agentName)]),events=[...pgMessages];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,team);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,team));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,team));if(team)for(let msg of chatMessages)events.push(chatMessageToLogEvent(msg,team));let sorted=sortByTimestamp(events);return applyLogFilter(sorted,filter)}async function readTeamLog(agents,repoPath,teamName,filter){let chatEvents=(await readMessages(repoPath,teamName)).map((msg)=>chatMessageToLogEvent(msg,teamName)),perAgentEvents=await Promise.all(agents.map(async(agent)=>{let agentName=agent.id,[transcriptEntries,inboxMessages,outboxMessages,pgMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,agentName),readOutbox(repoPath,agentName),readPgMessages(agentName)]),events=[...pgMessages];for(let entry of transcriptEntries){let event=transcriptToLogEvent(entry,agentName,teamName);if(event)events.push(event)}for(let msg of inboxMessages)events.push(inboxMessageToLogEvent(msg,agentName,teamName));for(let msg of outboxMessages)events.push(outboxMessageToLogEvent(msg,agentName,teamName));return events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startNatsFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startNatsFollow(agents,repoPath,teamName,filter,onEvent)}async function startNatsFollow(_agents,_repoPath,_team,filter,onEvent){let nats=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client));if(!await nats.isAvailable())throw Error("NATS is not available. Install nats package and ensure NATS server is running.");let kindsFilter=filter?.kinds?new Set(filter.kinds):null,subs=[],seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,handleNatsEvent=(_subject,data)=>{let event=data;if(!event?.timestamp||!event?.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},allSub=await nats.subscribe("genie.>",handleNatsEvent);return subs.push(allSub),{mode:"nats",stop:async()=>{for(let sub of subs)sub.unsubscribe()}}}async function readTranscriptSafe(agent){try{let{readTranscript:readTranscript2}=await Promise.resolve().then(() => exports_transcript);return await readTranscript2(agent)}catch{return[]}}function kindIcon(kind){switch(kind){case"user":return"U";case"assistant":return"A";case"message":return"M";case"state":return"S";case"tool_call":return"C";case"tool_result":return"R";case"system":return"*";default:return"?"}}function kindColor(kind){switch(kind){case"user":return"\x1B[33m";case"assistant":return"\x1B[36m";case"message":return"\x1B[35m";case"state":return"\x1B[35m";case"tool_call":return"\x1B[32m";case"tool_result":return"\x1B[90m";case"system":return"\x1B[34m";default:return"\x1B[0m"}}var RESET="\x1B[0m",DIM="\x1B[90m",BOLD="\x1B[1m";function summarizeToolCall2(event){let tc=event.data?.toolCall;if(!tc)return event.text;let input=tc.input;switch(tc.name){case"Read":case"Edit":case"Write":return`${tc.name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
1491
1491
  `)[0]}`;case"Grep":return`Grep "${input.pattern}" ${input.path??""}`;case"Glob":return`Glob ${input.pattern}`;case"Agent":return`Agent: ${input.description??""}`;case"SendMessage":return`SendMessage \u2192 ${input.to}: ${String(input.message??"").slice(0,80)}`;case"shell":case"exec_command":return`$ ${(Array.isArray(input.command)?input.command.join(" "):String(input.command??"")).split(`
1492
1492
  `)[0]}`;case"web_search":return`Search: ${input.query??""}`;default:return`${tc.name}`}}function formatEventBlock(event){let time=formatTime(event.timestamp,{seconds:!0,fallback:"??:??:??"}),icon=kindIcon(event.kind),color=kindColor(event.kind),agent=event.agent;if(event.direction==="in")agent=`${event.peer} \u2192 ${event.agent}`;else if(event.direction==="out")agent=`${event.agent} \u2192 ${event.peer}`;let header=`${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}`;if(event.kind==="tool_call"){let summary=summarizeToolCall2(event);return`${header} ${DIM}${summary}${RESET}`}if(event.kind==="tool_result"){let line=event.text.split(`
1493
1493
  `)[0].slice(0,120);return`${header} ${DIM}${line}${RESET}`}let text=event.text.trim();if(text.length<80&&!text.includes(`
@@ -1497,12 +1497,12 @@ ${updated}/${gitItems.length} items updated successfully.`);return}if(!nameOrVer
1497
1497
  `);return`${header}
1498
1498
  ${indented}`}function formatHumanOutput(events,label){let lines=[];if(lines.push(""),lines.push(`${BOLD}Log: ${label}${RESET} (${events.length} events)`),lines.push(""),events.length===0)return lines.push(" No events found."),lines.push(""),lines.join(`
1499
1499
  `);let lastKind=null;for(let event of events){if(lastKind!==null&&!(lastKind==="tool_call"&&event.kind==="tool_call"))lines.push("");lines.push(formatEventBlock(event)),lastKind=event.kind}return lines.push(""),lines.join(`
1500
- `)}async function findAgent(identifier){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;return(await list()).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
1500
+ `)}async function findAgent(identifier,teamName){let agent=await get(identifier);if(agent)return agent;if(agent=await findByTask(identifier),agent)return agent;let all=await list(),teamMatch=(teamName?all.filter((a)=>a.team===teamName):[]).find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()));if(teamMatch)return teamMatch;return all.find((w)=>w.id.includes(identifier)||w.taskId?.includes(identifier)||w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()))??null}async function findTeamAgents(teamName){return(await list()).filter((a)=>a.team===teamName)}function buildFilter2(options){let filter={},hasFilter=!1;if(options.last&&options.last>0)filter.last=options.last,hasFilter=!0;if(options.since)filter.since=options.since,hasFilter=!0;if(options.type)filter.kinds=[options.type],hasFilter=!0;return hasFilter?filter:void 0}function outputNdjson(events){for(let event of events)process.stdout.write(`${JSON.stringify(event)}
1501
1501
  `)}function outputJson(events){process.stdout.write(`${JSON.stringify(events,null,2)}
1502
- `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter2(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
1502
+ `)}async function logCommand(agentName,options){let repoPath=process.cwd(),filter=buildFilter2(options);if(options.follow){await followCommand(agentName,options,repoPath,filter);return}let events,label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);events=await readTeamLog(agents,repoPath,options.team,filter),label=`team:${options.team} (${agents.length} agents)`}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);events=await readAgentLog(agent,repoPath,filter),label=agent.id}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);events=await readTeamLog(allAgents,repoPath,"all",filter),label=`all agents (${allAgents.length})`}if(options.ndjson){outputNdjson(events);return}if(options.json){outputJson(events);return}console.log(formatHumanOutput(events,label))}async function followCommand(agentName,options,repoPath,filter){let lastFollowKind=null,outputEvent=(event)=>{if(options.ndjson)process.stdout.write(`${JSON.stringify(event)}
1503
1503
  `);else{if(lastFollowKind!==null&&!(lastFollowKind==="tool_call"&&event.kind==="tool_call"))process.stdout.write(`
1504
1504
  `);process.stdout.write(`${formatEventBlock(event)}
1505
- `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);label=`team:${options.team} (${agents.length} agents)`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(*)::int as cnt FROM agents WHERE state NOT IN ('done', 'error', 'suspended')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
1505
+ `),lastFollowKind=event.kind}},label;if(options.team){let agents=await findTeamAgents(options.team);if(agents.length===0)console.error(`No agents found for team "${options.team}".`),process.exit(1);label=`team:${options.team} (${agents.length} agents)`;let handle=await followTeamLog(agents,repoPath,options.team,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else if(agentName){let agent=await findAgent(agentName,options.team);if(!agent)console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`),process.exit(1);label=agent.id;let handle=await followAgentLog(agent,repoPath,filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}else{let allAgents=await list();if(allAgents.length===0)console.error("No agents found. Run `genie ls` to see agents."),process.exit(1);label=`all agents (${allAgents.length})`;let handle=await followTeamLog(allAgents,repoPath,"all",filter,outputEvent);console.error(`Following ${label} via ${handle.mode==="nats"?"NATS":"file polling"} (Ctrl+C to stop)...`),setupShutdown(handle.stop)}await new Promise(()=>{})}function setupShutdown(stop){let shutdown2=async()=>{await stop(),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2)}init_db();function parseSince2(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function metricsNowCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),snapshots=await sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`,agentCount=await sql`SELECT count(*)::int as cnt FROM agents WHERE state NOT IN ('done', 'error', 'suspended')`,teamCount=await sql`SELECT count(*)::int as cnt FROM teams WHERE status = 'in_progress'`,snapshot=snapshots[0]??{},data={active_workers:snapshot.active_workers??agentCount[0]?.cnt??0,active_teams:snapshot.active_teams??teamCount[0]?.cnt??0,tmux_sessions:snapshot.tmux_sessions??0,cpu_percent:snapshot.cpu_percent??null,memory_mb:snapshot.memory_mb??null,snapshot_at:snapshot.created_at?new Date(snapshot.created_at).toISOString():null};if(options.json){console.log(JSON.stringify(data,null,2));return}if(console.log("Machine State:"),console.log(` Workers: ${data.active_workers}`),console.log(` Teams: ${data.active_teams}`),console.log(` Tmux: ${data.tmux_sessions} sessions`),data.cpu_percent!==null)console.log(` CPU: ${data.cpu_percent}%`);if(data.memory_mb!==null)console.log(` Memory: ${data.memory_mb} MB`);if(data.snapshot_at)console.log(` As of: ${formatRelativeTimestamp(data.snapshot_at)}`)}async function metricsHistoryCommand(options){if(!await isAvailable())console.error("Database not available."),process.exit(1);let sql=await getConnection(),sinceTs=parseSince2(options.since??"1h"),rows=await sql`
1506
1506
  SELECT active_workers, active_teams, tmux_sessions, cpu_percent, memory_mb, created_at
1507
1507
  FROM machine_snapshots
1508
1508
  WHERE created_at >= ${sinceTs}::timestamptz
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260328.5",
5
+ "version": "4.260328.6",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.6",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.6",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260328.5",
3
+ "version": "4.260328.6",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Unified Log — Canonical event format and file-based aggregator.
2
+ * Unified Log — Canonical event format and aggregator.
3
3
  *
4
- * Aggregates 5 event sources (transcript, mailbox inbox, mailbox outbox,
5
- * team chat, registry state changes) into a single `LogEvent` stream
6
- * sorted by timestamp.
4
+ * Aggregates 6 event sources (transcript, mailbox inbox, mailbox outbox,
5
+ * team chat, PG messages, registry state changes) into a single `LogEvent`
6
+ * stream sorted by timestamp.
7
7
  *
8
8
  * Usage:
9
9
  * const events = await readAgentLog('engineer', repoPath, { last: 50 });
@@ -20,7 +20,7 @@ import type { TranscriptEntry } from './transcript.js';
20
20
  // ============================================================================
21
21
 
22
22
  export type LogEventKind = 'user' | 'assistant' | 'message' | 'state' | 'tool_call' | 'tool_result' | 'system' | 'qa';
23
- export type LogEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook';
23
+ export type LogEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook' | 'send';
24
24
 
25
25
  export interface LogEvent {
26
26
  /** ISO timestamp */
@@ -175,6 +175,44 @@ export function sortByTimestamp(events: LogEvent[]): LogEvent[] {
175
175
  return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
176
176
  }
177
177
 
178
+ // ============================================================================
179
+ // PG Message Source
180
+ // ============================================================================
181
+
182
+ /** Read messages from PG TaskService for a given agent. Returns [] if PG unavailable. */
183
+ async function readPgMessages(agentName: string): Promise<LogEvent[]> {
184
+ try {
185
+ const ts = await import('./task-service.js');
186
+ const actor = { actorType: 'local' as const, actorId: agentName };
187
+ const conversations = await ts.listConversations(actor);
188
+
189
+ const events: LogEvent[] = [];
190
+ for (const conv of conversations) {
191
+ const messages = await ts.getMessages(conv.id, { limit: 100 });
192
+ const members = await ts.getMembers(conv.id);
193
+ const peerMember = members.find((m) => m.actorId !== agentName);
194
+
195
+ for (const msg of messages) {
196
+ const isOutgoing = msg.senderId === agentName;
197
+ const peer = isOutgoing ? peerMember?.actorId : msg.senderId;
198
+ events.push({
199
+ timestamp: msg.createdAt,
200
+ kind: 'message',
201
+ agent: agentName,
202
+ direction: isOutgoing ? 'out' : 'in',
203
+ peer,
204
+ text: msg.body,
205
+ data: { messageId: msg.id, conversationId: msg.conversationId, senderId: msg.senderId },
206
+ source: 'send',
207
+ });
208
+ }
209
+ }
210
+ return events;
211
+ } catch {
212
+ return [];
213
+ }
214
+ }
215
+
178
216
  // ============================================================================
179
217
  // Aggregators
180
218
  // ============================================================================
@@ -188,15 +226,16 @@ export async function readAgentLog(agent: Agent, repoPath: string, filter?: LogF
188
226
  const team = agent.team;
189
227
 
190
228
  // Read all sources in parallel
191
- const [transcriptEntries, inboxMessages, outboxMessages, chatMessages] = await Promise.all([
229
+ const [transcriptEntries, inboxMessages, outboxMessages, chatMessages, pgMessages] = await Promise.all([
192
230
  readTranscriptSafe(agent),
193
231
  inbox(repoPath, agentName),
194
232
  readOutbox(repoPath, agentName),
195
233
  team ? readMessages(repoPath, team) : Promise.resolve([]),
234
+ readPgMessages(agentName),
196
235
  ]);
197
236
 
198
237
  // Convert to LogEvents
199
- const events: LogEvent[] = [];
238
+ const events: LogEvent[] = [...pgMessages];
200
239
 
201
240
  for (const entry of transcriptEntries) {
202
241
  const event = transcriptToLogEvent(entry, agentName, team);
@@ -241,13 +280,14 @@ export async function readTeamLog(
241
280
  agents.map(async (agent) => {
242
281
  const agentName = agent.id;
243
282
 
244
- const [transcriptEntries, inboxMessages, outboxMessages] = await Promise.all([
283
+ const [transcriptEntries, inboxMessages, outboxMessages, pgMessages] = await Promise.all([
245
284
  readTranscriptSafe(agent),
246
285
  inbox(repoPath, agentName),
247
286
  readOutbox(repoPath, agentName),
287
+ readPgMessages(agentName),
248
288
  ]);
249
289
 
250
- const events: LogEvent[] = [];
290
+ const events: LogEvent[] = [...pgMessages];
251
291
  for (const entry of transcriptEntries) {
252
292
  const event = transcriptToLogEvent(entry, agentName, teamName);
253
293
  if (event) events.push(event);
@@ -196,7 +196,7 @@ function formatHumanOutput(events: LogEvent[], label: string): string {
196
196
  // Agent Resolution
197
197
  // ============================================================================
198
198
 
199
- async function findAgent(identifier: string): Promise<agentRegistry.Agent | null> {
199
+ async function findAgent(identifier: string, teamName?: string): Promise<agentRegistry.Agent | null> {
200
200
  let agent = await agentRegistry.get(identifier);
201
201
  if (agent) return agent;
202
202
 
@@ -204,6 +204,17 @@ async function findAgent(identifier: string): Promise<agentRegistry.Agent | null
204
204
  if (agent) return agent;
205
205
 
206
206
  const all = await agentRegistry.list();
207
+
208
+ // Scope fuzzy match to team first, then fall back to global
209
+ const pool = teamName ? all.filter((a) => a.team === teamName) : [];
210
+ const teamMatch = pool.find(
211
+ (w) =>
212
+ w.id.includes(identifier) ||
213
+ w.taskId?.includes(identifier) ||
214
+ w.taskTitle?.toLowerCase().includes(identifier.toLowerCase()),
215
+ );
216
+ if (teamMatch) return teamMatch;
217
+
207
218
  return (
208
219
  all.find(
209
220
  (w) =>
@@ -286,8 +297,8 @@ export async function logCommand(agentName: string | undefined, options: LogOpti
286
297
  events = await readTeamLog(agents, repoPath, options.team, filter);
287
298
  label = `team:${options.team} (${agents.length} agents)`;
288
299
  } else if (agentName) {
289
- // Single agent mode
290
- const agent = await findAgent(agentName);
300
+ // Single agent mode — scope to team if specified
301
+ const agent = await findAgent(agentName, options.team);
291
302
  if (!agent) {
292
303
  console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`);
293
304
  process.exit(1);
@@ -357,7 +368,7 @@ async function followCommand(
357
368
  console.error(`Following ${label} via ${handle.mode === 'nats' ? 'NATS' : 'file polling'} (Ctrl+C to stop)...`);
358
369
  setupShutdown(handle.stop);
359
370
  } else if (agentName) {
360
- const agent = await findAgent(agentName);
371
+ const agent = await findAgent(agentName, options.team);
361
372
  if (!agent) {
362
373
  console.error(`Agent "${agentName}" not found. Run \`genie ls\` to see agents.`);
363
374
  process.exit(1);