@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/genie.js +4 -4
- 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/unified-log.ts +49 -9
- package/src/term-commands/log.ts +15 -4
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.260328.
|
|
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;
|
|
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
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260328.
|
|
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"
|
package/src/lib/unified-log.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified Log — Canonical event format and
|
|
2
|
+
* Unified Log — Canonical event format and aggregator.
|
|
3
3
|
*
|
|
4
|
-
* Aggregates
|
|
5
|
-
* team chat, registry state changes) into a single `LogEvent`
|
|
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);
|
package/src/term-commands/log.ts
CHANGED
|
@@ -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);
|