@automagik/genie 4.260409.5 → 4.260409.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.260409.5",
13
+ "version": "4.260409.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
@@ -3070,7 +3070,7 @@ ${context}`}}}catch{return}}var DENY_PATTERNS=[{test:(cmd)=>/git\s+push\b/i.test
3070
3070
  No agents registered. Add one with: genie agent register <name> --dir <path>`),console.log(`Use --builtins to also see built-in roles and council members.
3071
3071
  `);return}if(entries.length>0)printRegisteredAgentsTable(entries);if(includeBuiltins)printBuiltinAgentsTable()}function registerAgentDirectory(parent){parent.command("directory [name]").alias("dir").description("List all agents or show single entry details from directory").option("--json","Output as JSON").option("--builtins","Include built-in roles and council members").option("--all","Include archived agents").action(async(name,options)=>{try{if(name==="sync"){let resolved=await resolve3("sync");if(resolved&&!resolved.builtin)await showEntry("sync",options.json);else await handleSync()}else if(name)await showEntry(name,options.json);else await listEntries(options.json,options.builtins,options.all)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleSync(){let{findWorkspace:findWorkspace2}=await Promise.resolve().then(() => (init_workspace(),exports_workspace)),ws=findWorkspace2();if(!ws)console.error("Not in a genie workspace. Run `genie init` first."),process.exit(1);console.log(`Syncing agents from ${ws.root}/agents/...`);let result2=await syncAgentDirectory(ws.root);printSyncResult(result2)}init_term_format();init_msg();var _taskService2;async function getTaskService2(){if(!_taskService2)_taskService2=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService2}function printConversation(conv,lastMsg){let name=conv.name??conv.id,type2=conv.type==="dm"?"DM":"Group",linked=conv.linkedEntity?` [${conv.linkedEntity}:${conv.linkedEntityId}]`:"",preview=lastMsg?truncate2(lastMsg.body,50):"(no messages)",time=lastMsg?formatTime(lastMsg.createdAt):"";if(console.log(` ${padRight(name,30)} ${padRight(type2,6)}${linked}`),lastMsg)console.log(` ${time} ${lastMsg.senderId}: ${preview}`);console.log("")}async function handleInbox2(agent,options){let ts3=await getTaskService2(),resolvedAgent=agent??await detectSenderIdentity(),actor={actorType:"local",actorId:resolvedAgent},conversations=await ts3.listConversations(actor);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log(`No conversations for "${resolvedAgent}".`);return}console.log(""),console.log(`INBOX: ${resolvedAgent}`),console.log("\u2500".repeat(60));for(let conv of conversations){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null;printConversation(conv,lastMsg)}}function registerAgentInbox(parent){let inbox2=parent.command("inbox").description("Inbox management \u2014 list messages or watch for new ones");inbox2.command("list [agent]",{isDefault:!0}).description("List conversations with recent messages").option("--json","Output as JSON").action(async(agent,options)=>{try{await handleInbox2(agent,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),inbox2.command("watch").description("Run inbox watcher in foreground (Ctrl+C to stop)").action(async()=>{let{checkInboxes:checkInboxes2,getInboxPollIntervalMs:getInboxPollIntervalMs2,startInboxWatcher:startInboxWatcher2,stopInboxWatcher:stopInboxWatcher2}=await Promise.resolve().then(() => (init_inbox_watcher(),exports_inbox_watcher)),pollMs=getInboxPollIntervalMs2();if(pollMs===0)console.log("Inbox watcher is disabled (GENIE_INBOX_POLL_MS=0)"),process.exit(0);console.log(`Inbox watcher starting (poll every ${pollMs/1000}s)`),console.log(`Press Ctrl+C to stop.
3072
3072
  `);let initial=await checkInboxes2();if(initial.length>0)console.log(`[inbox-watcher] Spawned team-leads for: ${initial.join(", ")}`);let handle=startInboxWatcher2({listTeamsWithUnreadInbox:(await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams))).listTeamsWithUnreadInbox,isTeamActive:async(teamName)=>{let{isTeamActive:isTeamActive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isTeamActive2(teamName)},isAgentAlive:async(agentName)=>{let{isAgentAlive:isAgentAlive2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn));return isAgentAlive2(agentName)},ensureTeamLead:async(teamName,workingDir)=>{let{ensureTeamLead:ensureTeamLead2}=await Promise.resolve().then(() => (init_team_auto_spawn(),exports_team_auto_spawn)),result2=await ensureTeamLead2(teamName,workingDir);return console.log(`[inbox-watcher] Spawned team-lead for "${teamName}" in ${workingDir}`),result2},warn:(msg)=>console.log(msg)}),shutdown2=()=>{console.log(`
3073
- Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})})}init_agents();function registerAgentKill(parent){parent.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentList(parent){parent.command("list").alias("ls").description("List registered agents with runtime status").option("--json","Output as JSON").option("--source <name>","Filter by executor metadata source (e.g. omni)").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_registry();init_term_format();init_mailbox();init_runtime_events();init_db();function rowToMessage2(row){return{id:row.id,sender:row.sender,body:row.body,timestamp:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)}}async function readMessages(repoPath,teamName,since){let sql=await getConnection();if(since)return(await sql`
3073
+ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.on("SIGINT",shutdown2),process.on("SIGTERM",shutdown2),await new Promise(()=>{})})}init_agents();function registerAgentKill(parent){parent.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentList(parent){parent.command("list").alias("ls").description("List registered agents with runtime status").option("--json","Output as JSON").option("--source <name>","Filter by executor metadata source (e.g. omni)").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agent_registry();init_term_format();init_db();init_mailbox();init_runtime_events();init_db();function rowToMessage2(row){return{id:row.id,sender:row.sender,body:row.body,timestamp:row.created_at instanceof Date?row.created_at.toISOString():String(row.created_at)}}async function readMessages(repoPath,teamName,since){let sql=await getConnection();if(since)return(await sql`
3074
3074
  SELECT * FROM team_chat
3075
3075
  WHERE team = ${teamName} AND repo_path = ${repoPath} AND created_at >= ${since}
3076
3076
  ORDER BY created_at ASC
@@ -3078,9 +3078,17 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
3078
3078
  SELECT * FROM team_chat
3079
3079
  WHERE team = ${teamName} AND repo_path = ${repoPath}
3080
3080
  ORDER BY created_at ASC
3081
- `).map(rowToMessage2)}function mailboxActorKeys(agent){let keys=[agent.id];if(agent.role&&agent.role!==agent.id)keys.push(agent.role);if(agent.customName&&!keys.includes(agent.customName))keys.push(agent.customName);return keys}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 result2=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result2=result2.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}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,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages,chatMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys),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,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys)]),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 startPgFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startPgFollow(agents,repoPath,teamName,filter,onEvent)}async function startPgFollow(agents,repoPath,team,filter,onEvent){let kindsFilter=filter?.kinds?new Set(filter.kinds):null,agentIds=new Set(agents.map((agent)=>agent.id)),seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,matchesScope=(event)=>{if(team==="all")return!0;if(team&&event.team===team)return!0;return agentIds.has(event.agent)},handleRuntimeEvent=(event)=>{if(!event.timestamp||!event.kind)return;if(kindsFilter&&!kindsFilter.has(event.kind))return;if(!matchesScope(event))return;let key=eventKey(event);if(seenKeys.has(key))return;seenKeys.add(key),onEvent(event)},handle=await followRuntimeEvents({repoPath:team==="all"?void 0:repoPath,agentIds:team==="all"?void 0:[...agentIds],team:team&&team!=="all"?team:void 0,kinds:filter?.kinds,scopeMode:team&&team!=="all"?"any":"all"},handleRuntimeEvent,{pollIntervalMs:500});return{mode:"pg",stop:()=>handle.stop()}}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(`
3081
+ `).map(rowToMessage2)}function mailboxActorKeys(agent){let keys=[agent.id];if(agent.role&&agent.role!==agent.id)keys.push(agent.role);if(agent.customName&&!keys.includes(agent.customName))keys.push(agent.customName);return keys}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 result2=events;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);result2=result2.filter((e)=>kinds.has(e.kind))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}function sortByTimestamp(events){return events.sort((a,b2)=>new Date(a.timestamp).getTime()-new Date(b2.timestamp).getTime())}var SDK_KIND_MAP={"sdk.user.message":"user","sdk.assistant.message":"assistant","sdk.tool.summary":"tool_call","sdk.system":"system","sdk.result.success":"system","sdk.hook.started":"system","sdk.hook.response":"system","sdk.rate_limit":"system"};function sdkAuditRowToLogEvent(row){let details=row.details??{};return{timestamp:typeof row.created_at==="string"?row.created_at:new Date(row.created_at).toISOString(),kind:SDK_KIND_MAP[row.event_type]??"system",agent:row.actor??"unknown",text:details.textPreview??details.summaryPreview??row.event_type,data:details,source:"sdk"}}async function readSdkAuditEvents(agentId,filter){try{if(!await isAvailable())return[];let sql=await getConnection(),conditions=["entity_type = 'sdk_message'","actor = $1"],values2=[agentId],paramIdx=2;if(filter?.since)conditions.push(`created_at >= $${paramIdx++}::timestamptz`),values2.push(filter.since);let where=`WHERE ${conditions.join(" AND ")}`,limit=filter?.last??500,rows=await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
3082
+ FROM audit_events ${where}
3083
+ ORDER BY created_at ASC
3084
+ LIMIT ${limit}`,values2),events=[];for(let row of rows)if(SDK_KIND_MAP[row.event_type])events.push(sdkAuditRowToLogEvent(row));if(filter?.kinds&&filter.kinds.length>0){let kinds=new Set(filter.kinds);return events.filter((e)=>kinds.has(e.kind))}return events}catch{return[]}}async function readAgentLog(agent,repoPath,filter){let{id:agentName,team}=agent,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages,chatMessages,sdkEvents]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys),team?readMessages(repoPath,team):Promise.resolve([]),readSdkAuditEvents(agentName,filter)]),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));events.push(...sdkEvents);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,mailboxKeys=mailboxActorKeys(agent),[transcriptEntries,inboxMessages,outboxMessages,sdkEvents]=await Promise.all([readTranscriptSafe(agent),inbox(repoPath,mailboxKeys),readOutbox(repoPath,mailboxKeys),readSdkAuditEvents(agentName,filter)]),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.push(...sdkEvents),events})),allEvents=[...chatEvents,...perAgentEvents.flat()],sorted=sortByTimestamp(allEvents);return applyLogFilter(sorted,filter)}async function followAgentLog(agent,repoPath,filter,onEvent){return startPgFollow([agent],repoPath,agent.team,filter,onEvent)}async function followTeamLog(agents,repoPath,teamName,filter,onEvent){return startPgFollow(agents,repoPath,teamName,filter,onEvent)}async function startPgFollow(agents,repoPath,team,filter,onEvent){let kindsFilter=filter?.kinds?new Set(filter.kinds):null,agentIds=new Set(agents.map((agent)=>agent.id)),seenKeys=new Set,eventKey=(e)=>`${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0,80)}`,dedupAndEmit=(event)=>{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)},matchesScope=(event)=>{if(team==="all")return!0;if(team&&event.team===team)return!0;return agentIds.has(event.agent)},handleRuntimeEvent=(event)=>{if(!matchesScope(event))return;dedupAndEmit(event)},handle=await followRuntimeEvents({repoPath:team==="all"?void 0:repoPath,agentIds:team==="all"?void 0:[...agentIds],team:team&&team!=="all"?team:void 0,kinds:filter?.kinds,scopeMode:team&&team!=="all"?"any":"all"},handleRuntimeEvent,{pollIntervalMs:500}),sdkPollActive=!0,sdkLastId=0;try{if(await isAvailable()){let sql=await getConnection(),[row]=await sql`SELECT COALESCE(MAX(id), 0) AS max_id FROM audit_events WHERE entity_type = 'sdk_message'`;sdkLastId=Number(row?.max_id??0)}}catch{}let drainSdkAuditEvents=async()=>{if(!await isAvailable())return;let sql=await getConnection(),agentList=[...agentIds],rows=await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
3085
+ FROM audit_events
3086
+ WHERE entity_type = 'sdk_message' AND id > $1
3087
+ AND actor = ANY($2)
3088
+ ORDER BY id ASC
3089
+ LIMIT 100`,[sdkLastId,agentList]);for(let row of rows){if(SDK_KIND_MAP[row.event_type])dedupAndEmit(sdkAuditRowToLogEvent(row));sdkLastId=Math.max(sdkLastId,Number(row.id))}};return(async()=>{while(sdkPollActive){try{await drainSdkAuditEvents()}catch{}await new Promise((resolve5)=>setTimeout(resolve5,500))}})(),{mode:"pg",stop:async()=>{sdkPollActive=!1,await handle.stop()}}}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(`
3082
3090
  `)[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(`
3083
- `)[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),color2=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} ${color2}[${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(`
3091
+ `)[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),color2=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 sdkTag=event.source==="sdk"?` ${DIM}[SDK]${RESET}`:"",header=`${DIM}${time}${RESET} ${color2}[${icon}]${RESET} ${BOLD}${agent}${RESET}${sdkTag}`;if(event.kind==="tool_call"){let summary=summarizeToolCall2(event);return`${header} ${DIM}${summary}${RESET}`}if(event.kind==="tool_result"){let line=event.text.split(`
3084
3092
  `)[0].slice(0,120);return`${header} ${DIM}${line}${RESET}`}let text=event.text.trim();if(text.length<80&&!text.includes(`
3085
3093
  `))return`${header}
3086
3094
  ${text}`;let indented=text.split(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260409.5",
3
+ "version": "4.260409.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.260409.5",
3
+ "version": "4.260409.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.260409.5",
3
+ "version": "4.260409.6",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -73,7 +73,7 @@ export type RuntimeEventKind =
73
73
  | 'tool_result'
74
74
  | 'system'
75
75
  | 'qa';
76
- export type RuntimeEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook';
76
+ export type RuntimeEventSource = 'provider' | 'mailbox' | 'chat' | 'registry' | 'hook' | 'sdk';
77
77
  export type RuntimeEventDirection = 'in' | 'out';
78
78
 
79
79
  export interface RuntimeEvent {
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
10
10
  import type { Agent } from './agent-registry.js';
11
+ import { recordAuditEvent } from './audit.js';
11
12
  import { readOutbox, send } from './mailbox.js';
12
13
  import { publishRuntimeEvent } from './runtime-events.js';
13
14
  import { postMessage } from './team-chat.js';
@@ -23,6 +24,7 @@ import {
23
24
  outboxMessageToLogEvent,
24
25
  readAgentLog,
25
26
  readTeamLog,
27
+ sdkAuditRowToLogEvent,
26
28
  sortByTimestamp,
27
29
  transcriptToLogEvent,
28
30
  } from './unified-log.js';
@@ -593,3 +595,225 @@ describe.skipIf(!DB_AVAILABLE)('mailbox outbox', () => {
593
595
  expect(outbox[1].body).toBe('world');
594
596
  });
595
597
  });
598
+
599
+ // ============================================================================
600
+ // SDK audit event tests
601
+ // ============================================================================
602
+
603
+ describe('sdkAuditRowToLogEvent', () => {
604
+ test('maps sdk.assistant.message to assistant kind with sdk source', () => {
605
+ const event = sdkAuditRowToLogEvent({
606
+ id: 1,
607
+ entity_type: 'sdk_message',
608
+ entity_id: 'executor-123',
609
+ event_type: 'sdk.assistant.message',
610
+ actor: 'my-agent',
611
+ details: { textPreview: 'Hello from SDK agent' },
612
+ created_at: '2026-04-09T10:00:00.000Z',
613
+ });
614
+
615
+ expect(event.kind).toBe('assistant');
616
+ expect(event.agent).toBe('my-agent');
617
+ expect(event.text).toBe('Hello from SDK agent');
618
+ expect(event.source).toBe('sdk');
619
+ });
620
+
621
+ test('maps sdk.user.message to user kind', () => {
622
+ const event = sdkAuditRowToLogEvent({
623
+ id: 2,
624
+ entity_type: 'sdk_message',
625
+ entity_id: 'executor-123',
626
+ event_type: 'sdk.user.message',
627
+ actor: 'my-agent',
628
+ details: { textPreview: 'User said hello' },
629
+ created_at: '2026-04-09T10:01:00.000Z',
630
+ });
631
+
632
+ expect(event.kind).toBe('user');
633
+ expect(event.text).toBe('User said hello');
634
+ });
635
+
636
+ test('maps sdk.tool.summary to tool_call kind', () => {
637
+ const event = sdkAuditRowToLogEvent({
638
+ id: 3,
639
+ entity_type: 'sdk_message',
640
+ entity_id: 'executor-123',
641
+ event_type: 'sdk.tool.summary',
642
+ actor: 'my-agent',
643
+ details: { textPreview: 'Read /tmp/file.ts' },
644
+ created_at: '2026-04-09T10:02:00.000Z',
645
+ });
646
+
647
+ expect(event.kind).toBe('tool_call');
648
+ });
649
+
650
+ test('maps sdk.system to system kind', () => {
651
+ const event = sdkAuditRowToLogEvent({
652
+ id: 4,
653
+ entity_type: 'sdk_message',
654
+ entity_id: 'executor-123',
655
+ event_type: 'sdk.system',
656
+ actor: 'my-agent',
657
+ details: { textPreview: 'Session initialized' },
658
+ created_at: '2026-04-09T10:03:00.000Z',
659
+ });
660
+
661
+ expect(event.kind).toBe('system');
662
+ });
663
+
664
+ test('maps sdk.result.success to system kind', () => {
665
+ const event = sdkAuditRowToLogEvent({
666
+ id: 5,
667
+ entity_type: 'sdk_message',
668
+ entity_id: 'executor-123',
669
+ event_type: 'sdk.result.success',
670
+ actor: 'my-agent',
671
+ details: { textPreview: 'Task completed' },
672
+ created_at: '2026-04-09T10:04:00.000Z',
673
+ });
674
+
675
+ expect(event.kind).toBe('system');
676
+ });
677
+
678
+ test('maps sdk.rate_limit to system kind', () => {
679
+ const event = sdkAuditRowToLogEvent({
680
+ id: 6,
681
+ entity_type: 'sdk_message',
682
+ entity_id: 'executor-123',
683
+ event_type: 'sdk.rate_limit',
684
+ actor: 'my-agent',
685
+ details: {},
686
+ created_at: '2026-04-09T10:05:00.000Z',
687
+ });
688
+
689
+ expect(event.kind).toBe('system');
690
+ expect(event.text).toBe('sdk.rate_limit');
691
+ });
692
+
693
+ test('falls back to event_type as text when no textPreview', () => {
694
+ const event = sdkAuditRowToLogEvent({
695
+ id: 7,
696
+ entity_type: 'sdk_message',
697
+ entity_id: 'executor-123',
698
+ event_type: 'sdk.hook.started',
699
+ actor: 'my-agent',
700
+ details: {},
701
+ created_at: '2026-04-09T10:06:00.000Z',
702
+ });
703
+
704
+ expect(event.kind).toBe('system');
705
+ expect(event.text).toBe('sdk.hook.started');
706
+ });
707
+
708
+ test('unmapped event types default to system kind', () => {
709
+ const event = sdkAuditRowToLogEvent({
710
+ id: 8,
711
+ entity_type: 'sdk_message',
712
+ entity_id: 'executor-123',
713
+ event_type: 'sdk.unknown.future',
714
+ actor: 'my-agent',
715
+ details: { textPreview: 'something new' },
716
+ created_at: '2026-04-09T10:07:00.000Z',
717
+ });
718
+
719
+ expect(event.kind).toBe('system');
720
+ expect(event.text).toBe('something new');
721
+ });
722
+
723
+ test('handles null actor gracefully', () => {
724
+ const event = sdkAuditRowToLogEvent({
725
+ id: 9,
726
+ entity_type: 'sdk_message',
727
+ entity_id: 'executor-123',
728
+ event_type: 'sdk.assistant.message',
729
+ actor: null,
730
+ details: { textPreview: 'hello' },
731
+ created_at: '2026-04-09T10:08:00.000Z',
732
+ });
733
+
734
+ expect(event.agent).toBe('unknown');
735
+ });
736
+ });
737
+
738
+ describe.skipIf(!DB_AVAILABLE)('SDK events in readAgentLog', () => {
739
+ test('includes SDK audit events in agent log', async () => {
740
+ const repo = '/tmp/ulog-sdk-agent';
741
+ const agent = makeAgent('sdk-test-agent', 'sdk-team', repo);
742
+
743
+ // Insert SDK audit events
744
+ await recordAuditEvent('sdk_message', 'executor-1', 'sdk.assistant.message', 'sdk-test-agent', {
745
+ textPreview: 'SDK assistant response',
746
+ });
747
+ await recordAuditEvent('sdk_message', 'executor-1', 'sdk.user.message', 'sdk-test-agent', {
748
+ textPreview: 'SDK user input',
749
+ });
750
+
751
+ const events = await readAgentLog(agent, repo);
752
+ const sdkEvents = events.filter((e) => e.source === 'sdk');
753
+
754
+ expect(sdkEvents.length).toBeGreaterThanOrEqual(2);
755
+ expect(sdkEvents.some((e) => e.kind === 'assistant' && e.text === 'SDK assistant response')).toBe(true);
756
+ expect(sdkEvents.some((e) => e.kind === 'user' && e.text === 'SDK user input')).toBe(true);
757
+ });
758
+
759
+ test('SDK events are sorted with other sources by timestamp', async () => {
760
+ const repo = '/tmp/ulog-sdk-sorted';
761
+ const agent = makeAgent('sdk-sort-agent', undefined, repo);
762
+
763
+ // Insert an SDK event
764
+ await recordAuditEvent('sdk_message', 'executor-2', 'sdk.assistant.message', 'sdk-sort-agent', {
765
+ textPreview: 'SDK msg',
766
+ });
767
+ // Insert a mailbox event
768
+ await send(repo, 'reviewer', 'sdk-sort-agent', 'mailbox msg');
769
+
770
+ const events = await readAgentLog(agent, repo);
771
+
772
+ // Verify chronological order is maintained across sources
773
+ for (let i = 1; i < events.length; i++) {
774
+ expect(new Date(events[i].timestamp).getTime()).toBeGreaterThanOrEqual(
775
+ new Date(events[i - 1].timestamp).getTime(),
776
+ );
777
+ }
778
+ });
779
+
780
+ test('--type filter works with SDK event kinds', async () => {
781
+ const repo = '/tmp/ulog-sdk-filter';
782
+ const agent = makeAgent('sdk-filter-agent', undefined, repo);
783
+
784
+ await recordAuditEvent('sdk_message', 'executor-3', 'sdk.assistant.message', 'sdk-filter-agent', {
785
+ textPreview: 'assistant msg',
786
+ });
787
+ await recordAuditEvent('sdk_message', 'executor-3', 'sdk.system', 'sdk-filter-agent', {
788
+ textPreview: 'system msg',
789
+ });
790
+
791
+ const events = await readAgentLog(agent, repo, { kinds: ['assistant'] });
792
+ const sdkEvents = events.filter((e) => e.source === 'sdk');
793
+
794
+ for (const e of sdkEvents) {
795
+ expect(e.kind).toBe('assistant');
796
+ }
797
+ });
798
+ });
799
+
800
+ describe.skipIf(!DB_AVAILABLE)('SDK events in readTeamLog', () => {
801
+ test('includes SDK events from multiple agents interleaved', async () => {
802
+ const repo = '/tmp/ulog-sdk-team';
803
+ const eng = makeAgent('sdk-team-eng', 'sdk-log-team', repo);
804
+ const rev = makeAgent('sdk-team-rev', 'sdk-log-team', repo);
805
+
806
+ await recordAuditEvent('sdk_message', 'executor-4', 'sdk.assistant.message', 'sdk-team-eng', {
807
+ textPreview: 'eng SDK response',
808
+ });
809
+ await recordAuditEvent('sdk_message', 'executor-5', 'sdk.assistant.message', 'sdk-team-rev', {
810
+ textPreview: 'rev SDK response',
811
+ });
812
+
813
+ const events = await readTeamLog([eng, rev], repo, 'sdk-log-team');
814
+ const sdkEvents = events.filter((e) => e.source === 'sdk');
815
+
816
+ expect(sdkEvents.some((e) => e.agent === 'sdk-team-eng')).toBe(true);
817
+ expect(sdkEvents.some((e) => e.agent === 'sdk-team-rev')).toBe(true);
818
+ });
819
+ });
@@ -11,6 +11,8 @@
11
11
  */
12
12
 
13
13
  import type { Agent } from './agent-registry.js';
14
+ import type { AuditEventRow } from './audit.js';
15
+ import { getConnection, isAvailable } from './db.js';
14
16
  import { type MailboxMessage, inbox, readOutbox } from './mailbox.js';
15
17
  import {
16
18
  type RuntimeEvent,
@@ -188,6 +190,84 @@ export function sortByTimestamp(events: LogEvent[]): LogEvent[] {
188
190
  return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
189
191
  }
190
192
 
193
+ // ============================================================================
194
+ // SDK Audit Events
195
+ // ============================================================================
196
+
197
+ /** Map SDK event types to existing LogEventKind values. */
198
+ const SDK_KIND_MAP: Record<string, LogEventKind> = {
199
+ 'sdk.user.message': 'user',
200
+ 'sdk.assistant.message': 'assistant',
201
+ 'sdk.tool.summary': 'tool_call',
202
+ 'sdk.system': 'system',
203
+ 'sdk.result.success': 'system',
204
+ 'sdk.hook.started': 'system',
205
+ 'sdk.hook.response': 'system',
206
+ 'sdk.rate_limit': 'system',
207
+ };
208
+
209
+ /** Convert an audit_events row (entity_type=sdk_message) to a LogEvent. */
210
+ export function sdkAuditRowToLogEvent(row: AuditEventRow): LogEvent {
211
+ const details = row.details ?? {};
212
+ return {
213
+ timestamp: typeof row.created_at === 'string' ? row.created_at : new Date(row.created_at).toISOString(),
214
+ kind: SDK_KIND_MAP[row.event_type] ?? 'system',
215
+ agent: row.actor ?? 'unknown',
216
+ text: (details.textPreview as string) ?? (details.summaryPreview as string) ?? row.event_type,
217
+ data: details,
218
+ source: 'sdk',
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Read SDK audit events for an agent from the audit_events table.
224
+ * Filters by entity_type='sdk_message' AND actor=agentId.
225
+ */
226
+ async function readSdkAuditEvents(agentId: string, filter?: LogFilter): Promise<LogEvent[]> {
227
+ try {
228
+ if (!(await isAvailable())) return [];
229
+ const sql = await getConnection();
230
+
231
+ const conditions = [`entity_type = 'sdk_message'`, 'actor = $1'];
232
+ const values: unknown[] = [agentId];
233
+ let paramIdx = 2;
234
+
235
+ if (filter?.since) {
236
+ conditions.push(`created_at >= $${paramIdx++}::timestamptz`);
237
+ values.push(filter.since);
238
+ }
239
+
240
+ const where = `WHERE ${conditions.join(' AND ')}`;
241
+ const limit = filter?.last ?? 500;
242
+
243
+ const rows = (await sql.unsafe(
244
+ `SELECT id, entity_type, entity_id, event_type, actor, details, created_at
245
+ FROM audit_events ${where}
246
+ ORDER BY created_at ASC
247
+ LIMIT ${limit}`,
248
+ values,
249
+ )) as unknown as AuditEventRow[];
250
+
251
+ const events: LogEvent[] = [];
252
+ for (const row of rows) {
253
+ // Only include event types we have a mapping for
254
+ if (SDK_KIND_MAP[row.event_type]) {
255
+ events.push(sdkAuditRowToLogEvent(row));
256
+ }
257
+ }
258
+
259
+ // Apply kinds filter if specified
260
+ if (filter?.kinds && filter.kinds.length > 0) {
261
+ const kinds = new Set(filter.kinds);
262
+ return events.filter((e) => kinds.has(e.kind));
263
+ }
264
+
265
+ return events;
266
+ } catch {
267
+ return [];
268
+ }
269
+ }
270
+
191
271
  // ============================================================================
192
272
  // Aggregators
193
273
  // ============================================================================
@@ -202,11 +282,12 @@ export async function readAgentLog(agent: Agent, repoPath: string, filter?: LogF
202
282
  const mailboxKeys = mailboxActorKeys(agent);
203
283
 
204
284
  // Read all sources in parallel
205
- const [transcriptEntries, inboxMessages, outboxMessages, chatMessages] = await Promise.all([
285
+ const [transcriptEntries, inboxMessages, outboxMessages, chatMessages, sdkEvents] = await Promise.all([
206
286
  readTranscriptSafe(agent),
207
287
  inbox(repoPath, mailboxKeys),
208
288
  readOutbox(repoPath, mailboxKeys),
209
289
  team ? readMessages(repoPath, team) : Promise.resolve([]),
290
+ readSdkAuditEvents(agentName, filter),
210
291
  ]);
211
292
 
212
293
  // Convert to LogEvents
@@ -231,6 +312,8 @@ export async function readAgentLog(agent: Agent, repoPath: string, filter?: LogF
231
312
  }
232
313
  }
233
314
 
315
+ events.push(...sdkEvents);
316
+
234
317
  // Sort by time, then filter
235
318
  const sorted = sortByTimestamp(events);
236
319
  return applyLogFilter(sorted, filter);
@@ -256,10 +339,11 @@ export async function readTeamLog(
256
339
  const agentName = agent.id;
257
340
  const mailboxKeys = mailboxActorKeys(agent);
258
341
 
259
- const [transcriptEntries, inboxMessages, outboxMessages] = await Promise.all([
342
+ const [transcriptEntries, inboxMessages, outboxMessages, sdkEvents] = await Promise.all([
260
343
  readTranscriptSafe(agent),
261
344
  inbox(repoPath, mailboxKeys),
262
345
  readOutbox(repoPath, mailboxKeys),
346
+ readSdkAuditEvents(agentName, filter),
263
347
  ]);
264
348
 
265
349
  const events: LogEvent[] = [];
@@ -273,6 +357,7 @@ export async function readTeamLog(
273
357
  for (const msg of outboxMessages) {
274
358
  events.push(outboxMessageToLogEvent(msg, agentName, teamName));
275
359
  }
360
+ events.push(...sdkEvents);
276
361
  return events;
277
362
  }),
278
363
  );
@@ -321,6 +406,8 @@ export async function followTeamLog(
321
406
  /**
322
407
  * PG-first follow: wake up on LISTEN/NOTIFY, replay by event id cursor, and
323
408
  * filter by agent/team before emitting to the caller.
409
+ *
410
+ * Also polls audit_events for SDK agent events on the same interval.
324
411
  */
325
412
  async function startPgFollow(
326
413
  agents: Agent[],
@@ -335,6 +422,15 @@ async function startPgFollow(
335
422
 
336
423
  const eventKey = (e: LogEvent): string => `${e.timestamp}|${e.kind}|${e.agent}|${e.text.slice(0, 80)}`;
337
424
 
425
+ const dedupAndEmit = (event: LogEvent) => {
426
+ if (!event.timestamp || !event.kind) return;
427
+ if (kindsFilter && !kindsFilter.has(event.kind)) return;
428
+ const key = eventKey(event);
429
+ if (seenKeys.has(key)) return;
430
+ seenKeys.add(key);
431
+ onEvent(event);
432
+ };
433
+
338
434
  const matchesScope = (event: RuntimeEvent) => {
339
435
  if (team === 'all') return true;
340
436
  if (team && event.team === team) return true;
@@ -342,14 +438,8 @@ async function startPgFollow(
342
438
  };
343
439
 
344
440
  const handleRuntimeEvent = (event: RuntimeEvent) => {
345
- if (!event.timestamp || !event.kind) return;
346
- if (kindsFilter && !kindsFilter.has(event.kind)) return;
347
441
  if (!matchesScope(event)) return;
348
- // Dedup (same event can arrive on multiple matching subjects)
349
- const key = eventKey(event);
350
- if (seenKeys.has(key)) return;
351
- seenKeys.add(key);
352
- onEvent(event);
442
+ dedupAndEmit(event);
353
443
  };
354
444
 
355
445
  const handle = await followRuntimeEvents(
@@ -366,9 +456,64 @@ async function startPgFollow(
366
456
  },
367
457
  );
368
458
 
459
+ // SDK audit event poller — polls audit_events table for sdk_message events
460
+ let sdkPollActive = true;
461
+ let sdkLastId = 0;
462
+
463
+ // Seed the cursor: get the max id so we only see new events
464
+ try {
465
+ if (await isAvailable()) {
466
+ const sql = await getConnection();
467
+ const [row] =
468
+ await sql`SELECT COALESCE(MAX(id), 0) AS max_id FROM audit_events WHERE entity_type = 'sdk_message'`;
469
+ sdkLastId = Number(row?.max_id ?? 0);
470
+ }
471
+ } catch {
472
+ // Best effort — start from 0 if seed fails
473
+ }
474
+
475
+ const drainSdkAuditEvents = async () => {
476
+ if (!(await isAvailable())) return;
477
+ const sql = await getConnection();
478
+ const agentList = [...agentIds];
479
+ const rows = (await sql.unsafe(
480
+ `SELECT id, entity_type, entity_id, event_type, actor, details, created_at
481
+ FROM audit_events
482
+ WHERE entity_type = 'sdk_message' AND id > $1
483
+ AND actor = ANY($2)
484
+ ORDER BY id ASC
485
+ LIMIT 100`,
486
+ [sdkLastId, agentList],
487
+ )) as unknown as (AuditEventRow & { id: number })[];
488
+
489
+ for (const row of rows) {
490
+ if (SDK_KIND_MAP[row.event_type]) {
491
+ dedupAndEmit(sdkAuditRowToLogEvent(row));
492
+ }
493
+ sdkLastId = Math.max(sdkLastId, Number(row.id));
494
+ }
495
+ };
496
+
497
+ const sdkPoll = async () => {
498
+ while (sdkPollActive) {
499
+ try {
500
+ await drainSdkAuditEvents();
501
+ } catch {
502
+ // Best effort — skip failed polls
503
+ }
504
+ await new Promise((resolve) => setTimeout(resolve, 500));
505
+ }
506
+ };
507
+
508
+ // Start SDK poller in background
509
+ sdkPoll();
510
+
369
511
  return {
370
512
  mode: 'pg',
371
- stop: () => handle.stop(),
513
+ stop: async () => {
514
+ sdkPollActive = false;
515
+ await handle.stop();
516
+ },
372
517
  };
373
518
  }
374
519
 
@@ -137,7 +137,8 @@ function formatEventBlock(event: LogEvent): string {
137
137
  if (event.direction === 'in') agent = `${event.peer} → ${event.agent}`;
138
138
  else if (event.direction === 'out') agent = `${event.agent} → ${event.peer}`;
139
139
 
140
- const header = `${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}`;
140
+ const sdkTag = event.source === 'sdk' ? ` ${DIM}[SDK]${RESET}` : '';
141
+ const header = `${DIM}${time}${RESET} ${color}[${icon}]${RESET} ${BOLD}${agent}${RESET}${sdkTag}`;
141
142
 
142
143
  // Tool calls: one-line summary
143
144
  if (event.kind === 'tool_call') {
@@ -261,14 +261,14 @@ describe.skipIf(!DB_AVAILABLE)('buildTeamLeadCommand (shared module)', () => {
261
261
 
262
262
  test('includes --append-system-prompt-file when systemPromptFile provided (default promptMode)', async () => {
263
263
  const { buildTeamLeadCommand } = await import('../lib/team-lead-command.js');
264
- const cmd = buildTeamLeadCommand('genie', { systemPromptFile: '/tmp/test-agents.md' });
264
+ const cmd = buildTeamLeadCommand('genie', { systemPromptFile: '/tmp/test-agents.md', promptMode: 'append' });
265
265
  expect(cmd).toContain('--append-system-prompt-file');
266
266
  expect(cmd).toContain('/tmp/test-agents.md');
267
267
  });
268
268
 
269
269
  test('file path is passed directly, not copied', async () => {
270
270
  const { buildTeamLeadCommand } = await import('../lib/team-lead-command.js');
271
- const cmd = buildTeamLeadCommand('genie', { systemPromptFile: '/path/to/AGENTS.md' });
271
+ const cmd = buildTeamLeadCommand('genie', { systemPromptFile: '/path/to/AGENTS.md', promptMode: 'append' });
272
272
  expect(cmd).toContain('--append-system-prompt-file');
273
273
  expect(cmd).toContain('/path/to/AGENTS.md');
274
274
  });