@automagik/genie 4.260503.1 → 4.260503.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js
CHANGED
|
@@ -230,14 +230,13 @@ ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(f
|
|
|
230
230
|
'(no message)'
|
|
231
231
|
)
|
|
232
232
|
ORDER BY count DESC
|
|
233
|
-
LIMIT 50`,[sinceTs])}function getActor(){return process.env.GENIE_AGENT_NAME??"cli"}async function queryCostBreakdown(since,groupBy="agent"){let sql=await getConnection(),sinceTs=parseSince(since),groupExpr=groupBy==="agent"?"COALESCE(
|
|
233
|
+
LIMIT 50`,[sinceTs])}function getActor(){return process.env.GENIE_AGENT_NAME??"cli"}async function queryCostBreakdown(since,groupBy="agent"){let sql=await getConnection(),sinceTs=parseSince(since),groupExpr=groupBy==="agent"?"COALESCE(agent_id, 'unknown')":groupBy==="wish"?"COALESCE(details->>'wish_slug', entity_id)":"model";return await sql.unsafe(`SELECT
|
|
234
234
|
${groupExpr} AS group_key,
|
|
235
|
-
COALESCE(SUM(
|
|
235
|
+
COALESCE(SUM(cost_usd), 0)::float AS total_cost,
|
|
236
236
|
COUNT(*)::int AS request_count,
|
|
237
|
-
COALESCE(AVG((
|
|
238
|
-
FROM
|
|
239
|
-
WHERE
|
|
240
|
-
AND created_at >= $1::timestamptz
|
|
237
|
+
COALESCE(AVG(NULLIF(cost_usd, 0)), 0)::float AS avg_cost
|
|
238
|
+
FROM v_claude_usage_events
|
|
239
|
+
WHERE created_at >= $1::timestamptz
|
|
241
240
|
GROUP BY ${groupExpr}
|
|
242
241
|
ORDER BY total_cost DESC
|
|
243
242
|
LIMIT 100`,[sinceTs])}async function queryToolUsage(since,groupBy="tool"){let sql=await getConnection(),sinceTs=parseSince(since),groupExpr=groupBy==="tool"?"COALESCE(details->>'tool_name', entity_id)":"COALESCE(actor, 'unknown')";return await sql.unsafe(`SELECT
|
|
@@ -288,7 +287,10 @@ ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(f
|
|
|
288
287
|
LIMIT $3`,[entityId,sinceTs,limit])}async function querySummary(since){let sql=await getConnection(),sinceTs=parseSince(since),r=(await sql.unsafe(`SELECT
|
|
289
288
|
COUNT(*) FILTER (WHERE entity_type = 'worker' AND event_type = 'spawn')::int AS agents_spawned,
|
|
290
289
|
COUNT(*) FILTER (WHERE entity_type = 'task' AND event_type = 'stage_change')::int AS tasks_moved,
|
|
291
|
-
COALESCE(
|
|
290
|
+
COALESCE((
|
|
291
|
+
SELECT SUM(cost_usd)::float FROM v_claude_usage_events
|
|
292
|
+
WHERE created_at >= $1::timestamptz
|
|
293
|
+
), 0)::float AS total_cost,
|
|
292
294
|
COUNT(*) FILTER (WHERE event_type LIKE '%error%' OR (details ? 'error'))::int AS error_count,
|
|
293
295
|
COUNT(*)::int AS total_events,
|
|
294
296
|
COUNT(*) FILTER (WHERE entity_type = 'otel_tool')::int AS tool_calls,
|
|
@@ -422,7 +424,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
422
424
|
WHERE (${includeArchived} OR state IS DISTINCT FROM 'archived')
|
|
423
425
|
`;return rows.map(rowToAgentIdentity)}async function listAgentsForRender(filters){let all=await listAgents(filters);return dedupeShadowRows(all,{keyFor:(a)=>shadowKey(a.customName,a.id,a.team),hasExecutor:(a)=>a.currentExecutorId!=null,startedAt:(a)=>a.startedAt})}async function auditAgentKind(){let rows=await(await getConnection())`
|
|
424
426
|
SELECT id, kind, reports_to FROM agents
|
|
425
|
-
`,drifted=[];for(let row of rows){let expected=row.id.startsWith("dir:")||row.reports_to===null?"permanent":"task";if(row.kind!==expected)drifted.push({id:row.id,kind:row.kind,expected})}for(let drift of drifted)recordAuditEvent("worker",drift.id,"agents.kind.audit_drift","auditor",{stored:drift.kind,expected:drift.expected}).catch(()=>{});return{total:rows.length,drifted}}var DEAD_PANE_ZOMBIE_TTL_HOURS=24;var init_agent_registry=__esm(()=>{init_audit();init_db();init_tmux()});function ts2(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToExecutor(r){return{id:r.id,agentId:r.agent_id,provider:r.provider,transport:r.transport,pid:r.pid,tmuxSession:r.tmux_session,tmuxPaneId:r.tmux_pane_id,tmuxWindow:r.tmux_window,tmuxWindowId:r.tmux_window_id,claudeSessionId:r.claude_session_id,state:r.state,metadata:typeof r.metadata==="string"?JSON.parse(r.metadata):r.metadata??{},worktree:r.worktree,repoPath:r.repo_path,paneColor:r.pane_color,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,createdAt:ts2(r.created_at),updatedAt:ts2(r.updated_at),turnId:r.turn_id??null,outcome:r.outcome??null,closedAt:r.closed_at?ts2(r.closed_at):null,closeReason:r.close_reason??null}}function rowToAssignment(r){return{id:r.id,executorId:r.executor_id,taskId:r.task_id,wishSlug:r.wish_slug,groupNumber:r.group_number,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,outcome:r.outcome,createdAt:ts2(r.created_at)}}var exports_executor_registry={};__export(exports_executor_registry,{updateExecutorState:()=>updateExecutorState,updateClaudeSessionId:()=>updateClaudeSessionId,terminateExecutor:()=>terminateExecutor,terminateActiveExecutor:()=>terminateActiveExecutor,resolveWorkerLivenessByTransport:()=>resolveWorkerLivenessByTransport,relinkExecutorToAgent:()=>relinkExecutorToAgent,recordResumeProviderRejected:()=>recordResumeProviderRejected,recordResumeMissingSession:()=>recordResumeMissingSession,listExecutors:()=>listExecutors,isExecutorAlive:()=>isExecutorAlive,getResumeSessionId:()=>getResumeSessionId,getLiveExecutorState:()=>getLiveExecutorState,getExecutor:()=>getExecutor,getCurrentExecutor:()=>getCurrentExecutor,findLatestByMetadata:()=>findLatestByMetadata,findExecutorBySession:()=>findExecutorBySession,findExecutorByPane:()=>findExecutorByPane,createExecutor:()=>createExecutor,createAndLinkExecutor:()=>createAndLinkExecutor,_resumeJsonlScannerDeps:()=>_resumeJsonlScannerDeps,_resetMissingSessionDedupeForTests:()=>_resetMissingSessionDedupeForTests});import{randomUUID as randomUUID2}from"crypto";import{open,readdir,stat}from"fs/promises";import{homedir as homedir7}from"os";import{join as join13}from"path";async function createExecutor(agentId,provider,transport,opts={}){let sql=await getConnection(),id=opts.id??randomUUID2(),now=new Date().toISOString(),rows=await sql`
|
|
427
|
+
`,drifted=[];for(let row of rows){let expected=row.id.startsWith("dir:")||row.reports_to===null?"permanent":"task";if(row.kind!==expected)drifted.push({id:row.id,kind:row.kind,expected})}for(let drift of drifted)recordAuditEvent("worker",drift.id,"agents.kind.audit_drift","auditor",{stored:drift.kind,expected:drift.expected}).catch(()=>{});return{total:rows.length,drifted}}var DEAD_PANE_ZOMBIE_TTL_HOURS=24;var init_agent_registry=__esm(()=>{init_audit();init_db();init_tmux()});function ts2(v){if(!v)return new Date().toISOString();return v instanceof Date?v.toISOString():v}function rowToExecutor(r){return{id:r.id,agentId:r.agent_id,provider:r.provider,transport:r.transport,pid:r.pid,tmuxSession:r.tmux_session,tmuxPaneId:r.tmux_pane_id,tmuxWindow:r.tmux_window,tmuxWindowId:r.tmux_window_id,claudeSessionId:r.claude_session_id,state:r.state,metadata:typeof r.metadata==="string"?JSON.parse(r.metadata):r.metadata??{},worktree:r.worktree,repoPath:r.repo_path,paneColor:r.pane_color,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,createdAt:ts2(r.created_at),updatedAt:ts2(r.updated_at),turnId:r.turn_id??null,outcome:r.outcome??null,closedAt:r.closed_at?ts2(r.closed_at):null,closeReason:r.close_reason??null}}function rowToAssignment(r){return{id:r.id,executorId:r.executor_id,taskId:r.task_id,wishSlug:r.wish_slug,groupNumber:r.group_number,startedAt:ts2(r.started_at),endedAt:r.ended_at?ts2(r.ended_at):null,outcome:r.outcome,createdAt:ts2(r.created_at)}}var exports_executor_registry={};__export(exports_executor_registry,{updateExecutorState:()=>updateExecutorState,updateClaudeSessionId:()=>updateClaudeSessionId,terminateExecutor:()=>terminateExecutor,terminateActiveExecutor:()=>terminateActiveExecutor,resolveWorkerLivenessByTransport:()=>resolveWorkerLivenessByTransport,relinkExecutorToAgent:()=>relinkExecutorToAgent,recordResumeProviderRejected:()=>recordResumeProviderRejected,recordResumeMissingSession:()=>recordResumeMissingSession,listExecutors:()=>listExecutors,isExecutorAlive:()=>isExecutorAlive,getResumeSessionId:()=>getResumeSessionId,getLiveExecutorState:()=>getLiveExecutorState,getExecutor:()=>getExecutor,getCurrentExecutor:()=>getCurrentExecutor,findLatestByMetadata:()=>findLatestByMetadata,findExecutorBySession:()=>findExecutorBySession,findExecutorByPane:()=>findExecutorByPane,createExecutor:()=>createExecutor,createAndLinkExecutor:()=>createAndLinkExecutor,acquireResumeSessionForAttempt:()=>acquireResumeSessionForAttempt,_resumeJsonlScannerDeps:()=>_resumeJsonlScannerDeps,_resetMissingSessionDedupeForTests:()=>_resetMissingSessionDedupeForTests});import{randomUUID as randomUUID2}from"crypto";import{open,readdir,stat}from"fs/promises";import{homedir as homedir7}from"os";import{join as join13}from"path";async function createExecutor(agentId,provider,transport,opts={}){let sql=await getConnection(),id=opts.id??randomUUID2(),now=new Date().toISOString(),rows=await sql`
|
|
426
428
|
INSERT INTO executors (
|
|
427
429
|
id, agent_id, provider, transport, pid,
|
|
428
430
|
tmux_session, tmux_pane_id, tmux_window, tmux_window_id,
|
|
@@ -477,7 +479,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
477
479
|
ORDER BY started_at DESC
|
|
478
480
|
LIMIT 1
|
|
479
481
|
`;return rows.length>0?rowToExecutor(rows[0]):null}async function relinkExecutorToAgent(executorId,agentId){await(await getConnection())`UPDATE agents SET current_executor_id = ${executorId} WHERE id = ${agentId}`}async function updateClaudeSessionId(executorId,sessionId){await(await getConnection())`UPDATE executors SET claude_session_id = ${sessionId} WHERE id = ${executorId}`}function sanitizeCwdForProjects(p){return p.replace(/[^a-zA-Z0-9]/g,"-")}function resolveClaudeConfigDir(){return process.env.CLAUDE_CONFIG_DIR??join13(homedir7(),".claude")}async function readJsonlIdentity(filePath){let handle=null;try{handle=await open(filePath,"r");let buffer=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer,0,buffer.length,0),head=buffer.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
480
|
-
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:null,agentName=typeof entry.agentName==="string"?entry.agentName:null;if(teamName!==null&&agentName!==null)return{teamName,agentName}}catch{}}}catch{return{teamName:null,agentName:null}}finally{await handle?.close().catch(()=>{})}return{teamName:null,agentName:null}}async function mapWithConcurrency(items,cap,fn){let results=Array(items.length),cursor=0,workers=[],workerCount=Math.min(cap,items.length);for(let w=0;w<workerCount;w++)workers.push((async()=>{while(cursor<items.length){let i2=cursor++;if(i2>=items.length)return;results[i2]=await fn(items[i2])}})());return await Promise.all(workers),results}async function defaultScanForSession(cwd,identity){if(!identity)return null;let projectDir=join13(resolveClaudeConfigDir(),"projects",sanitizeCwdForProjects(cwd)),entries;try{entries=await readdir(projectDir)}catch{return null}let jsonls=entries.filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let sorted=(await mapWithConcurrency(jsonls,STAT_CONCURRENCY_CAP,async(name)=>{let full=join13(projectDir,name);try{let s=await stat(full);return{name,full,mtime:s.mtimeMs}}catch{return null}})).filter((x)=>x!==null).sort((a,b)=>b.mtime-a.mtime);for(let candidate of sorted){let{teamName,agentName}=await readJsonlIdentity(candidate.full);if(teamName!==identity.team||agentName!==identity.customName)continue;return candidate.name.replace(/\.jsonl$/,"")}return null}async function tryJsonlFallback(
|
|
482
|
+
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry=JSON.parse(trimmed),teamName=typeof entry.teamName==="string"?entry.teamName:null,agentName=typeof entry.agentName==="string"?entry.agentName:null;if(teamName!==null&&agentName!==null)return{teamName,agentName}}catch{}}}catch{return{teamName:null,agentName:null}}finally{await handle?.close().catch(()=>{})}return{teamName:null,agentName:null}}async function mapWithConcurrency(items,cap,fn){let results=Array(items.length),cursor=0,workers=[],workerCount=Math.min(cap,items.length);for(let w=0;w<workerCount;w++)workers.push((async()=>{while(cursor<items.length){let i2=cursor++;if(i2>=items.length)return;results[i2]=await fn(items[i2])}})());return await Promise.all(workers),results}async function defaultScanForSession(cwd,identity){if(!identity)return null;let projectDir=join13(resolveClaudeConfigDir(),"projects",sanitizeCwdForProjects(cwd)),entries;try{entries=await readdir(projectDir)}catch{return null}let jsonls=entries.filter((e)=>e.endsWith(".jsonl"));if(jsonls.length===0)return null;let sorted=(await mapWithConcurrency(jsonls,STAT_CONCURRENCY_CAP,async(name)=>{let full=join13(projectDir,name);try{let s=await stat(full);return{name,full,mtime:s.mtimeMs}}catch{return null}})).filter((x)=>x!==null).sort((a,b)=>b.mtime-a.mtime);for(let candidate of sorted){let{teamName,agentName}=await readJsonlIdentity(candidate.full);if(teamName!==identity.team||agentName!==identity.customName)continue;return candidate.name.replace(/\.jsonl$/,"")}return null}async function tryJsonlFallback(row){let cwd=row?.repo_path??null;if(!cwd)return null;let team=row?.team??null,customName=row?.custom_name??null,identity=team&&customName?{team,customName}:null;if(!identity)return null;let recoveredSessionId=await(_resumeJsonlScannerDeps.scanForSession??defaultScanForSession)(cwd,identity);if(!recoveredSessionId)return null;let recoveredFrom=row&&row.executor_id!==null?"null_session":"no_executor";return{sessionId:recoveredSessionId,source:"jsonl",executorId:row?.executor_id??null,cwd,team:identity.team,customName:identity.customName,recoveredFrom}}function _resetMissingSessionDedupeForTests(){}async function recordResumeMissingSession(agentId,actor,details){await recordAuditEvent("agent",agentId,"resume.missing_session",actor,details)}async function lookupResumeSession(agentId){let row=(await(await getConnection())`
|
|
481
483
|
SELECT a.current_executor_id AS executor_id,
|
|
482
484
|
e.claude_session_id,
|
|
483
485
|
a.repo_path,
|
|
@@ -486,7 +488,7 @@ ${bin} set-option -w pane-active-border-style "fg=$COLOR"
|
|
|
486
488
|
FROM agents a
|
|
487
489
|
LEFT JOIN executors e ON e.id = a.current_executor_id
|
|
488
490
|
WHERE a.id = ${agentId}
|
|
489
|
-
|
|
491
|
+
`)[0]??null;if(row&&row.executor_id!==null&&row.claude_session_id)return{sessionId:row.claude_session_id,source:"db",executorId:row.executor_id,cwd:row.repo_path,team:row.team,customName:row.custom_name,recoveredFrom:null};return await tryJsonlFallback(row)}async function getResumeSessionId(agentId){return(await lookupResumeSession(agentId))?.sessionId??null}async function acquireResumeSessionForAttempt(agentId,actor){let lookup=await lookupResumeSession(agentId);if(!lookup)return null;let emitActor=actor??process.env.GENIE_AGENT_NAME??"cli";if(lookup.source==="db")await recordAuditEvent("agent",agentId,"resume.found",emitActor,{executorId:lookup.executorId,sessionId:lookup.sessionId});else await recordAuditEvent("agent",agentId,"resume.recovered_via_jsonl",emitActor,{sessionId:lookup.sessionId,executorId:lookup.executorId,cwd:lookup.cwd,team:lookup.team,customName:lookup.customName,recoveredFrom:lookup.recoveredFrom});return lookup.sessionId}async function recordResumeProviderRejected(agentId,sessionId,reason){await recordAuditEvent("agent",agentId,"resume.provider_rejected",process.env.GENIE_AGENT_NAME??"cli",{sessionId,reason})}async function getLiveExecutorState(agentId){let rows=await(await getConnection())`
|
|
490
492
|
SELECT e.state FROM executors e
|
|
491
493
|
JOIN agents a ON a.current_executor_id = e.id
|
|
492
494
|
WHERE a.id = ${agentId}
|
|
@@ -1219,11 +1221,11 @@ ${context}`}}}catch{return}}var BRAIN_PKG="@khal-os/brain",BRAIN_DIR="node_modul
|
|
|
1219
1221
|
`+` genie task status <slug> \u2014 check progress
|
|
1220
1222
|
`+" genie agent send --to <a> \u2014 communicate directly"},{test:/sleep\s+\d+\s*&&\s*.*(?:capture-pane|tmux\s+list)/,message:`Consider using genie primitives instead of terminal polling:
|
|
1221
1223
|
genie task status <slug>
|
|
1222
|
-
genie events list --since 5m`}]});import{existsSync as existsSync32,readFileSync as readFileSync21}from"fs";import{basename as basename5,join as join38}from"path";function readAgentNameFromSettings(cwd){let localSettings=join38(cwd,".claude","settings.local.json");if(!existsSync32(localSettings))return;try{return JSON.parse(readFileSync21(localSettings,"utf-8")).agentName||void 0}catch{return}}function nameFromCwd(cwd){let name=basename5(cwd);return name&&name!=="/"&&name!=="."?name:void 0}function resolveAgentName(payload){let cwd=payload.cwd;return process.env.GENIE_AGENT_NAME||
|
|
1223
|
-
`)[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)}`;default:return name}}var init_runtime_emit=__esm(()=>{init_resolve_agent_name()});import{existsSync as existsSync33,mkdirSync as mkdirSync17,readFileSync as readFileSync22,renameSync as renameSync6,writeFileSync as writeFileSync14}from"fs";import{homedir as homedir30}from"os";import{join as join39}from"path";function effectiveCacheFile(){let override=globalThis.__GENIE_SESSION_SYNC_CACHE_FILE;return typeof override==="string"&&override.length>0?override:DEFAULT_CACHE_FILE}function loadDiskCache(){if(diskCacheLoaded)return;diskCacheLoaded=!0;let hasOverrides=Object.values(_deps2).some((v)=>v!==null),testCacheOverridden=typeof globalThis.__GENIE_SESSION_SYNC_CACHE_FILE==="string";if((hasOverrides||!1||!1)&&!testCacheOverridden)return;try{let cacheFile=effectiveCacheFile();if(!existsSync33(cacheFile))return;let parsed=JSON.parse(readFileSync22(cacheFile,"utf-8"));for(let[executorId,sessionId]of Object.entries(parsed))if(typeof sessionId==="string"&&sessionId.length>0)syncedSessions.set(executorId,sessionId)}catch{}}function trimCache(){while(syncedSessions.size>MAX_CACHE_ENTRIES){let oldest=syncedSessions.keys().next().value;if(oldest===void 0)break;syncedSessions.delete(oldest)}}function persistDiskCache(){let hasOverrides=Object.values(_deps2).some((v)=>v!==null),testCacheOverridden=typeof globalThis.__GENIE_SESSION_SYNC_CACHE_FILE==="string";if((hasOverrides||!1||!1)&&!testCacheOverridden)return;try{let cacheFile=effectiveCacheFile();mkdirSync17(join39(cacheFile,".."),{recursive:!0}),trimCache();let obj=Object.fromEntries(syncedSessions),tmp=`${cacheFile}.tmp.${process.pid}`;writeFileSync14(tmp,JSON.stringify(obj)),renameSync6(tmp,cacheFile)}catch{}}async function resolveDeps2(){let[agentMod,execMod,audit]=await Promise.all([_deps2.getAgentByName?null:Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),_deps2.getExecutor&&_deps2.updateClaudeSessionId?null:Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),_deps2.emitAuditEvent?null:Promise.resolve().then(() => (init_audit(),exports_audit))]);return{getAgentByName:_deps2.getAgentByName??agentMod.getAgentByName,getExecutor:_deps2.getExecutor??execMod.getExecutor,updateClaudeSessionId:_deps2.updateClaudeSessionId??execMod.updateClaudeSessionId,emitAuditEvent:_deps2.emitAuditEvent??audit.recordAuditEvent}}function shouldSkipSync(payload){let sessionId=payload.session_id;if(!sessionId||typeof sessionId!=="string")return null;let hasOverrides=Object.values(_deps2).some((v)=>v!==null),agentName=process.env.GENIE_AGENT_NAME??payload.teammate_name,teamName=process.env.GENIE_TEAM??payload.team_name;if(!agentName||!teamName)return null;return{sessionId,agentName,teamName}}async function sessionSync(payload){try{let ctx=shouldSkipSync(payload);if(!ctx)return;loadDiskCache();let deps=await resolveDeps2(),executorId=(await deps.getAgentByName(ctx.agentName,ctx.teamName))?.currentExecutorId;if(!executorId)return;if(syncedSessions.get(executorId)===ctx.sessionId)return;let executor=await deps.getExecutor(executorId);if(!executor)return;let oldSessionId=executor.claudeSessionId??null;if(oldSessionId===ctx.sessionId){syncedSessions.set(executorId,ctx.sessionId),persistDiskCache();return}let isTerminal=TERMINAL_EXECUTOR_STATES.has(executor.state??"");if(oldSessionId!==null&&isTerminal){await deps.emitAuditEvent("executor",executorId,"session.divergence_preserved",ctx.agentName,{stored_session_id:oldSessionId,live_session_id:ctx.sessionId,executor_state:executor.state??null,team:ctx.teamName,reason:"terminal_executor_is_recovery_anchor"}),syncedSessions.set(executorId,ctx.sessionId),persistDiskCache();return}await deps.updateClaudeSessionId(executorId,ctx.sessionId),await deps.emitAuditEvent("executor",executorId,"session.reconciled",ctx.agentName,{old_session_id:oldSessionId,new_session_id:ctx.sessionId,team:ctx.teamName}),syncedSessions.set(executorId,ctx.sessionId),persistDiskCache()}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[session-sync] ${msg}`)}return}var syncedSessions,GENIE_HOME3,DEFAULT_CACHE_FILE,diskCacheLoaded=!1,MAX_CACHE_ENTRIES=1000,_deps2,TERMINAL_EXECUTOR_STATES;var init_session_sync=__esm(()=>{syncedSessions=new Map,GENIE_HOME3=process.env.GENIE_HOME??join39(homedir30(),".genie"),DEFAULT_CACHE_FILE=join39(GENIE_HOME3,"cache","session-sync.json");_deps2={getAgentByName:null,getExecutor:null,updateClaudeSessionId:null,emitAuditEvent:null};TERMINAL_EXECUTOR_STATES=new Set(["done","error","terminated"])});function setRegistry(next){registryRef=Object.freeze([...next])}function getRegistry(){return registryRef}function resolveHandlers(event,toolName){return registryRef.filter((h)=>{if(h.event!==event)return!1;if(h.matcher&&toolName&&!h.matcher.test(toolName))return!1;if(h.matcher&&!toolName)return!1;return!0}).sort((a,b2)=>a.priority-b2.priority)}function hookDebug(handlerName,decision,elapsedMs){if(process.env.GENIE_HOOK_DEBUG==="1")console.error(`[hook-debug] ${handlerName} \u2192 ${decision} (${elapsedMs}ms)`)}async function runHandler(handler,payload,currentInput,isBlocking){let handlerPayload={...payload};if(currentInput)handlerPayload.tool_input=currentInput;let start=Date.now(),agentId=
|
|
1224
|
+
genie events list --since 5m`}]});import{existsSync as existsSync32,readFileSync as readFileSync21}from"fs";import{basename as basename5,join as join38}from"path";function readAgentNameFromSettings(cwd){let localSettings=join38(cwd,".claude","settings.local.json");if(!existsSync32(localSettings))return;try{return JSON.parse(readFileSync21(localSettings,"utf-8")).agentName||void 0}catch{return}}function nameFromCwd(cwd){let name=basename5(cwd);return name&&name!=="/"&&name!=="."?name:void 0}function resolveAgentName(payload){let cwd=payload.cwd;return payload.teammate_name||process.env.GENIE_AGENT_NAME||cwd&&readAgentNameFromSettings(cwd)||cwd&&nameFromCwd(cwd)||payload.session_id&&`session-${payload.session_id.slice(0,8)}`||HARNESS_AGENT}function resolveTeamName(payload){return payload.team_name??process.env.GENIE_TEAM??void 0}var HARNESS_AGENT="harness";var init_resolve_agent_name=()=>{};async function emit(subject,event){try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(process.cwd(),subject,event)}catch(error2){let msg=error2 instanceof Error?error2.message:String(error2);console.warn(`[runtime-emit] event log unavailable: ${msg}`)}}async function emitToolCallEvent(payload){let{tool_name:toolName,tool_input:input}=payload;if(!toolName||!input)return;let agent=resolveAgentName(payload);await emit(`genie.tool.${agent}.call`,{timestamp:new Date().toISOString(),kind:"tool_call",agent,team:resolveTeamName(payload),text:summarizeToolCall(toolName,input),data:{toolCall:{name:toolName,input}},source:"hook"});return}async function emitMessageEvent(payload){let input=payload.tool_input;if(!input)return;let msgType=input.type;if(msgType&&msgType!=="message"&&msgType!=="broadcast")return;let to=input.to,content=input.content??input.message;if(!to||!content)return;let agent=resolveAgentName(payload),subject=msgType==="broadcast"?"genie.msg.broadcast":`genie.msg.${to}`;await emit(subject,{timestamp:new Date().toISOString(),kind:"message",agent,team:resolveTeamName(payload),peer:to,direction:"out",text:content,source:"hook"});return}async function emitUserPromptEvent(payload){let prompt2=payload.prompt;if(!prompt2)return;let agent=resolveAgentName(payload);await emit(`genie.user.${agent}.prompt`,{timestamp:new Date().toISOString(),kind:"user",agent,team:resolveTeamName(payload),text:prompt2,source:"hook"});return}async function emitAssistantResponseEvent(payload){let lastMessage=payload.last_assistant_message;if(!lastMessage)return;let agent=resolveAgentName(payload);await emit(`genie.agent.${agent}.response`,{timestamp:new Date().toISOString(),kind:"assistant",agent,team:resolveTeamName(payload),text:lastMessage,source:"hook"});return}function summarizeToolCall(name,input){switch(name){case"Read":case"Edit":case"Write":return`${name} ${input.file_path??""}`;case"Bash":return`$ ${String(input.command??"").split(`
|
|
1225
|
+
`)[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)}`;default:return name}}var init_runtime_emit=__esm(()=>{init_resolve_agent_name()});import{existsSync as existsSync33,mkdirSync as mkdirSync17,readFileSync as readFileSync22,renameSync as renameSync6,writeFileSync as writeFileSync14}from"fs";import{homedir as homedir30}from"os";import{join as join39}from"path";function effectiveCacheFile(){let override=globalThis.__GENIE_SESSION_SYNC_CACHE_FILE;return typeof override==="string"&&override.length>0?override:DEFAULT_CACHE_FILE}function loadDiskCache(){if(diskCacheLoaded)return;diskCacheLoaded=!0;let hasOverrides=Object.values(_deps2).some((v)=>v!==null),testCacheOverridden=typeof globalThis.__GENIE_SESSION_SYNC_CACHE_FILE==="string";if((hasOverrides||!1||!1)&&!testCacheOverridden)return;try{let cacheFile=effectiveCacheFile();if(!existsSync33(cacheFile))return;let parsed=JSON.parse(readFileSync22(cacheFile,"utf-8"));for(let[executorId,sessionId]of Object.entries(parsed))if(typeof sessionId==="string"&&sessionId.length>0)syncedSessions.set(executorId,sessionId)}catch{}}function trimCache(){while(syncedSessions.size>MAX_CACHE_ENTRIES){let oldest=syncedSessions.keys().next().value;if(oldest===void 0)break;syncedSessions.delete(oldest)}}function persistDiskCache(){let hasOverrides=Object.values(_deps2).some((v)=>v!==null),testCacheOverridden=typeof globalThis.__GENIE_SESSION_SYNC_CACHE_FILE==="string";if((hasOverrides||!1||!1)&&!testCacheOverridden)return;try{let cacheFile=effectiveCacheFile();mkdirSync17(join39(cacheFile,".."),{recursive:!0}),trimCache();let obj=Object.fromEntries(syncedSessions),tmp=`${cacheFile}.tmp.${process.pid}`;writeFileSync14(tmp,JSON.stringify(obj)),renameSync6(tmp,cacheFile)}catch{}}async function resolveDeps2(){let[agentMod,execMod,audit]=await Promise.all([_deps2.getAgentByName?null:Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),_deps2.getExecutor&&_deps2.updateClaudeSessionId?null:Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),_deps2.emitAuditEvent?null:Promise.resolve().then(() => (init_audit(),exports_audit))]);return{getAgentByName:_deps2.getAgentByName??agentMod.getAgentByName,getExecutor:_deps2.getExecutor??execMod.getExecutor,updateClaudeSessionId:_deps2.updateClaudeSessionId??execMod.updateClaudeSessionId,emitAuditEvent:_deps2.emitAuditEvent??audit.recordAuditEvent}}function shouldSkipSync(payload){let sessionId=payload.session_id;if(!sessionId||typeof sessionId!=="string")return null;let hasOverrides=Object.values(_deps2).some((v)=>v!==null),agentName=process.env.GENIE_AGENT_NAME??payload.teammate_name,teamName=process.env.GENIE_TEAM??payload.team_name;if(!agentName||!teamName)return null;return{sessionId,agentName,teamName}}async function sessionSync(payload){try{let ctx=shouldSkipSync(payload);if(!ctx)return;loadDiskCache();let deps=await resolveDeps2(),executorId=(await deps.getAgentByName(ctx.agentName,ctx.teamName))?.currentExecutorId;if(!executorId)return;if(syncedSessions.get(executorId)===ctx.sessionId)return;let executor=await deps.getExecutor(executorId);if(!executor)return;let oldSessionId=executor.claudeSessionId??null;if(oldSessionId===ctx.sessionId){syncedSessions.set(executorId,ctx.sessionId),persistDiskCache();return}let isTerminal=TERMINAL_EXECUTOR_STATES.has(executor.state??"");if(oldSessionId!==null&&isTerminal){await deps.emitAuditEvent("executor",executorId,"session.divergence_preserved",ctx.agentName,{stored_session_id:oldSessionId,live_session_id:ctx.sessionId,executor_state:executor.state??null,team:ctx.teamName,reason:"terminal_executor_is_recovery_anchor"}),syncedSessions.set(executorId,ctx.sessionId),persistDiskCache();return}await deps.updateClaudeSessionId(executorId,ctx.sessionId),await deps.emitAuditEvent("executor",executorId,"session.reconciled",ctx.agentName,{old_session_id:oldSessionId,new_session_id:ctx.sessionId,team:ctx.teamName}),syncedSessions.set(executorId,ctx.sessionId),persistDiskCache()}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[session-sync] ${msg}`)}return}var syncedSessions,GENIE_HOME3,DEFAULT_CACHE_FILE,diskCacheLoaded=!1,MAX_CACHE_ENTRIES=1000,_deps2,TERMINAL_EXECUTOR_STATES;var init_session_sync=__esm(()=>{syncedSessions=new Map,GENIE_HOME3=process.env.GENIE_HOME??join39(homedir30(),".genie"),DEFAULT_CACHE_FILE=join39(GENIE_HOME3,"cache","session-sync.json");_deps2={getAgentByName:null,getExecutor:null,updateClaudeSessionId:null,emitAuditEvent:null};TERMINAL_EXECUTOR_STATES=new Set(["done","error","terminated"])});function setRegistry(next){registryRef=Object.freeze([...next])}function getRegistry(){return registryRef}function resolveHandlers(event,toolName){return registryRef.filter((h)=>{if(h.event!==event)return!1;if(h.matcher&&toolName&&!h.matcher.test(toolName))return!1;if(h.matcher&&!toolName)return!1;return!0}).sort((a,b2)=>a.priority-b2.priority)}function hookDebug(handlerName,decision,elapsedMs){if(process.env.GENIE_HOOK_DEBUG==="1")console.error(`[hook-debug] ${handlerName} \u2192 ${decision} (${elapsedMs}ms)`)}async function runHandler(handler,payload,currentInput,isBlocking){let handlerPayload={...payload};if(currentInput)handlerPayload.tool_input=currentInput;let start=Date.now(),agentId=resolveAgentName(handlerPayload),teamId=resolveTeamName(handlerPayload),span=isWideEmitEnabled()?startSpan("hook.delivery",{hook_name:handler.name,agent_id:agentId,tool:payload.tool_name,event:payload.hook_event_name},{source_subsystem:"hooks",ctx:getAmbient()??void 0,agent:agentId,team:teamId}):null;try{let result2=await handler.fn(handlerPayload);if(hookDebug(handler.name,result2?.decision??result2?.hookSpecificOutput?.permissionDecision??"allow",Date.now()-start),span)endSpan(span,{hook_name:handler.name,agent_id:agentId,status:result2?.decision==="deny"?"rejected":"ok"},{source_subsystem:"hooks",agent:agentId,team:teamId});return result2}catch(err){let msg=err instanceof Error?err.message:String(err);if(console.error(`[genie-hook] Handler "${handler.name}" threw: ${msg}`),span)endSpan(span,{hook_name:handler.name,agent_id:agentId,status:"error",stderr_excerpt:msg.slice(0,1024)},{source_subsystem:"hooks",agent:agentId,team:teamId});if(isBlocking)return hookDebug(handler.name,"deny (crash)",Date.now()-start),{decision:"deny",reason:`handler crashed: ${msg}`};hookDebug(handler.name,"allow (crash, non-blocking)",Date.now()-start);return}}function buildDenyResponse(handler,reason,hookEventName){if(hookEventName==="PreToolUse")return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:reason??`Denied by handler: ${handler.name}`}};return{decision:"block",reason:reason??`Denied by handler: ${handler.name}`}}function buildBlockingResponse(hookEventName,contextMessages,currentInput,originalInput){let response={},hasContext=contextMessages.length>0,hasInputChange=currentInput&&originalInput&&JSON.stringify(currentInput)!==JSON.stringify(originalInput);if(hasInputChange)response.updatedInput=currentInput;if(hookEventName==="PreToolUse"&&(hasContext||hasInputChange)){let output={hookEventName:"PreToolUse"};if(hasContext)output.additionalContext=contextMessages.join(`
|
|
1224
1226
|
`);if(hasInputChange)output.permissionDecision="allow",output.updatedInput=currentInput;response.hookSpecificOutput=output}if(hookEventName==="UserPromptSubmit"&&hasContext)response.hookSpecificOutput={hookEventName:"UserPromptSubmit",additionalContext:contextMessages.join(`
|
|
1225
|
-
`)};return response}async function executeBlockingChain(matched,payload){let currentInput=payload.tool_input?{...payload.tool_input}:void 0,contextMessages=[],hookEventName=payload.hook_event_name;for(let handler of matched){let result2=await runHandler(handler,payload,currentInput,!0);if(!result2)continue;if(result2.decision==="deny")return buildDenyResponse(handler,result2.reason,hookEventName);if(result2.hookSpecificOutput?.additionalContext)contextMessages.push(result2.hookSpecificOutput.additionalContext);let inputUpdate=result2.hookSpecificOutput?.updatedInput??result2.updatedInput;if(inputUpdate)currentInput={...currentInput,...inputUpdate}}return buildBlockingResponse(hookEventName,contextMessages,currentInput,payload.tool_input)}async function executeNonBlockingHandlers(matched,payload){await Promise.allSettled(matched.map((h)=>h.fn(payload).catch((err)=>{let msg=err instanceof Error?err.message:String(err);console.error(`[genie-hook] Handler "${h.name}" threw: ${msg}`)})))}async function dispatch(stdin){let payload;try{payload=JSON.parse(stdin)}catch{return console.error("[genie-hook] Invalid JSON on stdin"),""}let event=payload.hook_event_name;if(!event)return console.error("[genie-hook] Missing hook_event_name in payload"),"";let toolName=payload.tool_name,matched=resolveHandlers(event,toolName);if(matched.length===0)return"";if(isBlockingEvent(event)){let result2=await executeBlockingChain(matched,payload);if(Object.keys(result2).length>0)return JSON.stringify(result2);return""}return await executeNonBlockingHandlers(matched,payload),""}var BUILTIN_MANIFEST_PATH="src/hooks/index.ts",builtinHandlers,registryRef;var init_hooks=__esm(()=>{init_emit();init_trace_context();init_audit_context();init_brain_inject();init_branch_guard();init_codex_inbox_deliver();init_freshness();init_orchestration_guard();init_runtime_emit();init_session_sync();init_types2();builtinHandlers=[{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"branch-guard",event:"PreToolUse",matcher:/^Bash$/,priority:1,fn:branchGuard},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"orchestration-guard",event:"PreToolUse",matcher:/^Bash$/,priority:2,fn:orchestrationGuard},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"brain-inject",event:"PreToolUse",matcher:/.*/,priority:5,fn:brainInject},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"freshness",event:"PreToolUse",matcher:/^Read$/,priority:8,fn:freshness},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"audit-context",event:"PreToolUse",matcher:/^(Write|Edit)$/,priority:8,fn:auditContext},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"identity-inject",event:"PreToolUse",matcher:/^SendMessage$/,priority:10,fn:identityInject},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"auto-spawn",event:"PreToolUse",matcher:/^SendMessage$/,priority:20,fn:autoSpawn},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-tool",event:"PreToolUse",matcher:/.*/,priority:30,fn:emitToolCallEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-msg",event:"PostToolUse",matcher:/^SendMessage$/,priority:30,fn:emitMessageEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"codex-inbox-deliver",event:"UserPromptSubmit",priority:25,fn:codexInboxDeliver},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-user-prompt",event:"UserPromptSubmit",priority:30,fn:emitUserPromptEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-assistant-response",event:"Stop",priority:30,fn:emitAssistantResponseEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"session-sync-tool",event:"PreToolUse",matcher:/.*/,priority:35,fn:sessionSync},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"session-sync-prompt",event:"UserPromptSubmit",priority:35,fn:sessionSync}],registryRef=Object.freeze([...builtinHandlers])});var exports_interactivity={};__export(exports_interactivity,{isInteractive:()=>isInteractive,installWorkspaceCheck:()=>installWorkspaceCheck,ensureWorkspace:()=>ensureWorkspace,commandRequiresWorkspace:()=>commandRequiresWorkspace});function isInteractive(){if(!process.stdout.isTTY)return!1;if(process.env.CI)return!1;if(process.argv.includes("--no-interactive"))return!1;return!0}function getRootCommandName(cmd){let current=cmd;while(current.parent?.parent)current=current.parent;return current.name()}function commandRequiresWorkspace(cmd){return!WORKSPACE_EXEMPT.has(getRootCommandName(cmd))}async function ensureWorkspace(){if(findWorkspace())return;if(!isInteractive())console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{confirm}=await Promise.resolve().then(() => (init_esm14(),exports_esm));if(!await confirm({message:"No workspace found. Initialize? [Y/n]",default:!0}))console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{mkdirSync:mkdirSync18,writeFileSync:writeFileSync15}=await import("fs"),{basename:basename6,join:join40}=await import("path"),cwd=process.cwd(),genieDir=join40(cwd,".genie");mkdirSync18(genieDir,{recursive:!0});let config={name:basename6(cwd),agents:{defaults:{}},tmux:{socket:"genie"},sdk:{}};writeFileSync15(join40(genieDir,"workspace.json"),`${JSON.stringify(config,null,2)}
|
|
1226
|
-
`),console.log(`Workspace initialized: ${cwd}`)}function installWorkspaceCheck(program2){program2.hook("preAction",async(_thisCommand,actionCommand)=>{if(!commandRequiresWorkspace(actionCommand))return;await ensureWorkspace()})}var WORKSPACE_EXEMPT;var init_interactivity=__esm(()=>{init_workspace();WORKSPACE_EXEMPT=new Set(["init","setup","doctor","update","uninstall","shortcuts","team","version","help","hook"])});function extractValue(kv){let v=kv.value;if(v.stringValue!==void 0)return v.stringValue;if(v.intValue!==void 0)return typeof v.intValue==="string"?Number.parseInt(v.intValue,10):v.intValue;if(v.doubleValue!==void 0)return v.doubleValue;if(v.boolValue!==void 0)return v.boolValue;return}function attrsToObject(attrs){if(!attrs)return{};let obj={};for(let kv of attrs)obj[kv.key]=extractValue(kv);return obj}function extractResourceContext(resource){let
|
|
1227
|
+
`)};return response}async function executeBlockingChain(matched,payload){let currentInput=payload.tool_input?{...payload.tool_input}:void 0,contextMessages=[],hookEventName=payload.hook_event_name;for(let handler of matched){let result2=await runHandler(handler,payload,currentInput,!0);if(!result2)continue;if(result2.decision==="deny")return buildDenyResponse(handler,result2.reason,hookEventName);if(result2.hookSpecificOutput?.additionalContext)contextMessages.push(result2.hookSpecificOutput.additionalContext);let inputUpdate=result2.hookSpecificOutput?.updatedInput??result2.updatedInput;if(inputUpdate)currentInput={...currentInput,...inputUpdate}}return buildBlockingResponse(hookEventName,contextMessages,currentInput,payload.tool_input)}async function executeNonBlockingHandlers(matched,payload){await Promise.allSettled(matched.map((h)=>h.fn(payload).catch((err)=>{let msg=err instanceof Error?err.message:String(err);console.error(`[genie-hook] Handler "${h.name}" threw: ${msg}`)})))}async function dispatch(stdin){let payload;try{payload=JSON.parse(stdin)}catch{return console.error("[genie-hook] Invalid JSON on stdin"),""}let event=payload.hook_event_name;if(!event)return console.error("[genie-hook] Missing hook_event_name in payload"),"";let toolName=payload.tool_name,matched=resolveHandlers(event,toolName);if(matched.length===0)return"";if(isBlockingEvent(event)){let result2=await executeBlockingChain(matched,payload);if(Object.keys(result2).length>0)return JSON.stringify(result2);return""}return await executeNonBlockingHandlers(matched,payload),""}var BUILTIN_MANIFEST_PATH="src/hooks/index.ts",builtinHandlers,registryRef;var init_hooks=__esm(()=>{init_emit();init_trace_context();init_audit_context();init_brain_inject();init_branch_guard();init_codex_inbox_deliver();init_freshness();init_orchestration_guard();init_runtime_emit();init_session_sync();init_resolve_agent_name();init_types2();builtinHandlers=[{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"branch-guard",event:"PreToolUse",matcher:/^Bash$/,priority:1,fn:branchGuard},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"orchestration-guard",event:"PreToolUse",matcher:/^Bash$/,priority:2,fn:orchestrationGuard},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"brain-inject",event:"PreToolUse",matcher:/.*/,priority:5,fn:brainInject},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"freshness",event:"PreToolUse",matcher:/^Read$/,priority:8,fn:freshness},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"audit-context",event:"PreToolUse",matcher:/^(Write|Edit)$/,priority:8,fn:auditContext},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"identity-inject",event:"PreToolUse",matcher:/^SendMessage$/,priority:10,fn:identityInject},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"auto-spawn",event:"PreToolUse",matcher:/^SendMessage$/,priority:20,fn:autoSpawn},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-tool",event:"PreToolUse",matcher:/.*/,priority:30,fn:emitToolCallEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-msg",event:"PostToolUse",matcher:/^SendMessage$/,priority:30,fn:emitMessageEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"codex-inbox-deliver",event:"UserPromptSubmit",priority:25,fn:codexInboxDeliver},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-user-prompt",event:"UserPromptSubmit",priority:30,fn:emitUserPromptEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"runtime-emit-assistant-response",event:"Stop",priority:30,fn:emitAssistantResponseEvent},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"session-sync-tool",event:"PreToolUse",matcher:/.*/,priority:35,fn:sessionSync},{version:"1",source:"builtin",manifest_path:BUILTIN_MANIFEST_PATH,name:"session-sync-prompt",event:"UserPromptSubmit",priority:35,fn:sessionSync}],registryRef=Object.freeze([...builtinHandlers])});var exports_interactivity={};__export(exports_interactivity,{isInteractive:()=>isInteractive,installWorkspaceCheck:()=>installWorkspaceCheck,ensureWorkspace:()=>ensureWorkspace,commandRequiresWorkspace:()=>commandRequiresWorkspace});function isInteractive(){if(!process.stdout.isTTY)return!1;if(process.env.CI)return!1;if(process.argv.includes("--no-interactive"))return!1;return!0}function getRootCommandName(cmd){let current=cmd;while(current.parent?.parent)current=current.parent;return current.name()}function commandRequiresWorkspace(cmd){return!WORKSPACE_EXEMPT.has(getRootCommandName(cmd))}async function ensureWorkspace(){if(findWorkspace())return;if(!isInteractive())console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{confirm}=await Promise.resolve().then(() => (init_esm14(),exports_esm));if(!await confirm({message:"No workspace found. Initialize? [Y/n]",default:!0}))console.error("No workspace found. Run `genie init` to set up."),process.exit(2);let{mkdirSync:mkdirSync18,writeFileSync:writeFileSync15}=await import("fs"),{basename:basename6,join:join40}=await import("path"),cwd=process.cwd(),genieDir=join40(cwd,".genie");mkdirSync18(genieDir,{recursive:!0});let config={name:basename6(cwd),agents:{defaults:{}},tmux:{socket:"genie"},sdk:{}};writeFileSync15(join40(genieDir,"workspace.json"),`${JSON.stringify(config,null,2)}
|
|
1228
|
+
`),console.log(`Workspace initialized: ${cwd}`)}function installWorkspaceCheck(program2){program2.hook("preAction",async(_thisCommand,actionCommand)=>{if(!commandRequiresWorkspace(actionCommand))return;await ensureWorkspace()})}var WORKSPACE_EXEMPT;var init_interactivity=__esm(()=>{init_workspace();WORKSPACE_EXEMPT=new Set(["init","setup","doctor","update","uninstall","shortcuts","team","version","help","hook"])});function dropSensitiveKeys(obj){let result2={};for(let key of Object.keys(obj)){if(SENSITIVE_OTEL_KEYS.has(key))continue;result2[key]=obj[key]}return result2}function extractValue(kv){let v=kv.value;if(v.stringValue!==void 0)return v.stringValue;if(v.intValue!==void 0)return typeof v.intValue==="string"?Number.parseInt(v.intValue,10):v.intValue;if(v.doubleValue!==void 0)return v.doubleValue;if(v.boolValue!==void 0)return v.boolValue;return}function attrsToObject(attrs){if(!attrs)return{};let obj={};for(let kv of attrs)obj[kv.key]=extractValue(kv);return obj}function extractResourceContext(resource){let raw=attrsToObject(resource?.attributes),filtered={};for(let key of Object.keys(raw)){if(SENSITIVE_OTEL_KEYS.has(key))continue;if(!RESOURCE_ATTR_ALLOWLIST.has(key))continue;filtered[key]=raw[key]}return{agentName:filtered["agent.name"],teamName:filtered["team.name"],wishSlug:filtered["wish.slug"],sessionId:filtered["session.id"],agentRole:filtered["agent.role"]}}function mapEventToEntityType(eventName){if(eventName.includes("tool_result"))return"otel_tool";if(eventName.includes("api_request")||eventName.includes("api_error"))return"otel_api";if(eventName.includes("user_prompt"))return"otel_prompt";if(eventName.includes("tool_decision"))return"otel_decision";return"otel_event"}function parseValidPort(raw){if(!raw)return null;let parsed=Number.parseInt(raw,10);if(!Number.isNaN(parsed)&&parsed>0&&parsed<65536)return parsed;return null}function parseProbeMax(){let parsed=Number.parseInt(process.env.GENIE_OTEL_PORT_PROBE_MAX??"",10);if(!Number.isNaN(parsed)&&parsed>0)return parsed;return DEFAULT_OTEL_PORT_PROBE_MAX}function getConfiguredOtelPort(){let envPort=parseValidPort(process.env.GENIE_OTEL_PORT);if(envPort!==null)return{port:envPort,explicit:!0};return{port:getAuxiliaryPortBase()+1,explicit:!1}}function getCandidatePorts(startPort,explicit){if(explicit)return[startPort];let probeCount=Math.min(parseProbeMax(),Math.max(1,65535-startPort+1));return Array.from({length:probeCount},(_,index)=>startPort+index)}function formatPortList(ports){if(ports.length===1)return String(ports[0]);return`${ports[0]}-${ports[ports.length-1]}`}function isPortBusyError(err){if(err&&typeof err==="object"&&"code"in err&&err.code==="EADDRINUSE")return!0;let message=err instanceof Error?err.message:String(err);return message.includes("EADDRINUSE")||message.includes("address already in use")||/Failed to start server\. Is port \d+ in use\?/i.test(message)||/\bis in use\b/i.test(message)}function resolveEntityId(ctx){return ctx.sessionId??(ctx.agentName?`agent:${ctx.agentName}`:"unknown")}function mergeContext(details,ctx){if(ctx.teamName)details.team=ctx.teamName;if(ctx.wishSlug)details.wish_slug=ctx.wishSlug;if(ctx.agentRole)details.agent_role=ctx.agentRole;if(ctx.sessionId)details.session_id=ctx.sessionId}function logRecordToRow(record,ctx){let logAttrs=dropSensitiveKeys(attrsToObject(record.attributes)),eventName=logAttrs["event.name"]??record.body?.stringValue??"unknown",details={...logAttrs,event_name:eventName};if(mergeContext(details,ctx),record.severityText)details.severity=record.severityText;if(record.body?.kvlistValue?.values)Object.assign(details,dropSensitiveKeys(attrsToObject(record.body.kvlistValue.values)));return{entity_type:mapEventToEntityType(eventName),entity_id:resolveEntityId(ctx),event_type:eventName,actor:ctx.agentName??null,details}}function processLogs(payload){let rows=[];for(let resourceLog of payload.resourceLogs??[]){let ctx=extractResourceContext(resourceLog.resource);for(let scopeLog of resourceLog.scopeLogs??[])for(let record of scopeLog.logRecords??[])rows.push(logRecordToRow(record,ctx))}return rows}function buildMetricRow(metricName,entityId,actor,details){return{entity_type:"otel_metric",entity_id:entityId,event_type:metricName,actor,details}}function processSumGaugePoints(dataPoints,metricName,unit,entityId,ctx){return dataPoints.map((dp)=>{let dpAttrs=dropSensitiveKeys(attrsToObject(dp.attributes)),value=dp.asDouble??(dp.asInt!==void 0?Number(dp.asInt):void 0),details={metric_name:metricName,value,...dpAttrs};if(unit)details.unit=unit;return mergeContext(details,ctx),buildMetricRow(metricName,entityId,ctx.agentName??null,details)})}function metricToRows(metric,ctx){let metricName=metric.name??"unknown_metric",entityId=resolveEntityId(ctx),dataPoints=metric.sum?.dataPoints??metric.gauge?.dataPoints??[],rows=processSumGaugePoints(dataPoints,metricName,metric.unit,entityId,ctx);for(let dp of metric.histogram?.dataPoints??[]){let dpAttrs=dropSensitiveKeys(attrsToObject(dp.attributes)),details={metric_name:metricName,sum:dp.sum,count:dp.count!==void 0?Number(dp.count):void 0,...dpAttrs};if(metric.unit)details.unit=metric.unit;mergeContext(details,ctx),rows.push(buildMetricRow(metricName,entityId,ctx.agentName??null,details))}return rows}function processMetrics(payload){let rows=[];for(let resourceMetric of payload.resourceMetrics??[]){let ctx=extractResourceContext(resourceMetric.resource);for(let scopeMetric of resourceMetric.scopeMetrics??[])for(let metric of scopeMetric.metrics??[])rows.push(...metricToRows(metric,ctx))}return rows}async function flushToPg(rows){if(rows.length===0)return;try{let{getConnection:getConnection2,isAvailable:isAvailable2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!await isAvailable2())return;let sql=await getConnection2();await sql`
|
|
1227
1229
|
INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
|
|
1228
1230
|
SELECT * FROM unnest(
|
|
1229
1231
|
${sql.array(rows.map((r)=>r.entity_type))}::text[],
|
|
@@ -1232,7 +1234,7 @@ ${context}`}}}catch{return}}var BRAIN_PKG="@khal-os/brain",BRAIN_DIR="node_modul
|
|
|
1232
1234
|
${sql.array(rows.map((r)=>r.actor??""))}::text[],
|
|
1233
1235
|
${sql.array(rows.map((r)=>JSON.stringify(r.details)))}::jsonb[]
|
|
1234
1236
|
)
|
|
1235
|
-
`}catch{}}function getOtelPort(){return boundOtelPort??getConfiguredOtelPort().port}function serveOtelReceiver(port){return Bun.serve({port,hostname:"127.0.0.1",fetch:async(req)=>{let url=new URL(req.url);if(req.method==="POST"&&url.pathname==="/v1/logs"){try{let payload=await req.json(),rows=processLogs(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="POST"&&url.pathname==="/v1/metrics"){try{let payload=await req.json(),rows=processMetrics(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="GET"&&url.pathname==="/health")return Response.json({status:"ok",port});return new Response("Not Found",{status:404})}})}async function startOtelReceiver(){if(server)return!0;let{port:startPort,explicit}=getConfiguredOtelPort(),candidatePorts=getCandidatePorts(startPort,explicit);for(let port of candidatePorts)try{return server=serveOtelReceiver(port),boundOtelPort=server.port??port,!0}catch(err){if(isPortBusyError(err))continue;let message=err instanceof Error?err.message:String(err);return console.warn(`OTel receiver: failed to start on port ${port}: ${message}`),!1}if(explicit)console.warn(`OTel receiver: port ${startPort} already in use - skipping (another instance may be running)`);else console.warn(`OTel receiver: all probed ports busy (${formatPortList(candidatePorts)}) - skipping`);return!1}async function stopOtelReceiver(){let activeServer=server;if(server=null,boundOtelPort=null,activeServer)await activeServer.stop(!0)}var server=null,boundOtelPort=null,DEFAULT_OTEL_PORT_PROBE_MAX=8;var init_otel_receiver=__esm(()=>{init_db()});function debug(msg){if(process.env.DEBUG)console.error(`[target-resolver] ${msg}`)}async function defaultTmuxLookup(sessionName,windowName){try{let tmux=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),session=await tmux.findSessionByName(sessionName);if(!session)return null;let windows=await tmux.listWindows(session.id);if(!windows||windows.length===0)return null;let targetWindow;if(windowName){if(targetWindow=windows.find((w)=>w.name===windowName),!targetWindow)return null}else targetWindow=windows.find((w)=>w.active)||windows[0];let panes=await tmux.listPanes(targetWindow.id);if(!panes||panes.length===0)return null;return{paneId:(panes.find((p)=>p.active)||panes[0]).id,session:sessionName}}catch{return null}}async function defaultIsPaneLive(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{pane_id}'`)).trim()===paneId}catch{return!1}}async function defaultCleanupDeadPane(workerId,paneId){try{await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).removeSubPane(workerId,paneId)}catch{}}async function defaultDeriveSession(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{session_name}'`)).trim()||null}catch{return null}}async function assertLive(paneId,isPaneLive,errorMsg,cleanup){if(!await isPaneLive(paneId)){if(cleanup)await cleanup();throw Error(errorMsg)}}async function resolveRawPane(target,opts){if(opts.checkLiveness)await assertLive(target,opts.isPaneLive,`Pane ${target} is dead or does not exist. Check with: tmux list-panes -a`);let session=await opts.deriveSession(target);return{paneId:target,session:session??void 0,resolvedVia:"raw"}}async function resolveWindowId(target,workers,opts){let matchingWorker=Object.values(workers).find((w)=>w.windowId===target);if(!matchingWorker)throw Error(`Window "${target}" not found in worker registry.
|
|
1237
|
+
`}catch{}}function getOtelPort(){return boundOtelPort??getConfiguredOtelPort().port}function serveOtelReceiver(port){return Bun.serve({port,hostname:"127.0.0.1",fetch:async(req)=>{let url=new URL(req.url);if(req.method==="POST"&&url.pathname==="/v1/logs"){try{let payload=await req.json(),rows=processLogs(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="POST"&&url.pathname==="/v1/metrics"){try{let payload=await req.json(),rows=processMetrics(payload);flushToPg(rows).catch(()=>{})}catch{}return new Response("",{status:200})}if(req.method==="GET"&&url.pathname==="/health")return Response.json({status:"ok",port});return new Response("Not Found",{status:404})}})}async function startOtelReceiver(){if(server)return!0;let{port:startPort,explicit}=getConfiguredOtelPort(),candidatePorts=getCandidatePorts(startPort,explicit);for(let port of candidatePorts)try{return server=serveOtelReceiver(port),boundOtelPort=server.port??port,!0}catch(err){if(isPortBusyError(err))continue;let message=err instanceof Error?err.message:String(err);return console.warn(`OTel receiver: failed to start on port ${port}: ${message}`),!1}if(explicit)console.warn(`OTel receiver: port ${startPort} already in use - skipping (another instance may be running)`);else console.warn(`OTel receiver: all probed ports busy (${formatPortList(candidatePorts)}) - skipping`);return!1}async function stopOtelReceiver(){let activeServer=server;if(server=null,boundOtelPort=null,activeServer)await activeServer.stop(!0)}var server=null,boundOtelPort=null,DEFAULT_OTEL_PORT_PROBE_MAX=8,SENSITIVE_OTEL_KEYS,RESOURCE_ATTR_ALLOWLIST;var init_otel_receiver=__esm(()=>{init_db();SENSITIVE_OTEL_KEYS=new Set(["user.email","user.id","user.account_id","user.account_uuid","organization.id"]),RESOURCE_ATTR_ALLOWLIST=new Set(["agent.name","agent.role","team.name","wish.slug","session.id","service.name","service.version","service.namespace","host.arch","os.type"])});function debug(msg){if(process.env.DEBUG)console.error(`[target-resolver] ${msg}`)}async function defaultTmuxLookup(sessionName,windowName){try{let tmux=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),session=await tmux.findSessionByName(sessionName);if(!session)return null;let windows=await tmux.listWindows(session.id);if(!windows||windows.length===0)return null;let targetWindow;if(windowName){if(targetWindow=windows.find((w)=>w.name===windowName),!targetWindow)return null}else targetWindow=windows.find((w)=>w.active)||windows[0];let panes=await tmux.listPanes(targetWindow.id);if(!panes||panes.length===0)return null;return{paneId:(panes.find((p)=>p.active)||panes[0]).id,session:sessionName}}catch{return null}}async function defaultIsPaneLive(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{pane_id}'`)).trim()===paneId}catch{return!1}}async function defaultCleanupDeadPane(workerId,paneId){try{await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).removeSubPane(workerId,paneId)}catch{}}async function defaultDeriveSession(paneId){try{return(await(await Promise.resolve().then(() => (init_tmux(),exports_tmux))).executeTmux(`display-message -p -t '${paneId}' '#{session_name}'`)).trim()||null}catch{return null}}async function assertLive(paneId,isPaneLive,errorMsg,cleanup){if(!await isPaneLive(paneId)){if(cleanup)await cleanup();throw Error(errorMsg)}}async function resolveRawPane(target,opts){if(opts.checkLiveness)await assertLive(target,opts.isPaneLive,`Pane ${target} is dead or does not exist. Check with: tmux list-panes -a`);let session=await opts.deriveSession(target);return{paneId:target,session:session??void 0,resolvedVia:"raw"}}async function resolveWindowId(target,workers,opts){let matchingWorker=Object.values(workers).find((w)=>w.windowId===target);if(!matchingWorker)throw Error(`Window "${target}" not found in worker registry.
|
|
1236
1238
|
Run 'genie agent list' to list agents.`);if(opts.checkLiveness)await assertLive(matchingWorker.paneId,opts.isPaneLive,`Window ${target}: worker ${matchingWorker.id} pane ${matchingWorker.paneId} is dead. Run 'genie agent kill${matchingWorker.id}' to clean up.`);return{paneId:matchingWorker.paneId,session:matchingWorker.session,workerId:matchingWorker.id,resolvedVia:"worker"}}function resolveWorkerSubPane(worker,leftSide,rightSide){let index=Number.parseInt(rightSide,10);if(Number.isNaN(index)||index<0)throw Error(`Invalid sub-pane index "${rightSide}" for worker "${leftSide}". Use a non-negative integer (0 = primary, 1+ = sub-panes).`);let paneId=getPaneByIndex(worker,index);if(!paneId){let maxIndex=worker.subPanes?worker.subPanes.length:0;throw Error(`Worker "${leftSide}" has no sub-pane index ${index}. Available: 0 (primary)${maxIndex>0?`, 1-${maxIndex} (sub-panes)`:""}. Sub-pane index ${index} does not exist.`)}return paneId}function pickUnique(target,candidates,label){if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 ${label}: ${ids}
|
|
1237
1239
|
Use the full ID instead.`)}function resolveByRole(target,workers,currentTeam){if(!currentTeam)return null;let candidates=Object.entries(workers).filter(([,w])=>w.role===target&&w.team===currentTeam);return pickUnique(target,candidates,`${candidates.length} workers with role "${target}" in team "${currentTeam}"`)}function resolveByCustomName(target,workers,currentTeam){if(currentTeam){let teamCandidates=Object.entries(workers).filter(([,w])=>w.customName===target&&w.team===currentTeam),teamHit=pickUnique(target,teamCandidates,`${teamCandidates.length} workers with customName "${target}" in team "${currentTeam}"`);if(teamHit)return teamHit}let allCandidates=Object.entries(workers).filter(([,w])=>w.customName===target);return pickUnique(target,allCandidates,`${allCandidates.length} workers with customName "${target}"`)}function resolveByPartialId(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&id.endsWith(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
|
|
1238
1240
|
Use the full ID instead.`)}function resolveBySubstring(target,workers,currentTeam){let candidates=Object.entries(workers).filter(([id])=>id!==target&&!id.endsWith(target)&&id.includes(target));if(candidates.length===0)return null;if(candidates.length===1){let[id,w]=candidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}if(currentTeam){let teamCandidates=candidates.filter(([,w])=>w.team===currentTeam);if(teamCandidates.length===1){let[id,w]=teamCandidates[0];return{paneId:w.paneId,session:w.session,workerId:id,resolvedVia:"worker"}}}let ids=candidates.map(([id])=>id).join(", ");throw Error(`Ambiguous target "${target}" \u2014 matches ${candidates.length} workers: ${ids}
|
|
@@ -1610,7 +1612,7 @@ Synced: ${total} agent(s), ${result2.archived.length} removed.`)}async function
|
|
|
1610
1612
|
) AS latest_assignment_outcome
|
|
1611
1613
|
FROM agents a
|
|
1612
1614
|
WHERE a.id = ${agentId}
|
|
1613
|
-
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync37}from"fs";import{join as join43}from"path";function getSystemPromptFile(workingDir){let agentsPath=join43(workingDir,"AGENTS.md");if(existsSync37(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null,sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,isCliSender:()=>isCliSender,handleBroadcast:()=>handleBroadcast,fanoutBroadcastToNativeInboxes:()=>fanoutBroadcastToNativeInboxes,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir31}from"os";import{join as join44}from"path";async function getRegistry2(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}function isCliSender(sender){return sender==="cli"||sender.startsWith("cli:")}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry2(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join44(homedir31(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join44(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(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)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,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 handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1615
|
+
`)[0]??null}function isPermanent(row){return row.kind==="permanent"}async function shouldResume(agentId){let row=await readAgentResumeRow(agentId);if(!row)return{resume:!1,reason:"unknown_agent",rehydrate:"lazy"};let rehydrate=isPermanent(row)?"eager":"lazy";if(row.auto_resume===!1){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"auto_resume_disabled",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}if(row.latest_assignment_outcome!==null){let sessionId2=await getResumeSessionId(agentId).catch(()=>null),result2={resume:!1,reason:"assignment_closed",rehydrate};if(sessionId2)result2.sessionId=sessionId2;return result2}let sessionId=await getResumeSessionId(agentId).catch(()=>null);if(!sessionId)return{resume:!1,reason:"no_session_id",rehydrate};return{resume:!0,reason:"ok",sessionId,rehydrate}}function classifyBootPass(agentId,decision){if(!decision.resume)return{agentId,decision,action:"skip"};if(decision.rehydrate==="eager")return{agentId,decision,action:"eager_invoke"};return{agentId,decision,action:"lazy_surface"}}function bootPassEventType(action,decision){if(action==="eager_invoke")return"agent.boot_pass.eager_invoked";if(action==="lazy_surface")return"agent.boot_pass.lazy_pending";if(decision.reason==="assignment_closed")return"agent.boot_pass.skipped_task_done";return"agent.boot_pass.rehydrated"}async function bootPassDecisions(agentIds){let cap=Math.min(BOOT_PASS_CONCURRENCY_CAP,Math.max(1,agentIds.length)),results=Array(agentIds.length),cursor=0,workers=Array.from({length:cap},async()=>{while(cursor<agentIds.length){let i2=cursor++;if(i2>=agentIds.length)return;let agentId=agentIds[i2];try{let decision=await shouldResume(agentId);results[i2]=classifyBootPass(agentId,decision)}catch{let decision={resume:!1,reason:"no_session_id",rehydrate:"lazy"};results[i2]=classifyBootPass(agentId,decision)}}});return await Promise.all(workers),results}async function emitBootPassEvent(decision,actor=process.env.GENIE_AGENT_NAME??"scheduler"){let eventType=bootPassEventType(decision.action,decision.decision),details={action:decision.action,reason:decision.decision.reason,rehydrate:decision.decision.rehydrate};if(decision.decision.sessionId)details.sessionId=decision.decision.sessionId;await recordAuditEvent("agent",decision.agentId,eventType,actor,details)}var BOOT_PASS_CONCURRENCY_CAP=32;var init_should_resume=__esm(()=>{init_audit();init_db();init_executor_registry()});var exports_team_auto_spawn={};__export(exports_team_auto_spawn,{isTeamActive:()=>isTeamActive,isAgentAlive:()=>isAgentAlive,ensureTeamLead:()=>ensureTeamLead});import{randomUUID as randomUUID6}from"crypto";import{existsSync as existsSync37}from"fs";import{join as join43}from"path";function getSystemPromptFile(workingDir){let agentsPath=join43(workingDir,"AGENTS.md");if(existsSync37(agentsPath))return agentsPath;return null}async function ensureSession2(teamName){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),teamConfig=await getTeam2(teamName);if(teamConfig?.tmuxSessionName){if(await findSessionByName(teamConfig.tmuxSessionName))return teamConfig.tmuxSessionName}if(!teamConfig){let current=await getCurrentSessionName();if(current)return current}let sessionName=teamConfig?.tmuxSessionName??sanitizeTeamName(teamName);try{await createSession(sessionName)}catch(error2){if(!(error2 instanceof Error?error2.message:String(error2)).includes("duplicate session"))throw error2}return sessionName}async function isTeamActive(teamName){if(!await loadConfig(teamName))return!1;let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),sessionName=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(!await findSessionByName(sessionName))return!1;try{let windows=await listWindows(sessionName),sanitized=sanitizeTeamName(teamName);return windows.some((w)=>w.name===sanitized||w.name===teamName)}catch{return!1}}async function isAgentAlive(agentName){try{let{list:list2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),match=(await list2()).find((a)=>a.id===agentName||a.role===agentName);if(!match?.paneId)return!1;return resolveWorkerLivenessByTransport(match)}catch{return!1}}async function ensureTeamLead(teamName,workingDir){let{getTeam:getTeam2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),targetSession=(await getTeam2(teamName))?.tmuxSessionName??await getCurrentSessionName()??sanitizeTeamName(teamName);if(await isTeamActive(teamName))return{created:!1,session:targetSession,window:sanitizeWindowName(teamName)};let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName),sanitized=sanitizeTeamName(teamName),leaderAgent=await findOrCreateAgent(leaderName,sanitized,leaderName),priorSessionId=(await shouldResume(leaderAgent.id).catch(()=>null))?.sessionId??null;if(priorSessionId!==null)priorSessionId=await acquireResumeSessionForAttempt(leaderAgent.id).catch(()=>null)??priorSessionId;let sessionId=priorSessionId??randomUUID6(),resumeLeader=priorSessionId!==null;await ensureNativeTeamWithSessionId(teamName,`Genie team: ${teamName}`,sessionId,leaderName),await registerNativeMember(teamName,{agentName:leaderName,agentType:"general-purpose",color:"blue",cwd:workingDir});let session=await ensureSession2(teamName),windowName=sanitizeWindowName(teamName),teamWindow=await ensureTeamWindow(session,windowName,workingDir);if(teamWindow.created){let systemPromptFile=getSystemPromptFile(workingDir),target=`${session}:${windowName}`,cdCmd=`cd ${shellQuote(workingDir)}`;await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cdCmd)} Enter`);let cmd=buildTeamLeadCommand(teamName,{systemPromptFile:systemPromptFile??void 0,leaderName,sessionId,resume:resumeLeader});await executeTmux2(`send-keys -t ${shellQuote(target)} ${shellQuote(cmd)} Enter`),await recordTeamLeadExecutor({agentId:leaderAgent.id,session,windowName,windowId:teamWindow.windowId,paneId:teamWindow.paneId,sessionId,workingDir}).catch(()=>{})}return{created:teamWindow.created,session,window:windowName}}async function recordTeamLeadExecutor(opts){await terminateActiveExecutor(opts.agentId);let pid=null;try{let target=`${opts.session}:${opts.windowName}`,pidStr=(await executeTmux2(`display -t ${shellQuote(target)} -p '#{pane_pid}'`)).trim(),parsed=Number.parseInt(pidStr,10);if(parsed>0)pid=parsed}catch{}await createAndLinkExecutor(opts.agentId,"claude","tmux",{pid,tmuxSession:opts.session,tmuxPaneId:opts.paneId,tmuxWindow:opts.windowName,tmuxWindowId:opts.windowId??null,claudeSessionId:opts.sessionId,state:"spawning",repoPath:opts.workingDir})}var init_team_auto_spawn=__esm(()=>{init_session();init_agent_registry();init_claude_native_teams();init_executor_registry();init_should_resume();init_team_lead_command();init_tmux()});var exports_inbox_watcher={};__export(exports_inbox_watcher,{stopInboxWatcher:()=>stopInboxWatcher,startInboxWatcher:()=>startInboxWatcher,resetSpawnFailures:()=>resetSpawnFailures,resetNoWorkingDirWarned:()=>resetNoWorkingDirWarned,getInboxPollIntervalMs:()=>getInboxPollIntervalMs,checkInboxes:()=>checkInboxes});function getInboxPollIntervalMs(){let env=process.env.GENIE_INBOX_POLL_MS;if(env!==void 0){if(env==="")return INBOX_POLL_INTERVAL_MS;let parsed=Number(env);if(!Number.isNaN(parsed)&&parsed>=0)return parsed}return INBOX_POLL_INTERVAL_MS}function resetSpawnFailures(){spawnFailures.clear()}function resetNoWorkingDirWarned(){noWorkingDirWarned.clear()}function resolveSessionKeyFromMessage(teamName,firstUnreadText){if(!firstUnreadText)return teamName;let header=parseRoutingHeader(firstUnreadText);return header?resolveSessionKey(teamName,header):teamName}function shouldWarnMissingWorkingDir(teamName){let now=Date.now(),lastWarned=noWorkingDirWarned.get(teamName)??0;if(now-lastWarned<NO_WORKING_DIR_RECHECK_MS)return!1;return noWorkingDirWarned.set(teamName,now),!0}async function attemptSpawn(deps,teamName,workingDir,sessionKey2,currentFailures){try{return await deps.ensureTeamLead(teamName,workingDir),spawnFailures.set(sessionKey2,0),!0}catch(err){let newCount=currentFailures+1;spawnFailures.set(sessionKey2,newCount);let message=err instanceof Error?err.message:String(err);if(deps.warn(`[inbox-watcher] Failed to spawn team-lead for "${teamName}" (attempt ${newCount}/${MAX_SPAWN_FAILURES}): ${message}`),newCount===MAX_SPAWN_FAILURES)deps.emitDeadInbox({team_name:teamName,session_key:sessionKey2,failure_count:newCount,last_error_message:message.length>2048?`${message.slice(0,2045)}...`:message});return!1}}async function checkInboxes(deps=defaultDeps2){if(getInboxPollIntervalMs()===0)return[];let teamsWithUnread=await deps.listTeamsWithUnreadInbox(),spawned=[];for(let{teamName,workingDir,firstUnreadText}of teamsWithUnread){let sessionKey2=resolveSessionKeyFromMessage(teamName,firstUnreadText),failures=spawnFailures.get(sessionKey2)??0;if(failures>=MAX_SPAWN_FAILURES){deps.warn(`[inbox-watcher] Skipping "${sessionKey2}" \u2014 ${failures} consecutive spawn failures`);continue}if(await deps.isTeamActive(teamName))continue;if(!workingDir){if(shouldWarnMissingWorkingDir(teamName))deps.warn(`[inbox-watcher] Cannot spawn team-lead for "${teamName}" \u2014 no workingDir in config`);continue}if(noWorkingDirWarned.delete(teamName),await attemptSpawn(deps,teamName,workingDir,sessionKey2,failures))spawned.push(teamName)}return spawned}function startInboxWatcher(deps=defaultDeps2){return setInterval(()=>{checkInboxes(deps).catch((err)=>{let message=err instanceof Error?err.message:String(err);deps.warn(`[inbox-watcher] Poll error: ${message}`)})},getInboxPollIntervalMs())}function stopInboxWatcher(handle){clearInterval(handle)}var defaultDeps2,INBOX_POLL_INTERVAL_MS=30000,MAX_SPAWN_FAILURES=3,NO_WORKING_DIR_RECHECK_MS=3600000,spawnFailures,noWorkingDirWarned;var init_inbox_watcher=__esm(()=>{init_claude_native_teams();init_emit();init_routing_header();init_team_auto_spawn();defaultDeps2={listTeamsWithUnreadInbox,isTeamActive:(teamName)=>isTeamActive(teamName),isAgentAlive:(agentName)=>isAgentAlive(agentName),ensureTeamLead:(teamName,workingDir)=>ensureTeamLead(teamName,workingDir),warn:(msg)=>console.warn(msg),emitDeadInbox:(payload)=>{try{emitEvent("rot.inbox-watcher-spawn-loop.detected",payload)}catch{}}};spawnFailures=new Map,noWorkingDirWarned=new Map});var exports_msg={};__export(exports_msg,{suggestRelayLeader:()=>suggestRelayLeader,resolveSenderTeams:()=>resolveSenderTeams,registerSendInboxCommands:()=>registerSendInboxCommands,printBridgeSuggestion:()=>printBridgeSuggestion,isCliSender:()=>isCliSender,handleBroadcast:()=>handleBroadcast,fanoutBroadcastToNativeInboxes:()=>fanoutBroadcastToNativeInboxes,detectSenderIdentity:()=>detectSenderIdentity,checkSendScope:()=>checkSendScope});import{readFile as readFile7}from"fs/promises";import{homedir as homedir31}from"os";import{join as join44}from"path";async function getRegistry2(){if(!_registry)_registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return _registry}async function getTaskService(){if(!_taskService)_taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService}async function getTeamManager(){if(!_teamManager)_teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return _teamManager}async function getMailbox(){if(!_mailbox)_mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox));return _mailbox}function isCliSender(sender){return sender==="cli"||sender.startsWith("cli:")}async function detectSenderIdentity(teamName){let envName=process.env.GENIE_AGENT_NAME;if(envName)return envName;let paneId=process.env.TMUX_PANE;if(!paneId)return"cli";let registry=await getRegistry2(),worker=typeof registry.findByPane==="function"?await registry.findByPane(paneId):null;if(worker)return worker.role??worker.id;let resolvedTeam=teamName??process.env.GENIE_TEAM;if(resolvedTeam){let memberName=await findMemberByPane(resolvedTeam,paneId);if(memberName)return memberName}return"cli"}async function findMemberByPane(teamName,paneId){let configDir=process.env.CLAUDE_CONFIG_DIR??join44(homedir31(),".claude"),sanitized=teamName.replace(/[^a-zA-Z0-9]/g,"-").toLowerCase(),cfgPath=join44(configDir,"teams",sanitized,"config.json");try{let raw=await readFile7(cfgPath,"utf-8");return(JSON.parse(raw).members??[]).find((m)=>m.tmuxPaneId===paneId)?.name??null}catch{return null}}async function resolveLeaderAlias(recipient,teamContext){if(recipient!=="team-lead")return recipient;let teamName=teamContext??process.env.GENIE_TEAM;if(teamName)return(await getTeamManager()).resolveLeaderName(teamName);return recipient}async function checkSendScope(_repoPath,sender,recipient){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),senderTeams=resolveSenderTeams(teams,sender);if(senderTeams.length===0)return null;for(let team of senderTeams)if(isRecipientInTeam(team,recipient))return null;let reachableChildren=resolveReachableChildren(teams,senderTeams);for(let child of reachableChildren){if(recipient===child.name)return null;if(isRecipientInTeam(child,recipient))return null}let teamNames=senderTeams.map((t)=>t.name).join(", ");return`Scope violation: "${recipient}" is not in sender's team(s): ${teamNames}`}function childReachbackAllowed(child,parent){if(parent.allowChildReachback?.some((prefix)=>child.name.startsWith(prefix)))return!0;return DEFAULT_REACHBACK_PREFIXES.some((prefix)=>child.name.startsWith(prefix))}function walkParentChain(teams,start,visited,out){let current=start,depth=0;while(current.parentTeam&&depth<PARENT_CHAIN_MAX_DEPTH){if(visited.has(current.parentTeam))return;let parent=teams.find((t)=>t.name===current.parentTeam);if(!parent)return;if(!childReachbackAllowed(current,parent))return;out.push(parent),visited.add(parent.name),current=parent,depth++}}function walkChildTeams(teams,parent,visited,out,depth){if(depth>=PARENT_CHAIN_MAX_DEPTH)return;for(let child of teams){if(visited.has(child.name))continue;if(child.parentTeam!==parent.name)continue;if(!childReachbackAllowed(child,parent))continue;out.push(child),visited.add(child.name),walkChildTeams(teams,child,visited,out,depth+1)}}function resolveReachableChildren(teams,senderTeams){let visited=new Set(senderTeams.map((t)=>t.name)),result2=[];for(let team of senderTeams)walkChildTeams(teams,team,visited,result2,0);return result2}function resolveSenderTeams(teams,sender){let direct=teams.filter((t)=>t.members.includes(sender)),visited=new Set(direct.map((t)=>t.name)),result2=[...direct];for(let team of direct)walkParentChain(teams,team,visited,result2);if(teams.some((t)=>t.leader===sender)||sender==="team-lead"){let envTeam=process.env.GENIE_TEAM;if(envTeam){let leaderTeam=teams.find((t)=>t.name===envTeam);if(leaderTeam&&!visited.has(leaderTeam.name))result2.push(leaderTeam),visited.add(leaderTeam.name),walkParentChain(teams,leaderTeam,visited,result2)}}return result2}async function suggestRelayLeader(sender){if(isCliSender(sender))return null;let teams=await(await getTeamManager()).listTeams(),reachable=resolveSenderTeams(teams,sender);if(reachable.length===0)return null;let target=reachable[0];return{leader:target.leader??target.name,team:target.name}}function isRecipientInTeam(team,recipient){if(team.members.includes(recipient)||recipient===team.leader||recipient==="team-lead")return!0;if(recipient.startsWith(`${team.name}-`)){let roleOnly=recipient.slice(team.name.length+1);if(team.members.includes(roleOnly))return!0}return!1}async function findAgentTeam(_repoPath,agentName){let teams=await(await getTeamManager()).listTeams(),memberTeam=teams.find((t)=>t.members.includes(agentName));if(memberTeam)return memberTeam;if(agentName==="team-lead"||teams.some((t)=>t.leader===agentName)){let envTeam=process.env.GENIE_TEAM;if(envTeam)return teams.find((t)=>t.name===envTeam)??null;let leaderTeam=teams.find((t)=>t.leader===agentName);if(leaderTeam)return leaderTeam}return null}function localActor(name){return{actorType:"local",actorId:name}}async function resolveTeamName2(explicit,repoPath,from){if(explicit)return explicit;let name=(await findAgentTeam(repoPath,from))?.name??process.env.GENIE_TEAM;if(!name)console.error("Error: Could not auto-detect team. Use --team <name>."),process.exit(1);return name}async function handleInbox(agent,options){let ts3=await getTaskService(),resolvedAgent=agent??await detectSenderIdentity(),actor=localActor(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)await printConversationSummary(ts3,conv)}async function printConversationSummary(ts3,conv){let messages2=await ts3.getMessages(conv.id,{limit:1}),lastMsg=messages2.length>0?messages2[messages2.length-1]:null,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 handleChatThread(messageId,options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),parentMsgId=Number(messageId),parentMsg=await ts3.getMessage(parentMsgId);if(!parentMsg)console.error(`Error: Message not found: ${messageId}`),process.exit(1);let conv=await ts3.findOrCreateConversation({type:"group",name:options.name??`Thread on message #${parentMsgId}`,parentMessageId:parentMsgId,createdBy:actor,members:[actor]});console.log(`Thread created: ${conv.id}`),console.log(` Parent message: #${parentMsgId} in ${parentMsg.conversationId}`),console.log(` Name: ${conv.name??"(unnamed)"}`)}function printConversationTable(conversations){console.log(` ${padRight("ID",20)} ${padRight("NAME",25)} ${padRight("TYPE",8)} ${padRight("LINKED",20)} UPDATED`),console.log(` ${"\u2500".repeat(80)}`);for(let c of conversations){let name=truncate2(c.name??"(unnamed)",23),linked=c.linkedEntity?`${c.linkedEntity}:${c.linkedEntityId}`:"-",updated=formatTime(c.updatedAt);console.log(` ${padRight(c.id,20)} ${padRight(name,25)} ${padRight(c.type,8)} ${padRight(linked,20)} ${updated}`)}console.log(`
|
|
1614
1616
|
${conversations.length} conversation${conversations.length===1?"":"s"}`)}async function handleChatList(options){let ts3=await getTaskService(),from=options.from??await detectSenderIdentity(),actor=localActor(from),conversations=await ts3.listConversations(actor);if(options.type)conversations=conversations.filter((c)=>c.type===options.type);if(options.linked)conversations=conversations.filter((c)=>c.linkedEntity===options.linked);if(options.json){console.log(JSON.stringify(conversations,null,2));return}if(conversations.length===0){console.log("No conversations found.");return}printConversationTable(conversations)}async function handleChatRead(conversationId,options){let ts3=await getTaskService(),conv=await ts3.getConversation(conversationId);if(!conv)console.error(`Error: Conversation not found: ${conversationId}`),process.exit(1);let messages2=await ts3.getMessages(conversationId,{since:options.since,limit:Number(options.limit)||50});if(options.json){console.log(JSON.stringify(messages2,null,2));return}let name=conv.name??conversationId;if(messages2.length===0){console.log(`No messages in "${name}".`);return}console.log(""),console.log(`CHAT: ${name}`),console.log("\u2500".repeat(60));for(let msg of messages2){let time=formatTime(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}console.log("")}async function discoverCurrentTeam(nativeTeams,from,explicitTeam){if(explicitTeam)return explicitTeam;let discovered=await nativeTeams.discoverTeamName().catch(()=>null);if(discovered)return discovered;return(await(await getRegistry2()).list()).find((w)=>w.role===from||w.id===from||w.customName===from)?.team??null}async function deliverToTeam(nativeTeams,team,recipient,msg){let nativeName=await nativeTeams.resolveNativeMemberName(team,recipient);if(!nativeName)return!1;return await nativeTeams.writeNativeInbox(team,nativeName,msg),!0}async function bridgeToNativeInbox(from,recipient,body,explicitTeam){if(from===recipient)return!1;let nativeTeams=await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1},currentTeam=await discoverCurrentTeam(nativeTeams,from,explicitTeam);if(currentTeam&&await deliverToTeam(nativeTeams,currentTeam,recipient,nativeMsg))return!0;let allTeams=await nativeTeams.listTeams().catch(()=>[]);for(let team of allTeams){if(team===currentTeam)continue;if(await deliverToTeam(nativeTeams,team,recipient,nativeMsg))return!0}return console.warn(`[genie send] Native inbox bridge: could not find native team member for "${recipient}"`),!1}function quoteForShell(value){return`'${value.replace(/'/g,"'\\''")}'`}async function printBridgeSuggestion(sender,recipient,body,scopeError){let suggestion=await suggestRelayLeader(sender);if(console.error(`Scope violation: ${scopeError}`),!suggestion){console.error("No reachable leader found \u2014 sender is not bound to any team.");return}if(suggestion.leader===sender){console.error(`You are already the nearest reachable leader (${suggestion.leader}@${suggestion.team}) \u2014 no external relay path available.`);return}console.error(`Nearest reachable leader: ${suggestion.leader}@${suggestion.team}`),console.error("Relay manually via:"),console.error(` genie send ${quoteForShell(`[relay to ${recipient}] ${body}`)} --to ${suggestion.leader} --team ${suggestion.team}`)}function broadcastAgentName(agent){return agent.customName??agent.role??agent.id}function mergeBroadcastRosterEntry(bySanitized,sanitized,patch){if(!sanitized)return;let existing=bySanitized.get(sanitized);bySanitized.set(sanitized,{agent:existing?.agent??patch.agent,sanitized,inboxName:patch.inboxName??existing?.inboxName??sanitized,agentId:existing?.agentId??patch.agentId,nativeColor:existing?.nativeColor??patch.nativeColor,nativeActive:existing?.nativeActive??patch.nativeActive})}async function resolveBroadcastRoster(teamName,deps){let[agents,nativeConfig]=await Promise.all([deps.listAgents({team:teamName}).catch(()=>[]),deps.loadConfig(teamName).catch(()=>null)]),bySanitized=new Map;for(let agent of agents){let name=broadcastAgentName(agent),sanitized=deps.sanitizeTeamName(name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:name,inboxName:sanitized,agentId:agent.id,nativeColor:agent.nativeColor})}for(let member of nativeConfig?.members??[]){let sanitized=deps.sanitizeTeamName(member.name);mergeBroadcastRosterEntry(bySanitized,sanitized,{agent:member.name,inboxName:member.name,nativeColor:member.color,nativeActive:member.isActive})}return[...bySanitized.values()]}function makeBroadcastNativeMessage(from,body,timestamp2,color2){return{from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:timestamp2,color:color2,read:!1}}async function broadcastSkipReason(entry2,deps){if(entry2.nativeActive===!1)return"offline";if(!entry2.agentId)return null;let state=await deps.getAgentEffectiveState(entry2.agentId).catch(()=>"offline");if(state==="offline")return"offline";if(state==="terminated")return"terminated";if(state==="error")return"dead";if(state==="done")return"done";return null}async function fanoutBroadcastToNativeInboxes(teamName,from,body,deps){let sender=deps.sanitizeTeamName(from),roster=await resolveBroadcastRoster(teamName,deps),timestamp2=(deps.now??(()=>new Date))().toISOString(),recipients=[];for(let entry2 of roster){if(entry2.sanitized===sender)continue;let skipReason=await broadcastSkipReason(entry2,deps);if(skipReason){recipients.push({agent:entry2.agent,delivered:!1,reason:skipReason});continue}try{await deps.writeNativeInbox(teamName,entry2.inboxName,makeBroadcastNativeMessage(from,body,timestamp2,entry2.nativeColor??"blue")),recipients.push({agent:entry2.agent,delivered:!0})}catch(err){recipients.push({agent:entry2.agent,delivered:!1,reason:err instanceof Error?err.message:String(err)})}}return recipients}async function handleBroadcast(body,options,deps={}){let ts3=deps.taskService??await getTaskService(),registry=deps.registry??await getRegistry2(),nativeTeams=deps.nativeTeams??await Promise.resolve().then(() => (init_claude_native_teams(),exports_claude_native_teams)),repoPath=deps.repoPath??process.cwd(),from=options.from??await detectSenderIdentity(options.team),teamName=await resolveTeamName2(options.team,repoPath,from),senderActor=localActor(from),conv=await ts3.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await ts3.addMember(conv.id,senderActor);let msg=await ts3.sendMessage(conv.id,senderActor,body),recipients=await fanoutBroadcastToNativeInboxes(teamName,from,body,{listAgents:registry.listAgents,getAgentEffectiveState:registry.getAgentEffectiveState,loadConfig:nativeTeams.loadConfig,sanitizeTeamName:nativeTeams.sanitizeTeamName,writeNativeInbox:nativeTeams.writeNativeInbox,now:deps.now});try{await(deps.publishSubjectEvent??(await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events))).publishSubjectEvent)(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,team:teamName,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName,recipients},source:"mailbox"})}catch{}let deliveredCount=recipients.filter((r)=>r.delivered).length;console.log(`Broadcast sent to team "${teamName}" (delivered to ${deliveredCount} of ${recipients.length} members).`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`);for(let recipient of recipients)if(!recipient.delivered)console.log(` ${recipient.agent}: ${recipient.reason??"delivery failed"}`);return{messageId:msg.id,conversationId:conv.id,team:teamName,recipients}}async function handleSend(body,options){let ts3=await getTaskService(),mailbox=await getMailbox(),repoPath=process.cwd(),from=options.from??await detectSenderIdentity(options.team),to=await resolveLeaderAlias(options.to,options.team),scopeError=await checkSendScope(repoPath,from,to);if(scopeError){if(options.bridge){await printBridgeSuggestion(from,to,body,scopeError);return}console.error(`Error: ${scopeError}`),process.exit(1)}let senderActor=localActor(from),recipientActor=localActor(to),conv=await ts3.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await ts3.addMember(conv.id,senderActor),await ts3.addMember(conv.id,recipientActor);let mailboxMessage=await mailbox.send(repoPath,from,to,body),msg=await ts3.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,`genie.msg.${to}`,{kind:"message",agent:from,direction:"out",peer:to,text:body,data:{messageId:msg.id,conversationId:conv.id,from,to},source:"mailbox"})}catch{}if(await bridgeToNativeInbox(from,to,body,options.team).catch((err)=>{let reason=err instanceof Error?err.message:String(err);return console.warn(`[genie send] Native inbox bridge failed: ${reason}`),!1}))await mailbox.markDelivered(repoPath,to,mailboxMessage.id).catch(()=>{});console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}function registerSendInboxCommands(program2){program2.command("send <body>").description("Send a direct message to an agent (PG-backed)").option("--to <agent>","Recipient agent name (default: team leader)","team-lead").option("--from <sender>","Sender ID (auto-detected from context)").option("--team <name>","Explicit team context for sender/recipient resolution").option("--bridge","On scope violation, print an advisory + relay command instead of failing (exit 0)").addHelpText("after",`
|
|
1615
1617
|
Examples:
|
|
1616
1618
|
genie send 'start task #3' --to engineer # Message a specific agent
|
|
@@ -1780,7 +1782,7 @@ ${otherList}
|
|
|
1780
1782
|
${groupSection}`:"","",gitLog?`Last git log:
|
|
1781
1783
|
${gitLog}`:"","",gitStatus?`Uncommitted changes:
|
|
1782
1784
|
${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
|
|
1783
|
-
`);await _deps3.mailboxSend(repoPath,"genie",workerId,resumePrompt)}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Resume context injection failed: ${msg}`)}}var execAsync,_deps3;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_audit();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_provider_adapters();init_registry2();init_should_resume();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2),_deps3={findAnyGroupByAssignee,mailboxSend:send,writeNativeInbox}});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}async function waitForExecutorReady(executorId,opts){let timeoutMs=opts?.timeoutMs??DEFAULT_SPAWN_TIMEOUT_MS,start=Date.now();if(!await _pgDeps.isAvailable())return{ready:!1,elapsedMs:0};try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))return{ready:!0,elapsedMs:Date.now()-start}}catch{return{ready:!1,elapsedMs:Date.now()-start}}let sql;try{sql=await _pgDeps.getConnection()}catch{return{ready:!1,elapsedMs:Date.now()-start}}return new Promise((resolve7)=>{let resolved=!1,listener=null,pollInterval=null,timeout=null,cleanup=async()=>{if(pollInterval)clearInterval(pollInterval);if(timeout)clearTimeout(timeout);if(listener)try{await listener.unlisten()}catch{}},finish=(ready)=>{if(resolved)return;resolved=!0,cleanup().then(()=>resolve7({ready,elapsedMs:Date.now()-start}))};timeout=setTimeout(()=>finish(!1),timeoutMs),sql.listen("genie_executor_state",(payload)=>{let parts=payload.split(":");if(parts.length<4)return;let[notifyExecId,,,newState]=parts;if(notifyExecId===executorId&&(newState==="running"||newState==="idle"))finish(!0)}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollInterval=setInterval(async()=>{if(resolved)return;try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))finish(!0)}catch{}},READINESS_POLL_INTERVAL_MS)})}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000,_pgDeps;var init_spawn_command=__esm(()=>{init_db();init_executor_registry();init_orchestrator();init_tmux();_pgDeps={isAvailable,getConnection,getExecutor}});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,resolveResumeSessionId:()=>resolveResumeSessionId,getInbox:()=>getInbox,deliverToPane:()=>deliverToPane,_deps:()=>_deps4,MissingResumeSessionError:()=>MissingResumeSessionError});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){try{let executor=await findExecutorByPane(paneId);if(executor&&executor.state!=="terminated"&&executor.state!=="error"){if((await waitForExecutorReady(executor.id,{timeoutMs})).ready)return!0}}catch{}let start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isExecutorCompleted(worker){if(!worker?.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);return executor!=null&&(executor.state==="done"||executor.state==="terminated")}async function isExecutorResumable(worker){if(!worker.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);if(!executor)return!1;return!["done","error","terminated"].includes(executor.state)}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await _deps4.isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}function dedupShadowsForSend(workers){let groups=new Map;for(let w of workers){let key=`${w.customName??w.id}\x00${w.team??""}`,arr=groups.get(key);if(arr)arr.push(w);else groups.set(key,[w])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b2)=>{let aExec=a.currentExecutorId!=null?1:0,bExec=b2.currentExecutorId!=null?1:0;if(aExec!==bExec)return bExec-aExec;return(b2.startedAt??"").localeCompare(a.startedAt??"")}),out.push(arr[0])}return out}async function findLiveWorkerFuzzy(recipientId){let matches=dedupShadowsForSend(await resolveRecipient(recipientId));return matches.length===1?matches[0]:null}async function findSpawnTemplate(worker,recipientId){let templates=await listTemplates(),candidates=[worker?.role,worker?.id,recipientId].filter((v)=>Boolean(v)),uniqueCandidates=[...new Set(candidates)],workerTeam=worker?.team;return templates.find((t)=>{if(workerTeam&&t.team!==workerTeam)return!1;return uniqueCandidates.some((q)=>t.id===q||t.role===q||`${t.team}:${t.role}`===q)})??null}async function lockedSpawnWorker(recipientId,worker,template,resumeSessionId){let sql=await getConnection(),workerTeam=worker?.team,lockResult=await sql.begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${recipientId}))`;let postLockLive=await findLiveWorkerFuzzy(recipientId);if(postLockLive)return{type:"existing",worker:postLockLive};if(await cleanupDeadWorkers(recipientId,workerTeam),worker)await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn));return{type:"spawned",...await spawnWorkerFromTemplate2(template,resumeSessionId)}});if(lockResult.type==="existing")return{worker:lockResult.worker,respawned:!1};if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await(_deps4.waitForWorkerReady??waitForWorkerReady)(lockResult.paneId),!await _deps4.isPaneAlive(lockResult.paneId))return await unregister(lockResult.worker.id),null;return{worker:lockResult.worker,respawned:!0}}async function resolveResumeSessionId(worker,template,recipientId){if(template.provider!=="claude")return;let agentIdToProbe=worker?.id??`dir:${recipientId}`,decision=await shouldResume(agentIdToProbe);if(worker&&await isExecutorResumable(worker)){if(!decision.sessionId)throw new MissingResumeSessionError(worker.id,recipientId)}return decision.sessionId}async function handleSpawnError(err,worker,recipientId){if(err instanceof MissingResumeSessionError)throw err;let msg=err instanceof Error?err.message:String(err);if(console.error(`[protocol-router] Spawn failed for "${recipientId}": ${msg}`),worker)await update(worker.id,{state:"error"}).catch(()=>{});return null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await _deps4.isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(await isExecutorCompleted(worker))return null;if(!process.env.TMUX)return null;let template=await findSpawnTemplate(worker,recipientId);if(!template)return null;let resumeSessionId=await resolveResumeSessionId(worker,template,recipientId);try{return await lockedSpawnWorker(recipientId,worker,template,resumeSessionId)}catch(err){return handleSpawnError(err,worker,recipientId)}}async function cleanupDeadWorkers(recipientId,team){let allWorkers=await list();for(let w of allWorkers){if(team&&w.team!==team)continue;if(!(w.role===recipientId||w.id===recipientId))continue;if(await _deps4.isPaneAlive(w.paneId))continue;await unregister(w.id)}}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);else console.error(`[protocol-router] Delivery failed: all paths exhausted (worker=${worker.id}, pane=${worker.paneId}, msg="${body.slice(0,50)}")`);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to;try{let message=await send(repoPath,from,to,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,to,message.id),{messageId:message.id,workerId:to,delivered:!0}}catch{return null}}async function deliverAfterPaneRecheck(repoPath,from,worker,body,paneDeadReason){if(!await _deps4.isPaneAlive(worker.paneId)){let message=await send(repoPath,from,worker.id,body);return console.error(`[protocol-router] Delivery failed: ${paneDeadReason} (worker=${worker.id}, msg="${body.slice(0,50)}")`),{messageId:message.id,workerId:worker.id,delivered:!1,reason:paneDeadReason}}return deliverToWorker(repoPath,from,worker,body)}async function findKnownWorker(to){let worker=await get(to);if(worker)return worker;let allWorkers=await list();return allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}async function attemptAutoSpawnDelivery(repoPath,from,to,body,worker){let alive;try{alive=await ensureWorkerAlive(worker,to)}catch(err){if(err instanceof MissingResumeSessionError)return console.error(`[protocol-router] ${err.message}`),{messageId:"",workerId:worker?.id??to,delivered:!1,reason:err.message};throw err}if(!alive)return null;return deliverAfterPaneRecheck(repoPath,from,alive.worker,body,"pane dead after spawn")}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let liveMatches=await resolveRecipient(to);if(liveMatches.length===1)return deliverAfterPaneRecheck(repoPath,from,liveMatches[0],body,"Pane died before delivery");if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve7}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve7(to),worker=await findKnownWorker(to);if(dirResolved||worker){let result2=await attemptAutoSpawnDelivery(repoPath,from,to,body,worker);if(result2)return result2}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;if(!await _deps4.isPaneAlive(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve7)=>setTimeout(resolve7,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function deliverToPane(toWorker,messageId){let worker=await get(toWorker);if(!worker||!worker.paneId)return await markFailed(messageId),!1;if(!await _deps4.isPaneAlive(worker.paneId))return await markFailed(messageId),!1;let message=await getById(messageId);if(!message||message.deliveredAt)return!1;let injected=await injectToTmuxPane(worker,message);if(injected&&worker.repoPath)await markDelivered(worker.repoPath,worker.id,messageId);else await markFailed(messageId);return injected}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var MissingResumeSessionError,_deps4,AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_orchestrator();init_should_resume();init_spawn_command();init_tmux();MissingResumeSessionError=class MissingResumeSessionError extends Error{workerId;entityId;recipientId;reason;constructor(workerId,recipientId,reason="null_session"){let suffix=recipientId?` (recipient "${recipientId}")`:"";super(`Cannot resume worker "${workerId}"${suffix}: executor has no claude_session_id recorded (reason: ${reason}). This usually means the worker predates the session-sync hook. Run \`genie reset ${workerId}\` or re-spawn the worker to recover.`);this.name="MissingResumeSessionError",this.workerId=workerId,this.entityId=workerId,this.recipientId=recipientId,this.reason=reason}};_deps4={isPaneAlive,waitForWorkerReady:null}});var exports_sdk_session_capture={};__export(exports_sdk_session_capture,{updateTurnCount:()=>updateTurnCount,startSession:()=>startSession,recordTurn:()=>recordTurn,endSession:()=>endSession});async function startSession(safePgCall,executorId,claudeSessionId,agentId,team,role,wishSlug){let sessionId=claudeSessionId??`sdk-${executorId}-${Date.now()}`;if(!await safePgCall("sdk-session-start",(sql)=>sql`INSERT INTO sessions (id, agent_id, executor_id, team, role, wish_slug, status, jsonl_path, project_path)
|
|
1785
|
+
`);await _deps3.mailboxSend(repoPath,"genie",workerId,resumePrompt)}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Resume context injection failed: ${msg}`)}}var execAsync,_deps3;var init_protocol_router_spawn=__esm(()=>{init_agent_registry();init_audit();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_provider_adapters();init_registry2();init_should_resume();init_team_manager();init_tmux_wrapper();init_tmux();init_wish_state();execAsync=promisify2(exec2),_deps3={findAnyGroupByAssignee,mailboxSend:send,writeNativeInbox}});async function waitForAgentReady(paneId,opts){let timeoutMs=opts?.timeoutMs??(process.env.GENIE_SPAWN_TIMEOUT_MS?Number(process.env.GENIE_SPAWN_TIMEOUT_MS):DEFAULT_SPAWN_TIMEOUT_MS),pollIntervalMs=opts?.pollIntervalMs??READINESS_POLL_INTERVAL_MS,start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,50);if(content){let state=detectState(content);if(state.type==="idle"||state.type==="tool_use")return{ready:!0,elapsedMs:Date.now()-start}}}catch{}await new Promise((r)=>setTimeout(r,pollIntervalMs))}return{ready:!1,elapsedMs:Date.now()-start}}async function waitForExecutorReady(executorId,opts){let timeoutMs=opts?.timeoutMs??DEFAULT_SPAWN_TIMEOUT_MS,start=Date.now();if(!await _pgDeps.isAvailable())return{ready:!1,elapsedMs:0};try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))return{ready:!0,elapsedMs:Date.now()-start}}catch{return{ready:!1,elapsedMs:Date.now()-start}}let sql;try{sql=await _pgDeps.getConnection()}catch{return{ready:!1,elapsedMs:Date.now()-start}}return new Promise((resolve7)=>{let resolved=!1,listener=null,pollInterval=null,timeout=null,cleanup=async()=>{if(pollInterval)clearInterval(pollInterval);if(timeout)clearTimeout(timeout);if(listener)try{await listener.unlisten()}catch{}},finish=(ready)=>{if(resolved)return;resolved=!0,cleanup().then(()=>resolve7({ready,elapsedMs:Date.now()-start}))};timeout=setTimeout(()=>finish(!1),timeoutMs),sql.listen("genie_executor_state",(payload)=>{let parts=payload.split(":");if(parts.length<4)return;let[notifyExecId,,,newState]=parts;if(notifyExecId===executorId&&(newState==="running"||newState==="idle"))finish(!0)}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollInterval=setInterval(async()=>{if(resolved)return;try{let executor=await _pgDeps.getExecutor(executorId);if(executor&&(executor.state==="running"||executor.state==="idle"))finish(!0)}catch{}},READINESS_POLL_INTERVAL_MS)})}var DEFAULT_SPAWN_TIMEOUT_MS=30000,READINESS_POLL_INTERVAL_MS=2000,_pgDeps;var init_spawn_command=__esm(()=>{init_db();init_executor_registry();init_orchestrator();init_tmux();_pgDeps={isAvailable,getConnection,getExecutor}});var exports_protocol_router={};__export(exports_protocol_router,{sendMessage:()=>sendMessage2,resolveResumeSessionId:()=>resolveResumeSessionId,getInbox:()=>getInbox,deliverToPane:()=>deliverToPane,_deps:()=>_deps4,MissingResumeSessionError:()=>MissingResumeSessionError});async function waitForWorkerReady(paneId,timeoutMs=AUTO_SPAWN_READY_TIMEOUT_MS){try{let executor=await findExecutorByPane(paneId);if(executor&&executor.state!=="terminated"&&executor.state!=="error"){if((await waitForExecutorReady(executor.id,{timeoutMs})).ready)return!0}}catch{}let start=Date.now();while(Date.now()-start<timeoutMs){try{let content=await capturePaneContent(paneId,30);if(detectState(content).type==="idle")return!0}catch{}await new Promise((r)=>setTimeout(r,AUTO_SPAWN_POLL_INTERVAL_MS))}return!1}async function isExecutorCompleted(worker){if(!worker?.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);return executor!=null&&(executor.state==="done"||executor.state==="terminated")}async function isExecutorResumable(worker){if(!worker.currentExecutorId)return!1;let executor=await getCurrentExecutor(worker.id);if(!executor)return!1;return!["done","error","terminated"].includes(executor.state)}async function isWorkerDead(w){if(w.currentExecutorId){let state=await getAgentEffectiveState(w.id);return state==="terminated"||state==="offline"}return w.state==="suspended"}async function resolveRecipient(recipientId){let allWorkers=await list(),byId=[],byRole=[],byTeamRole=[];for(let w of allWorkers){if(await isWorkerDead(w))continue;if(!await _deps4.isPaneAlive(w.paneId))continue;if(w.id===recipientId)byId.push(w);else if(w.role===recipientId)byRole.push(w);else if(`${w.team}:${w.role}`===recipientId)byTeamRole.push(w)}if(byId.length>0)return byId;if(byRole.length>0)return byRole;return byTeamRole}function dedupShadowsForSend(workers){let groups=new Map;for(let w of workers){let key=`${w.customName??w.id}\x00${w.team??""}`,arr=groups.get(key);if(arr)arr.push(w);else groups.set(key,[w])}let out=[];for(let arr of groups.values()){if(arr.length===1){out.push(arr[0]);continue}arr.sort((a,b2)=>{let aExec=a.currentExecutorId!=null?1:0,bExec=b2.currentExecutorId!=null?1:0;if(aExec!==bExec)return bExec-aExec;return(b2.startedAt??"").localeCompare(a.startedAt??"")}),out.push(arr[0])}return out}async function findLiveWorkerFuzzy(recipientId){let matches=dedupShadowsForSend(await resolveRecipient(recipientId));return matches.length===1?matches[0]:null}async function findSpawnTemplate(worker,recipientId){let templates=await listTemplates(),candidates=[worker?.role,worker?.id,recipientId].filter((v)=>Boolean(v)),uniqueCandidates=[...new Set(candidates)],workerTeam=worker?.team;return templates.find((t)=>{if(workerTeam&&t.team!==workerTeam)return!1;return uniqueCandidates.some((q)=>t.id===q||t.role===q||`${t.team}:${t.role}`===q)})??null}async function lockedSpawnWorker(recipientId,worker,template,resumeSessionId){let sql=await getConnection(),workerTeam=worker?.team,lockResult=await sql.begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${recipientId}))`;let postLockLive=await findLiveWorkerFuzzy(recipientId);if(postLockLive)return{type:"existing",worker:postLockLive};if(await cleanupDeadWorkers(recipientId,workerTeam),worker)await unregister(worker.id);let{spawnWorkerFromTemplate:spawnWorkerFromTemplate2}=await Promise.resolve().then(() => (init_protocol_router_spawn(),exports_protocol_router_spawn));return{type:"spawned",...await spawnWorkerFromTemplate2(template,resumeSessionId)}});if(lockResult.type==="existing")return{worker:lockResult.worker,respawned:!1};if(await saveTemplate({...template,lastSpawnedAt:new Date().toISOString()}),await(_deps4.waitForWorkerReady??waitForWorkerReady)(lockResult.paneId),!await _deps4.isPaneAlive(lockResult.paneId))return await unregister(lockResult.worker.id),null;return{worker:lockResult.worker,respawned:!0}}async function resolveResumeSessionId(worker,template,recipientId){if(template.provider!=="claude")return;let agentIdToProbe=worker?.id??`dir:${recipientId}`,decision=await shouldResume(agentIdToProbe);if(worker&&await isExecutorResumable(worker)){if(!decision.sessionId)throw new MissingResumeSessionError(worker.id,recipientId)}if(!decision.sessionId)return;let{acquireResumeSessionForAttempt:acquireResumeSessionForAttempt2}=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry));return await acquireResumeSessionForAttempt2(agentIdToProbe).catch(()=>null)??decision.sessionId}async function handleSpawnError(err,worker,recipientId){if(err instanceof MissingResumeSessionError)throw err;let msg=err instanceof Error?err.message:String(err);if(console.error(`[protocol-router] Spawn failed for "${recipientId}": ${msg}`),worker)await update(worker.id,{state:"error"}).catch(()=>{});return null}async function ensureWorkerAlive(worker,recipientId){if(worker&&worker.state!=="suspended"&&await _deps4.isPaneAlive(worker.paneId))return{worker,respawned:!1};let live=await findLiveWorkerFuzzy(recipientId);if(live)return{worker:live,respawned:!1};if(await isExecutorCompleted(worker))return null;if(!process.env.TMUX)return null;let template=await findSpawnTemplate(worker,recipientId);if(!template)return null;let resumeSessionId=await resolveResumeSessionId(worker,template,recipientId);try{return await lockedSpawnWorker(recipientId,worker,template,resumeSessionId)}catch(err){return handleSpawnError(err,worker,recipientId)}}async function cleanupDeadWorkers(recipientId,team){let allWorkers=await list();for(let w of allWorkers){if(team&&w.team!==team)continue;if(!(w.role===recipientId||w.id===recipientId))continue;if(await _deps4.isPaneAlive(w.paneId))continue;await unregister(w.id)}}async function deliverToWorker(repoPath,from,worker,body){let message=await send(repoPath,from,worker.id,body),delivered=!1;if(worker.nativeTeamEnabled&&worker.team&&worker.role)delivered=await writeToNativeInbox(worker,message);else delivered=await injectToTmuxPane(worker,message);if(!delivered&&worker.team){let agentName=worker.role||worker.id.split("-").slice(-1)[0]||worker.id;try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue");await writeNativeInbox(worker.team,agentName,nativeMsg),delivered=!0}catch{}}if(delivered)await markDelivered(repoPath,worker.id,message.id);else console.error(`[protocol-router] Delivery failed: all paths exhausted (worker=${worker.id}, pane=${worker.paneId}, msg="${body.slice(0,50)}")`);return{messageId:message.id,workerId:worker.id,delivered}}async function deliverViaNativeInbox(repoPath,from,to,body,teamName){let resolvedTeam=teamName??await discoverTeamName();if(!resolvedTeam)return null;let config=await loadConfig(resolvedTeam).catch(()=>null);if(!config)return null;let sanitizedTo=sanitizeTeamName(to),matchedMember=config.members?.find((m)=>m.name===to||m.name===sanitizedTo||m.agentId===`${to}@${resolvedTeam}`||m.agentId===`${sanitizedTo}@${resolvedTeam}`);if(!matchedMember)return null;let inboxName=matchedMember.name??to;try{let message=await send(repoPath,from,to,body),nativeMsg={from,text:body,summary:body.length>50?`${body.substring(0,50)}...`:body,timestamp:new Date().toISOString(),color:"blue",read:!1};return await writeNativeInbox(resolvedTeam,inboxName,nativeMsg),await markDelivered(repoPath,to,message.id),{messageId:message.id,workerId:to,delivered:!0}}catch{return null}}async function deliverAfterPaneRecheck(repoPath,from,worker,body,paneDeadReason){if(!await _deps4.isPaneAlive(worker.paneId)){let message=await send(repoPath,from,worker.id,body);return console.error(`[protocol-router] Delivery failed: ${paneDeadReason} (worker=${worker.id}, msg="${body.slice(0,50)}")`),{messageId:message.id,workerId:worker.id,delivered:!1,reason:paneDeadReason}}return deliverToWorker(repoPath,from,worker,body)}async function findKnownWorker(to){let worker=await get(to);if(worker)return worker;let allWorkers=await list();return allWorkers.find((w)=>w.role===to&&w.state==="suspended")??allWorkers.find((w)=>w.role===to)??null}async function attemptAutoSpawnDelivery(repoPath,from,to,body,worker){let alive;try{alive=await ensureWorkerAlive(worker,to)}catch(err){if(err instanceof MissingResumeSessionError)return console.error(`[protocol-router] ${err.message}`),{messageId:"",workerId:worker?.id??to,delivered:!1,reason:err.message};throw err}if(!alive)return null;return deliverAfterPaneRecheck(repoPath,from,alive.worker,body,"pane dead after spawn")}async function sendMessage2(repoPath,from,to,body,teamName){if(from===to)return{messageId:"",workerId:to,delivered:!0,reason:"Self-delivery suppressed"};let liveMatches=await resolveRecipient(to);if(liveMatches.length===1)return deliverAfterPaneRecheck(repoPath,from,liveMatches[0],body,"Pane died before delivery");if(liveMatches.length>1)return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" is ambiguous. Found ${liveMatches.length} live matches: ${liveMatches.map((m)=>m.id).join(", ")}. Use exact worker ID.`};let{resolve:resolve7}=await Promise.resolve().then(() => (init_agent_directory(),exports_agent_directory)),dirResolved=await resolve7(to),worker=await findKnownWorker(to);if(dirResolved||worker){let result2=await attemptAutoSpawnDelivery(repoPath,from,to,body,worker);if(result2)return result2}let nativeResult=await deliverViaNativeInbox(repoPath,from,to,body,teamName);if(nativeResult)return nativeResult;return{messageId:"",workerId:to,delivered:!1,reason:`Worker "${to}" not found or not alive`}}async function writeToNativeInbox(worker,message){try{let nativeMsg=toNativeInboxMessage(message,worker.nativeColor??"blue"),agentName=worker.role??worker.id;return await writeNativeInbox(worker.team??"",agentName,nativeMsg),!0}catch{return!1}}async function injectToTmuxPane(worker,message){if(!worker.paneId)return!1;if(!/^%\d+$/.test(worker.paneId))return!1;if(!await _deps4.isPaneAlive(worker.paneId))return!1;try{let escaped=message.body.replace(/'/g,"'\\''");return await executeTmux2(`send-keys -t '${worker.paneId}' '${escaped}'`),await new Promise((resolve7)=>setTimeout(resolve7,200)),await executeTmux2(`send-keys -t '${worker.paneId}' Enter`),!0}catch{return!1}}async function deliverToPane(toWorker,messageId){let worker=await get(toWorker);if(!worker||!worker.paneId)return await markFailed(messageId),!1;if(!await _deps4.isPaneAlive(worker.paneId))return await markFailed(messageId),!1;let message=await getById(messageId);if(!message||message.deliveredAt)return!1;let injected=await injectToTmuxPane(worker,message);if(injected&&worker.repoPath)await markDelivered(worker.repoPath,worker.id,messageId);else await markFailed(messageId);return injected}async function getInbox(repoPath,workerId){return inbox(repoPath,workerId)}var MissingResumeSessionError,_deps4,AUTO_SPAWN_READY_TIMEOUT_MS=15000,AUTO_SPAWN_POLL_INTERVAL_MS=1000;var init_protocol_router=__esm(()=>{init_agent_registry();init_claude_native_teams();init_db();init_executor_registry();init_mailbox();init_orchestrator();init_should_resume();init_spawn_command();init_tmux();MissingResumeSessionError=class MissingResumeSessionError extends Error{workerId;entityId;recipientId;reason;constructor(workerId,recipientId,reason="null_session"){let suffix=recipientId?` (recipient "${recipientId}")`:"";super(`Cannot resume worker "${workerId}"${suffix}: executor has no claude_session_id recorded (reason: ${reason}). This usually means the worker predates the session-sync hook. Run \`genie reset ${workerId}\` or re-spawn the worker to recover.`);this.name="MissingResumeSessionError",this.workerId=workerId,this.entityId=workerId,this.recipientId=recipientId,this.reason=reason}};_deps4={isPaneAlive,waitForWorkerReady:null}});var exports_sdk_session_capture={};__export(exports_sdk_session_capture,{updateTurnCount:()=>updateTurnCount,startSession:()=>startSession,recordTurn:()=>recordTurn,endSession:()=>endSession});async function startSession(safePgCall,executorId,claudeSessionId,agentId,team,role,wishSlug){let sessionId=claudeSessionId??`sdk-${executorId}-${Date.now()}`;if(!await safePgCall("sdk-session-start",(sql)=>sql`INSERT INTO sessions (id, agent_id, executor_id, team, role, wish_slug, status, jsonl_path, project_path)
|
|
1784
1786
|
VALUES (${sessionId}, ${agentId}, ${executorId}, ${team??null}, ${role??null}, ${wishSlug??null}, 'active', '', '')
|
|
1785
1787
|
ON CONFLICT (id) DO UPDATE SET
|
|
1786
1788
|
agent_id = COALESCE(sessions.agent_id, EXCLUDED.agent_id),
|
|
@@ -2133,7 +2135,7 @@ Owner identities are exclusive \u2014 to spawn a separate worker under the same
|
|
|
2133
2135
|
LIMIT 1
|
|
2134
2136
|
`;if(anchor.length>0)await relinkExecutorToAgent(anchor[0].id,agentId),createdAnchor=!0,decision=await shouldResume(agentId)}}}return await recordAuditEvent("agent",agentId,"recover.surgery_applied",getActor(),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}).catch(()=>{}),{flippedAutoResume:flipped.length>0,staleSpawningTerminated:terminated.length,createdAnchor,sessionId:decision.sessionId??null}}async function jsonlHeadMatchesCustomName(filePath,customName){let{open:open4}=await import("fs/promises"),handle=null;try{handle=await open4(filePath,"r");let buffer2=Buffer.alloc(16384),{bytesRead}=await handle.read(buffer2,0,buffer2.length,0),head=buffer2.toString("utf-8",0,bytesRead);for(let line of head.split(`
|
|
2135
2137
|
`).slice(0,40)){let trimmed=line.trim();if(!trimmed)continue;try{let entry2=JSON.parse(trimmed);if(typeof entry2.agentName==="string"&&entry2.agentName===customName)return!0}catch{}}return!1}catch{return!1}finally{await handle?.close().catch(()=>{})}}async function resolveClaudeProjectDir(cwd){let{join:join49}=await import("path"),{homedir:homedir33}=await import("os"),sanitize=(p)=>p.replace(/[^a-zA-Z0-9]/g,"-"),claudeConfigDir5=process.env.CLAUDE_CONFIG_DIR??join49(homedir33(),".claude");return join49(claudeConfigDir5,"projects",sanitize(cwd))}async function listJsonlsByMtime(projectDir){let{readdir:readdir6,stat:stat5}=await import("fs/promises"),{join:join49}=await import("path"),entries;try{entries=await readdir6(projectDir)}catch{return[]}let jsonls=entries.filter((e)=>e.endsWith(".jsonl")),candidates=[];for(let name of jsonls){let full=join49(projectDir,name);try{let s=await stat5(full);candidates.push({name,full,mtime:s.mtimeMs})}catch{}}return candidates.sort((a,b2)=>b2.mtime-a.mtime),candidates}async function scanJsonlByCustomName(cwd,customName){let projectDir=await resolveClaudeProjectDir(cwd),candidates=await listJsonlsByMtime(projectDir);for(let candidate of candidates)if(await jsonlHeadMatchesCustomName(candidate.full,customName))return candidate.name.replace(/\.jsonl$/,"");return null}async function confirmRecover(agentId){if(!process.stdin.isTTY)return console.error(`Refusing to recover "${agentId}" without --yes in a non-interactive shell. Re-run with \`--yes\` for unattended use.`),!1;let{createInterface:createInterface3}=await import("readline"),rl=createInterface3({input:process.stdin,output:process.stdout}),answer=await new Promise((resolve7)=>{rl.question(`About to recover agent "${agentId}" (flip auto_resume, terminate stale spawning executors, resume). Continue? [y/N] `,(a)=>{rl.close(),resolve7(a.trim().toLowerCase())})});return answer==="y"||answer==="yes"}async function handleWorkerRecover(name,options){let agent=await resolveAgentForRecover(name);if(!options.yes){if(!await confirmRecover(agent.id)){console.log("Recovery cancelled.");return}}console.log(`Recovering agent "${agent.id}"...`);let surgery=await recoverSurgery(agent.id);if(surgery.flippedAutoResume)console.log(" \u2713 auto_resume re-enabled");if(surgery.staleSpawningTerminated>0)console.log(` \u2713 terminated ${surgery.staleSpawningTerminated} stale spawning executor(s)`);if(surgery.createdAnchor)console.log(" \u2713 created executor anchor from JSONL on disk");if(surgery.sessionId)console.log(` \u2713 session UUID located: ${surgery.sessionId}`);else console.error(` \u2717 no recoverable session UUID for "${agent.id}". JSONL scan in ${agent.repoPath??"<no repo_path>"} did not match custom_name="${agent.customName??agent.role??"<none>"}".`),process.exit(1);let resumeName=agent.customName??agent.role??agent.id;await handleWorkerResume(resumeName,{all:!1,noResetAttempts:!1});let post=await get(agent.id);if(post?.paneId&&post.session)console.log(` pane: ${post.paneId}`),console.log(` attach: tmux attach -t ${post.session}`)}async function buildResumeParams(agent,template,resumeSessionId){let agentName=agent.role??agent.id,provider=template?.provider??agent.provider??"claude",team=template?.team??agent.team??await discoverTeamName();if(!team)throw Error(`Cannot resume agent "${agent.id}": no team context (template, agent record, env, or session). Pass --team or set GENIE_TEAM, or run inside a registered tmux session.`);let systemPromptFile,promptMode,dirEntry=await get2(agentName);if(dirEntry?.dir)systemPromptFile=loadIdentity(dirEntry)??void 0,promptMode=dirEntry.promptMode;return{provider,team,role:agentName,skill:template?.skill??agent.skill,extraArgs:template?.extraArgs,resume:resumeSessionId,name:`${team}-${agentName}`,model:dirEntry?.model,systemPromptFile,promptMode}}function formatGroupStatus(name,group,allGroups){let detail=group.status;if(group.completedAt)detail+=` (completed at ${group.completedAt})`;else if(group.startedAt)detail+=` (started at ${group.startedAt})`;if(group.status==="blocked"&&group.dependsOn.length>0){let pending=group.dependsOn.filter((dep)=>allGroups[dep]?.status!=="done");if(pending.length>0)detail+=` (depends on ${pending.join(", ")})`}return`Group ${name}: ${detail}`}async function buildResumeContext(agent){if((agent.role==="team-lead"||agent.team&&agent.role===await resolveTeamLeaderName(agent.team))&&agent.wishSlug)try{let state=await(await Promise.resolve().then(() => (init_wish_state(),exports_wish_state))).getState(agent.wishSlug,agent.repoPath);if(state){let groupLines=Object.entries(state.groups).map(([name,group])=>formatGroupStatus(name,group,state.groups));return["You were resumed after a crash. Here's where you left off:",`Wish: ${state.wish}`,"",...groupLines,"",`Continue from where you left off. Run \`genie status ${state.wish}\` to verify, then dispatch the next wave.`].join(`
|
|
2136
|
-
`)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let params=await buildResumeParams(agent,template,
|
|
2138
|
+
`)}}catch{}if(agent.team)return"You were resumed. Check your team's current state with `genie status`.";return}async function buildFullResumeParams(agent,template){let decision=await shouldResume(agent.id);if(!decision.resume||!decision.sessionId){let errReason=decision.reason==="unknown_agent"?"no_executor":"null_session";throw new MissingResumeSessionError(agent.id,void 0,errReason)}let sessionId=await acquireResumeSessionForAttempt(agent.id)??decision.sessionId,params=await buildResumeParams(agent,template,sessionId),resumeContext=await buildResumeContext(agent);if(resumeContext)params.initialPrompt=resumeContext;if(agent.nativeTeamEnabled){let nativeResult=await resolveNativeTeam(params.team,agent.repoPath,{provider:params.provider,role:params.role,color:agent.nativeColor});if(nativeResult.nativeTeam)params.nativeTeam=nativeResult.nativeTeam}return params}async function createResumeExecutor(agent,params,paneId,teamWindow,cwd,spawnColor){let resumeAgentName=agent.role??agent.id,resumeTeam=agent.team??params.team,agentId=params.agentId??(await findOrCreateAgent(resumeAgentName,resumeTeam,agent.role)).id;await terminateActiveExecutorWithCleanup(agentId);let pid=await capturePanePid2(paneId);await createAndLinkExecutor2(agentId,params.provider,resolveExecutorTransport2(params.provider,"tmux"),{id:params.executorId,pid,tmuxSession:params.team,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:params.resume??null,state:"spawning",repoPath:cwd,paneColor:spawnColor})}function resumeTelemetryState(raw){return raw&&TELEMETRY_KNOWN_STATES.has(raw)?raw:"unknown"}function recordManualResumeTelemetry(shouldEmit,eventType,payload){if(!shouldEmit)return;recordAuditEvent("agent.resume",payload.entity_id,eventType,getActor(),{...payload,trigger:"manual"}).catch(()=>{});try{let v2={entity_id:payload.entity_id,attempt_number:payload.attempt_number,state_before:payload.state_before,state_after:payload.state_after,trigger:"manual"};if(payload.last_error)v2.last_error=payload.last_error.slice(0,500);if(eventType==="agent.resume.failed")v2.exhausted=payload.exhausted??!1;emitEvent(eventType,v2,{severity:eventType==="agent.resume.failed"?"warn":"info",source_subsystem:"cli.resume"})}catch{}}function buildResumeSpawnCtx(args){let{agent,validated,launch,fullCommand,now,template,resumeSessionId,teamName,agentIdentityId,executorId}=args;return{workerId:agent.id,validated,launch,layoutMode:resolveLayoutMode(void 0),fullCommand,agentName:agent.role??agent.id,spawnColor:agent.nativeColor??"blue",parentSessionId:agent.parentSessionId??`genie-${teamName}`,claudeSessionId:resumeSessionId,otelRelayActive:!1,now,transport:"tmux",extraArgs:template?.extraArgs,cwd:template?.cwd??agent.repoPath,spawnIntoCurrentWindow:!1,autoResume:agent.autoResume,agentIdentityId,executorId}}function createResumeTmuxPaneOrExit(ctx,teamWindow,telemetry){try{return createTmuxPane(ctx,teamWindow)}catch(err){let errorMessage=err instanceof Error?err.message:"unknown error";recordManualResumeTelemetry(telemetry.shouldEmit,"agent.resume.failed",{entity_id:telemetry.entityId,attempt_number:telemetry.attemptNumber,state_before:telemetry.stateBefore,state_after:telemetry.stateBefore,last_error:`createTmuxPane: ${errorMessage}`,exhausted:!1}),console.error(`Failed to create tmux pane: ${errorMessage}`),process.exit(1)}}function logResumeSuccess(agent,resumeSessionId,paneId,teamWindow){if(console.log(`Agent "${agent.id}" resumed.`),console.log(` Session: ${resumeSessionId??"(none)"}`),console.log(` Pane: ${paneId}`),teamWindow)console.log(` Window: ${teamWindow.windowName} (${teamWindow.windowId})`)}async function resumeAgent(agent,opts={}){let resetAttempts=opts.resetAttempts!==!1,template=(await listTemplates()).find((t)=>t.id===(agent.role??agent.id)),shouldEmitTelemetry=resetAttempts,telemetryStateBefore=resumeTelemetryState(agent.state),telemetryAttemptNumber=1;if(resetAttempts)await update(agent.id,{resumeAttempts:0});recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.attempted",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:telemetryStateBefore});let params=await buildFullResumeParams(agent,template),agentIdentity=await findOrCreateAgent(agent.role??agent.id,agent.team??params.team,agent.role),executorId=crypto.randomUUID();params.agentId=agentIdentity.id,params.executorId=executorId;let validated=validateSpawnParams(params),launch=buildLaunchCommand(validated),fullCommand=prependEnvVars(launch.command,launch.env),now=new Date().toISOString();if(!process.env.TMUX)console.error("Error: resume requires tmux. Start a tmux session first."),process.exit(1);let ctx=buildResumeSpawnCtx({agent,validated,launch,fullCommand,now,template,resumeSessionId:params.resume,teamName:params.team,agentIdentityId:agentIdentity.id,executorId}),teamWindow=await resolveSpawnTeamWindow(validated.team,ctx.cwd),paneId=createResumeTmuxPaneOrExit(ctx,teamWindow,{shouldEmit:shouldEmitTelemetry,entityId:agent.id,attemptNumber:1,stateBefore:telemetryStateBefore});if(await createResumeExecutor(agent,validated,paneId,teamWindow,ctx.cwd,ctx.spawnColor),await applySpawnLayout(ctx,teamWindow),await update(agent.id,{paneId,state:"spawning",startedAt:now,lastStateChange:now,suspendedAt:void 0,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId,window:teamWindow?.windowName}),await notifySpawnJoin(ctx,paneId),await injectResumeContext(ctx.cwd??agent.repoPath??process.cwd(),agent.id,agent.role??agent.id,params.team),ctx.spawnColor&&paneId!=="inline")await applyPaneColor(paneId,ctx.spawnColor,teamWindow?.windowId);recordAuditEvent("worker",agent.id,"worker.resume.attempted",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,claude_session_id:params.resume,pane_id:paneId}).catch(()=>{});let resumedPid=null;try{if(paneId!=="inline"){let{execSync:execSync11}=__require("child_process"),pidOutput=execSync11(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`),{encoding:"utf-8"}).trim(),parsedPid=Number.parseInt(pidOutput,10);if(resumedPid=parsedPid>0?parsedPid:null,!execSync11(genieTmuxCmd("list-panes -a -F '#{pane_id}'"),{encoding:"utf-8"}).split(`
|
|
2137
2139
|
`).map((line)=>line.trim()).includes(paneId))throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pane_not_in_list");if(resumedPid!==null)try{process.kill(resumedPid,0)}catch(err){if(err.code==="ESRCH")throw new ResumePaneVanishedError(agent.id,paneId,resumedPid,"pid_dead")}}}catch(err){if(err instanceof ResumePaneVanishedError)throw recordAuditEvent("worker",agent.id,"worker.resume.skipped",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,reason:err.reason,error:err.message}).catch(()=>{}),err;throw err}recordAuditEvent("worker",agent.id,"resumed",getActor(),{claudeSessionId:params.resume,team:agent.team}).catch(()=>{}),recordAuditEvent("worker",agent.id,"worker.resume.completed",getActor(),{worker_id:agent.id,agent_role:agent.role??agent.id,team:agent.team,pane_id:paneId,pid:resumedPid,claude_session_id:params.resume}).catch(()=>{}),recordManualResumeTelemetry(shouldEmitTelemetry,"agent.resume.succeeded",{entity_id:agent.id,attempt_number:1,state_before:telemetryStateBefore,state_after:"spawning"}),logResumeSuccess(agent,params.resume,paneId,teamWindow)}async function resolveWorkerLiveness(w){if(/^%\d+$/.test(w.paneId))return{alive:await isPaneAliveOrDead(w.paneId),state:w.state};let execState=await getLiveExecutorState(w.id);return{alive:execState!==null,state:execState??w.state}}async function buildWorkerStatusMap(workers){let statusMap=new Map;for(let w of workers){let name=w.customName??w.role??w.id,{alive,state}=await resolveWorkerLiveness(w);if(alive)statusMap.set(name,{state,team:w.team||"-"});else if(w.state==="suspended"||w.state==="error"){let attempts=w.resumeAttempts??0,max=w.maxResumeAttempts??3,autoStr=w.autoResume===!1?"off":"on";statusMap.set(name,{state:`${w.state} (${attempts}/${max} resumes, auto-resume: ${autoStr})`,team:w.team||"-",resumeAttempts:attempts,maxResumeAttempts:max,autoResume:w.autoResume!==!1})}}return statusMap}async function resolveAgentNamesBySource(source){let executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agentRegistry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executors=await executorRegistry.listExecutors(void 0,source),agentIds=new Set(executors.map((e)=>e.agentId)),agents=await agentRegistry.listAgents({});return new Set(agents.filter((a)=>agentIds.has(a.id)).map((a)=>a.customName??a.role??a.id))}async function handleLsCommand(options){let dirEntries=await ls(),workers=await listForRender(),statusMap=await buildWorkerStatusMap(workers),sourceAgentNames=options.source?await resolveAgentNamesBySource(options.source):void 0,entries=[];for(let entry2 of dirEntries){let running2=statusMap.get(entry2.name);entries.push({name:entry2.name,dir:entry2.dir||"-",status:running2?running2.state:"offline",team:running2?.team||"-",model:entry2.model||"-",resumeAttempts:running2?.resumeAttempts,maxResumeAttempts:running2?.maxResumeAttempts,autoResume:running2?.autoResume}),statusMap.delete(entry2.name)}for(let[name,info]of statusMap)entries.push({name,dir:"(built-in)",status:info.state,team:info.team,model:"-",resumeAttempts:info.resumeAttempts,maxResumeAttempts:info.maxResumeAttempts,autoResume:info.autoResume});if(sourceAgentNames)entries=entries.filter((e)=>sourceAgentNames.has(e.name));if(!options.all)entries=entries.filter((e)=>e.status!=="archived");if(options.json){console.log(JSON.stringify(entries,null,2));return}if(entries.length===0){console.log("No agents registered. Use `genie dir add <name> --dir <path>` to register one.");return}console.log(""),console.log(formatLsRow("NAME","DIR","STATUS","TEAM","MODEL")),console.log("-".repeat(106));for(let e of entries)console.log(formatLsRow(e.name,e.dir,e.status,e.team,e.model));console.log("")}function formatLsRow(name,dir,status,team,model){return`${name.padEnd(20).substring(0,20)}${dir.padEnd(30).substring(0,30)}${status.padEnd(44).substring(0,44)}${team.padEnd(12).substring(0,12)}${model}`}var SpawnPaneVanishedError,AgentReadinessTimeoutError,OwnerSpawnCollisionError,_spawnAutoSyncDeps,UUID_REGEX,RecoverAgentNotFoundError,TELEMETRY_KNOWN_STATES,ResumePaneVanishedError;var init_agents=__esm(()=>{init_agent_directory();init_agent_registry();init_agent_sync();init_audit();init_builtin_agents();init_claude_native_teams();init_codex_config();init_defaults();init_emit();init_ensure_tmux();init_executor_registry();init_frontmatter();init_otel_receiver();init_protocol_router_spawn();init_protocol_router();init_provider_adapters();init_registry2();init_should_resume();init_spawn_command();init_team_manager();init_tmux_wrapper();init_tmux();init_tmux();init_workspace();SpawnPaneVanishedError=class SpawnPaneVanishedError extends Error{paneId;expectedPid;reason;constructor(paneId,expectedPid,reason){super(`Spawn pane "${paneId}" (pid=${expectedPid??"unknown"}) vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched script likely failed to exec the worker binary; check ~/.genie/spawn-scripts/ and tmux server log.`);this.name="SpawnPaneVanishedError",this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}};AgentReadinessTimeoutError=class AgentReadinessTimeoutError extends Error{role;paneId;elapsedMs;constructor(role,paneId,elapsedMs){super(`Agent "${role}" did not become ready within ${Math.round(elapsedMs/1000)}s (pane ${paneId}). This likely means the worker process exited before the readiness probe succeeded. Pass tolerateReadinessTimeout:true on SpawnOptions to fall back to warn-and-proceed.`);this.name="AgentReadinessTimeoutError",this.role=role,this.paneId=paneId,this.elapsedMs=elapsedMs}};OwnerSpawnCollisionError=class OwnerSpawnCollisionError extends Error{ownerName;team;conflictId;conflictPaneId;conflictState;name="OwnerSpawnCollisionError";constructor(ownerName,team,conflictId,conflictPaneId,conflictState){super(`owner agent "${ownerName}" already alive in team "${team}"`);this.ownerName=ownerName;this.team=team;this.conflictId=conflictId;this.conflictPaneId=conflictPaneId;this.conflictState=conflictState}};_spawnAutoSyncDeps={resolveAgent:resolve6,loadIdentity,syncSingleAgentByName,findWorkspace,parseFrontmatter,existsSync:existsSync40,readFileSync:readFileSync25,stderr:(line)=>console.error(line)};UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;RecoverAgentNotFoundError=class RecoverAgentNotFoundError extends Error{recoverName;constructor(recoverName){super(`Agent "${recoverName}" not found. Tried exact id, dir:<name>, custom_name, and role lookups. Run \`genie agent list\` to see registered agents.`);this.name="RecoverAgentNotFoundError",this.recoverName=recoverName}};TELEMETRY_KNOWN_STATES=new Set(["spawning","working","idle","permission","question","done","error","suspended"]);ResumePaneVanishedError=class ResumePaneVanishedError extends Error{agentId;paneId;expectedPid;reason;constructor(agentId,paneId,expectedPid,reason){super(`Resumed pane "${paneId}" (pid=${expectedPid??"unknown"}) for agent "${agentId}" vanished immediately after createTmuxPane returned. Reason: ${reason}. The launched resume script likely failed to exec the worker binary.`);this.name="ResumePaneVanishedError",this.agentId=agentId,this.paneId=paneId,this.expectedPid=expectedPid,this.reason=reason}}});var exports_codex_logs={};__export(exports_codex_logs,{parseCodexLine:()=>parseCodexLine,extractCodexContent:()=>extractCodexContent,codexTranscriptProvider:()=>codexTranscriptProvider});import{access as access2,readFile as readFile11,readdir as readdir6}from"fs/promises";import{homedir as homedir33}from"os";import{join as join49}from"path";function getCodexDir(){return join49(homedir33(),".codex")}function getSessionsDir(){return join49(getCodexDir(),"sessions")}function getStateDbPath(){return join49(getCodexDir(),"state_5.sqlite")}async function discoverLogPath(worker){let cwd=worker.worktree||worker.repoPath,sqlitePath=await discoverViaSqlite(cwd);if(sqlitePath)try{return await access2(sqlitePath),sqlitePath}catch{}return discoverViaScan(cwd)}async function discoverViaSqlite(cwd){try{let{Database}=await import("bun:sqlite"),dbPath=getStateDbPath(),db=new Database(dbPath,{readonly:!0});try{return db.query("SELECT rollout_path FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT 1").get(cwd)?.rollout_path??null}finally{db.close()}}catch{return null}}async function listDirsDesc(parent,pattern){return(await readdir6(parent)).filter((d)=>pattern.test(d)).sort().reverse()}async function discoverViaScan(cwd){let sessionsDir=getSessionsDir();try{let years=await listDirsDesc(sessionsDir,/^\d{4}$/);for(let year of years.slice(0,2)){let result2=await scanYear(join49(sessionsDir,year),cwd);if(result2)return result2}}catch{}return null}async function scanYear(yearDir,cwd){let months=await listDirsDesc(yearDir,/^\d{2}$/);for(let month of months.slice(0,2)){let result2=await scanMonth(join49(yearDir,month),cwd);if(result2)return result2}return null}async function scanMonth(monthDir,cwd){let days=await listDirsDesc(monthDir,/^\d{2}$/);for(let day of days.slice(0,3)){let result2=await scanDay(join49(monthDir,day),cwd);if(result2)return result2}return null}async function scanDay(dayDir,cwd){let files=(await readdir6(dayDir)).filter((f)=>f.endsWith(".jsonl")).sort().reverse();for(let file of files.slice(0,5)){let filePath=join49(dayDir,file);if((await readSessionMeta(filePath))?.cwd===cwd)return filePath}return null}async function readSessionMeta(filePath){try{let content=await readFile11(filePath,"utf-8"),nlIdx=content.indexOf(`
|
|
2138
2140
|
`),firstLine=nlIdx===-1?content:content.slice(0,nlIdx),entry2=JSON.parse(firstLine);if(entry2.type==="session_meta"&&entry2.payload?.cwd)return{cwd:entry2.payload.cwd}}catch{}return null}function parseEventMsg(payload,ts3,base){if(payload.type==="user_message"){let text=String(payload.message??"");return text?[{...base,role:"user",timestamp:ts3,text}]:[]}if(payload.type==="agent_message"){let text=String(payload.message??"");return text?[{...base,role:"assistant",timestamp:ts3,text}]:[]}return[]}function parseResponseMessage(payload,ts3,base){let role=payload.role,text=extractCodexContent(payload.content);if(!text)return[];if(role==="user")return[{...base,role:"user",timestamp:ts3,text}];if(role==="developer")return[{...base,role:"system",timestamp:ts3,text}];if(role==="assistant")return[{...base,role:"assistant",timestamp:ts3,text}];return[]}function parseFunctionCall(payload,ts3,base){let name=String(payload.name??payload.type),callId=String(payload.call_id??""),input={};try{input=typeof payload.arguments==="string"?JSON.parse(payload.arguments):{}}catch{input={raw:payload.arguments}}let cmdText=input.command?String(Array.isArray(input.command)?input.command.join(" "):input.command):name;return[{...base,role:"tool_call",timestamp:ts3,text:`${name}: ${cmdText.slice(0,200)}`,toolCall:{id:callId,name,input}}]}function parseWebSearch(payload,ts3,base){let action=payload.action,query2=String(action?.query??"web search");return[{...base,role:"tool_call",timestamp:ts3,text:`web_search: ${query2.slice(0,200)}`,toolCall:{id:"",name:"web_search",input:{query:query2}}}]}function parseResponseItem(payload,ts3,base){if(payload.type==="message")return parseResponseMessage(payload,ts3,base);if(payload.type==="function_call"||payload.type==="shell")return parseFunctionCall(payload,ts3,base);if(payload.type==="function_call_output"){let output=String(payload.output??"").slice(0,500);return[{...base,role:"tool_result",timestamp:ts3,text:output}]}if(payload.type==="web_search_call")return parseWebSearch(payload,ts3,base);return[]}function parseCodexLine(line){if(!line.trim())return[];let raw;try{raw=JSON.parse(line)}catch{return[]}if(!raw.type||!raw.timestamp)return[];let base={provider:"codex",raw};if(!raw.payload||typeof raw.payload!=="object")return[];if(raw.type==="event_msg")return parseEventMsg(raw.payload,raw.timestamp,base);if(raw.type==="response_item")return parseResponseItem(raw.payload,raw.timestamp,base);return[]}function extractCodexContent(content){if(typeof content==="string")return content;if(!Array.isArray(content))return"";let parts=[];for(let item of content)if(typeof item==="string")parts.push(item);else if(item&&typeof item==="object"){if("text"in item)parts.push(String(item.text));else if("input_text"in item)parts.push(String(item.input_text))}return parts.join(" ")}async function readEntries(logPath){let content;try{content=await readFile11(logPath,"utf-8")}catch{return[]}return content.split(`
|
|
2139
2141
|
`).flatMap(parseCodexLine)}var codexTranscriptProvider;var init_codex_logs=__esm(()=>{codexTranscriptProvider={discoverLogPath,readEntries}});var exports_transcript={};__export(exports_transcript,{readTranscript:()=>readTranscript,getProvider:()=>getProvider2,applyFilter:()=>applyFilter});function applyFilter(entries,filter){if(!filter)return entries;let result2=entries;if(filter.since){let sinceMs=new Date(filter.since).getTime();result2=result2.filter((e)=>new Date(e.timestamp).getTime()>=sinceMs)}if(filter.roles&&filter.roles.length>0){let roles=new Set(filter.roles);result2=result2.filter((e)=>roles.has(e.role))}if(filter.last&&filter.last>0)result2=result2.slice(-filter.last);return result2}async function getClaudeProvider(){if(!_claudeProvider)_claudeProvider=(await Promise.resolve().then(() => (init_claude_logs(),exports_claude_logs))).claudeTranscriptProvider;return _claudeProvider}async function getCodexProvider(){if(!_codexProvider)_codexProvider=(await Promise.resolve().then(() => (init_codex_logs(),exports_codex_logs))).codexTranscriptProvider;return _codexProvider}async function getProvider2(worker){if((worker.provider??"claude")==="codex")return getCodexProvider();return getClaudeProvider()}async function readTranscript(worker,filter){let provider=await getProvider2(worker),logPath=await provider.discoverLogPath(worker);if(!logPath)return[];let entries=await provider.readEntries(logPath);return applyFilter(entries,filter)}var _claudeProvider,_codexProvider;function isTmuxMarkerOrNoise(line){let trimmed=line.trim();if(trimmed.includes("TMUX_MCP_START")||trimmed.includes("TMUX_MCP_DONE_"))return!0;if(line.includes('echo "TMUX_MCP_START"')||line.includes('echo "TMUX_MCP_DONE_'))return!0;if(line.includes("-bash:")||line.includes("warning: setlocale:")||line.includes("cannot change locale"))return!0;if(trimmed==="or directory")return!0;return!1}function stripTmuxMarkers(content){let filtered=content.split(`
|
|
@@ -2590,9 +2592,8 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2590
2592
|
COUNT(*) AS total
|
|
2591
2593
|
FROM teams
|
|
2592
2594
|
`,sql`
|
|
2593
|
-
SELECT COALESCE(SUM(
|
|
2594
|
-
FROM
|
|
2595
|
-
WHERE event_type = 'claude_code.cost.usage'
|
|
2595
|
+
SELECT COALESCE(SUM(cost_usd), 0) AS total_cost
|
|
2596
|
+
FROM v_claude_usage_events
|
|
2596
2597
|
`,sql`SELECT * FROM machine_snapshots ORDER BY created_at DESC LIMIT 1`]);return{agents:{online:Number(agentRows[0]?.online??0),errored:Number(agentRows[0]?.errored??0),total:Number(agentRows[0]?.total??0)},tasks:{active:Number(taskRows[0]?.active??0),backlog:Number(taskRows[0]?.backlog??0),done:Number(taskRows[0]?.done??0),total:Number(taskRows[0]?.total??0)},teams:{active:Number(teamRows[0]?.active??0),total:Number(teamRows[0]?.total??0)},cost_usd:Number(costRows[0]?.total_cost??0),snapshot:snapshot[0]??null}}),reply(sub.agents.list(ORG_ID),async()=>{return sql`
|
|
2597
2598
|
SELECT a.id, a.custom_name, a.role, a.team, a.title, a.state,
|
|
2598
2599
|
a.reports_to, a.current_executor_id, a.started_at
|
|
@@ -2617,10 +2618,10 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2617
2618
|
SELECT s.id, s.status, s.total_turns, s.started_at, s.ended_at,
|
|
2618
2619
|
a.custom_name AS agent_name,
|
|
2619
2620
|
COALESCE(
|
|
2620
|
-
(SELECT SUM(
|
|
2621
|
-
FROM
|
|
2622
|
-
WHERE
|
|
2623
|
-
|
|
2621
|
+
(SELECT SUM(v.cost_usd)
|
|
2622
|
+
FROM v_claude_usage_events v
|
|
2623
|
+
WHERE v.executor_id = e.id
|
|
2624
|
+
OR v.session_id = s.id), 0
|
|
2624
2625
|
) AS cost_usd
|
|
2625
2626
|
FROM sessions s
|
|
2626
2627
|
LEFT JOIN executors e ON s.executor_id = e.id
|
|
@@ -2663,49 +2664,45 @@ Genie Serve`),console.log("\u2500".repeat(50)),console.log(` Status: ${runn
|
|
|
2663
2664
|
`,tasks=await sql`
|
|
2664
2665
|
SELECT * FROM tasks WHERE board_id = ${params.board_id} ORDER BY seq ASC
|
|
2665
2666
|
`;return{board:boards[0],columns,tasks}}),reply(sub.costs.summary(ORG_ID),async()=>{return sql`
|
|
2666
|
-
SELECT
|
|
2667
|
-
SUM(
|
|
2667
|
+
SELECT model,
|
|
2668
|
+
SUM(cost_usd) AS total_cost,
|
|
2668
2669
|
COUNT(*) AS usage_count
|
|
2669
|
-
FROM
|
|
2670
|
-
|
|
2671
|
-
GROUP BY 1
|
|
2670
|
+
FROM v_claude_usage_events
|
|
2671
|
+
GROUP BY model
|
|
2672
2672
|
ORDER BY total_cost DESC
|
|
2673
2673
|
`}),reply(sub.costs.sessions(ORG_ID),async(params)=>{let limit=params.limit??50;return sql`
|
|
2674
|
-
SELECT
|
|
2674
|
+
SELECT COALESCE(v.executor_id, v.session_id, v.entity_id) AS executor_id,
|
|
2675
2675
|
a.custom_name AS agent_name,
|
|
2676
|
-
SUM(
|
|
2676
|
+
SUM(v.cost_usd) AS cost_usd,
|
|
2677
2677
|
COUNT(*) AS events
|
|
2678
|
-
FROM
|
|
2679
|
-
LEFT JOIN executors e ON
|
|
2678
|
+
FROM v_claude_usage_events v
|
|
2679
|
+
LEFT JOIN executors e ON v.executor_id = e.id
|
|
2680
2680
|
LEFT JOIN agents a ON e.agent_id = a.id
|
|
2681
|
-
|
|
2682
|
-
GROUP BY ae.entity_id, a.custom_name
|
|
2681
|
+
GROUP BY 1, a.custom_name
|
|
2683
2682
|
ORDER BY cost_usd DESC
|
|
2684
2683
|
LIMIT ${limit}
|
|
2685
2684
|
`}),reply(sub.costs.tokens(ORG_ID),async()=>{return sql`
|
|
2686
|
-
SELECT
|
|
2687
|
-
SUM(
|
|
2688
|
-
SUM(
|
|
2689
|
-
SUM(COALESCE(
|
|
2690
|
-
SUM(COALESCE(
|
|
2691
|
-
FROM
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
ORDER BY input_tokens DESC
|
|
2685
|
+
SELECT model,
|
|
2686
|
+
SUM(input_tokens) AS input_tokens,
|
|
2687
|
+
SUM(output_tokens) AS output_tokens,
|
|
2688
|
+
SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens,
|
|
2689
|
+
SUM(COALESCE(cache_write_tokens, 0)) AS cache_write_tokens
|
|
2690
|
+
FROM v_claude_usage_events
|
|
2691
|
+
GROUP BY model
|
|
2692
|
+
ORDER BY input_tokens DESC NULLS LAST
|
|
2695
2693
|
`}),reply(sub.costs.efficiency(ORG_ID),async()=>{return sql`
|
|
2696
|
-
SELECT
|
|
2697
|
-
SUM(COALESCE(
|
|
2698
|
-
SUM(COALESCE(
|
|
2699
|
-
CASE WHEN SUM(COALESCE(
|
|
2694
|
+
SELECT model,
|
|
2695
|
+
SUM(COALESCE(cache_read_tokens, 0)) AS cache_hits,
|
|
2696
|
+
SUM(COALESCE(input_tokens, 0)) AS total_input,
|
|
2697
|
+
CASE WHEN SUM(COALESCE(input_tokens, 0)) > 0
|
|
2700
2698
|
THEN ROUND(
|
|
2701
|
-
SUM(COALESCE(
|
|
2702
|
-
SUM(COALESCE(
|
|
2699
|
+
SUM(COALESCE(cache_read_tokens, 0))::numeric /
|
|
2700
|
+
SUM(COALESCE(input_tokens, 0))::numeric * 100, 2
|
|
2703
2701
|
)
|
|
2704
2702
|
ELSE 0
|
|
2705
2703
|
END AS cache_hit_pct
|
|
2706
|
-
FROM
|
|
2707
|
-
|
|
2708
|
-
GROUP BY 1
|
|
2704
|
+
FROM v_claude_usage_events
|
|
2705
|
+
GROUP BY model
|
|
2709
2706
|
ORDER BY cache_hit_pct DESC
|
|
2710
2707
|
`}),reply(sub.schedules.list(ORG_ID),async()=>{try{return await sql`
|
|
2711
2708
|
SELECT id, name, cron_expression, timezone, command, status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260503.
|
|
3
|
+
"version": "4.260503.2",
|
|
4
4
|
"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"wishes:lint": "bun run scripts/wishes-lint.ts",
|
|
44
44
|
"skills:audit": "bun run scripts/skills-audit.ts",
|
|
45
45
|
"lint:emit": "bun run scripts/lint-emit-discipline.ts",
|
|
46
|
+
"lint:complexity-budget": "bun run scripts/complexity-budget.ts",
|
|
46
47
|
"check:perf": "bun run test/perf/observability/gate.ts --duration=30000",
|
|
47
48
|
"check": "bun run typecheck && bun run lint && bun run dead-code && bun run skills:lint && bun run wishes:lint && bun run lint:emit && bun test",
|
|
48
49
|
"check:fast": "bun run typecheck && bun run lint && bun run dead-code && bun run skills:lint && bun run wishes:lint && bun run lint:emit",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260503.
|
|
3
|
+
"version": "4.260503.2",
|
|
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"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- 058_claude_usage_view.sql — normalize Claude cost/usage rows into one view.
|
|
2
|
+
--
|
|
3
|
+
-- Background (observability-signal-normalization, Group 2):
|
|
4
|
+
-- Claude cost telemetry lands in `audit_events` under two shapes:
|
|
5
|
+
--
|
|
6
|
+
-- 1. OTel metric — emitted by the OTel receiver from `claude_code.cost.usage`
|
|
7
|
+
-- sum/gauge data points. Cost is stored under `details->>'value'`.
|
|
8
|
+
-- entity_type='otel_metric'. Entity_id is usually the Claude session UUID
|
|
9
|
+
-- (or 'agent:<name>' / 'unknown' when no session attribute is attached).
|
|
10
|
+
--
|
|
11
|
+
-- 2. Legacy / fixture shape — historical rows (and current tests) that store
|
|
12
|
+
-- cost under `details->>'cost_usd'`. entity_type varies; usually keyed on
|
|
13
|
+
-- executor.id or a synthetic request id.
|
|
14
|
+
--
|
|
15
|
+
-- App + CLI cost queries previously only honored shape (2), so OTel cost rows
|
|
16
|
+
-- — the bulk of real traffic — silently summed to zero. This view is the one
|
|
17
|
+
-- canonical projection: callers read `cost_usd`, `model`, token columns,
|
|
18
|
+
-- `agent_id`, `executor_id`, `session_id`, `created_at` regardless of source
|
|
19
|
+
-- shape.
|
|
20
|
+
--
|
|
21
|
+
-- Scope: only `event_type = 'claude_code.cost.usage'` rows are exposed. Other
|
|
22
|
+
-- OTel logs (api_request, tool_result, …) keep their existing query paths;
|
|
23
|
+
-- this view does not try to be a universal cost ledger.
|
|
24
|
+
|
|
25
|
+
CREATE OR REPLACE VIEW v_claude_usage_events AS
|
|
26
|
+
SELECT
|
|
27
|
+
ae.id,
|
|
28
|
+
ae.entity_type,
|
|
29
|
+
ae.entity_id,
|
|
30
|
+
ae.event_type,
|
|
31
|
+
ae.actor,
|
|
32
|
+
COALESCE(NULLIF(ae.details->>'model', ''), 'unknown') AS model,
|
|
33
|
+
-- Prefer explicit cost_usd (legacy/test shape); fall back to OTel `value`.
|
|
34
|
+
-- COALESCE before cast keeps NULLs in malformed rows from poisoning sums.
|
|
35
|
+
COALESCE(
|
|
36
|
+
NULLIF(ae.details->>'cost_usd', '')::numeric,
|
|
37
|
+
NULLIF(ae.details->>'value', '')::numeric,
|
|
38
|
+
0::numeric
|
|
39
|
+
) AS cost_usd,
|
|
40
|
+
NULLIF(ae.details->>'input_tokens', '')::bigint AS input_tokens,
|
|
41
|
+
NULLIF(ae.details->>'output_tokens', '')::bigint AS output_tokens,
|
|
42
|
+
NULLIF(ae.details->>'cache_read_tokens', '')::bigint AS cache_read_tokens,
|
|
43
|
+
NULLIF(ae.details->>'cache_write_tokens', '')::bigint AS cache_write_tokens,
|
|
44
|
+
COALESCE(
|
|
45
|
+
NULLIF(ae.actor, ''),
|
|
46
|
+
NULLIF(ae.details->>'agent.name', ''),
|
|
47
|
+
NULLIF(ae.details->>'agent_id', '')
|
|
48
|
+
) AS agent_id,
|
|
49
|
+
-- executorId comes from SDK rows; legacy rows often key entity_id directly to executor.id.
|
|
50
|
+
COALESCE(
|
|
51
|
+
NULLIF(ae.details->>'executorId', ''),
|
|
52
|
+
NULLIF(ae.details->>'executor_id', '')
|
|
53
|
+
) AS executor_id,
|
|
54
|
+
-- session_id is set by the OTel receiver when the resource carries `session.id`.
|
|
55
|
+
-- For OTel rows entity_id IS the session UUID, so fall back to it as a hint.
|
|
56
|
+
COALESCE(
|
|
57
|
+
NULLIF(ae.details->>'session_id', ''),
|
|
58
|
+
NULLIF(ae.details->>'sessionId', ''),
|
|
59
|
+
CASE WHEN ae.entity_type = 'otel_metric' THEN NULLIF(ae.entity_id, '') END
|
|
60
|
+
) AS session_id,
|
|
61
|
+
ae.details AS details,
|
|
62
|
+
ae.created_at AS created_at
|
|
63
|
+
FROM audit_events ae
|
|
64
|
+
WHERE ae.event_type = 'claude_code.cost.usage';
|
|
65
|
+
|
|
66
|
+
COMMENT ON VIEW v_claude_usage_events IS
|
|
67
|
+
'Normalized cost/usage projection over audit_events. Maps both OTel `claude_code.cost.usage` metric shape (details.value) and legacy/test cost_usd shape into a unified per-row API. See migration 058 for source of truth.';
|