@automagik/genie 4.260429.9 → 4.260429.11

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
@@ -180,7 +180,7 @@ ${content}`;if(params.extraArgs){let fileIdx=params.extraArgs.indexOf("--append-
180
180
  ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(fileIdx,2)}writeFileSync6(promptFile,content);let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg2(promptFile))}else if(params.systemPromptFile){let flag=params.promptMode==="system"?"--system-prompt-file":"--append-system-prompt-file";parts.push(flag,escapeShellArg2(params.systemPromptFile))}}function appendOtelEnv(env,params){if(!params.otelPort||process.env.OTEL_EXPORTER_OTLP_ENDPOINT)return;if(env.CLAUDE_CODE_ENABLE_TELEMETRY="1",env.OTEL_LOGS_EXPORTER="otlp",env.OTEL_METRICS_EXPORTER="otlp",env.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",env.OTEL_EXPORTER_OTLP_ENDPOINT=`http://127.0.0.1:${params.otelPort}`,env.OTEL_LOG_TOOL_DETAILS="1",params.otelLogPrompts!==!1)env.OTEL_LOG_USER_PROMPTS="1";let resourceParts=[];if(params.role)resourceParts.push(`agent.name=${params.role}`);if(params.team)resourceParts.push(`team.name=${params.team}`);if(params.otelWishSlug)resourceParts.push(`wish.slug=${params.otelWishSlug}`);if(params.role)resourceParts.push(`agent.role=${params.role}`);if(resourceParts.length>0)env.OTEL_RESOURCE_ATTRIBUTES=resourceParts.join(",")}function appendTraceContext(parts,env,params){let ctx=getAmbient();if(params.initialPrompt){let prompt=ctx?injectPromptPreamble(params.initialPrompt,ctx):params.initialPrompt;parts.push(escapeShellArg2(prompt))}if(ctx)env[TRACE_ENV_VAR]=mintToken(ctx),env[TRACE_ID_ENV_VAR]=ctx.trace_id}function buildClaudeGenieEnv(params){let env={};if(env.GENIE_WORKER="1",params.role)env.GENIE_AGENT_NAME=params.role;if(params.team)env.GENIE_TEAM=params.team;if(params.executorId)env.GENIE_EXECUTOR_ID=params.executorId;if(params.agentId)env.GENIE_AGENT_ID=params.agentId;return env}function appendSessionFlags(parts,params){if(params.resume)parts.push("--resume",escapeShellArg2(params.resume));else if(params.sessionId)parts.push("--session-id",escapeShellArg2(params.sessionId));let claudeAgentFlag=params.agentTemplate??params.role;if(claudeAgentFlag)parts.push("--agent",escapeShellArg2(claudeAgentFlag));if(params.model)parts.push("--model",escapeShellArg2(params.model));if(params.name)parts.push("--name",escapeShellArg2(params.name))}function buildSettingsObject(params){let settingsObj={};if(!params.skipHooks){let hookEntry={type:"command",command:buildDispatchCommand(),timeout:15};settingsObj.hooks={PreToolUse:[{matcher:"*",hooks:[hookEntry]}],PostToolUse:[{matcher:"*",hooks:[hookEntry]}],UserPromptSubmit:[{hooks:[hookEntry]}],Stop:[{hooks:[hookEntry]}]}}if(params.permissions){let perms={};if(params.permissions.allow?.length)perms.allow=params.permissions.allow;if(params.permissions.deny?.length)perms.deny=params.permissions.deny;if(Object.keys(perms).length>0)settingsObj.permissions=perms}return settingsObj}function warnIfAllowRulesAreBypassed(params){if(!params.permissions?.allow?.length)return;if(params.nativeTeam?.permissionMode!=="bypassPermissions")return;let agentName=params.nativeTeam.agentName??params.role??params.name??"unknown";process.stderr.write(`Warning: agent ${agentName} declares permissions.allow but permissionMode is bypassPermissions \u2014 allow rules are advisory under bypass (deny still enforced).
181
181
  `)}function appendDisallowedAndExtraArgs(parts,params){if(params.disallowedTools?.length)for(let tool of params.disallowedTools)parts.push("--disallowedTools",escapeShellArg2(tool));if(params.extraArgs)for(let arg of params.extraArgs)parts.push(escapeShellArg2(arg))}function assertClaudeBuiltinHasIdentity(params){let templateName=params.agentTemplate??params.role;if(!templateName)return;if(params.resume)return;if(!resolveBuiltinAgentPath(templateName))return;if(params.systemPromptFile||params.systemPrompt)return;throw Error(`Refusing to launch built-in agent "${templateName}" without AGENTS.md identity. Resolve systemPromptFile or systemPrompt before building the Claude command.`)}function buildClaudeCommand(params){assertClaudeBuiltinHasIdentity(params),preflightCheck("claude");let parts=[resolveShellBinary("claude")??"claude","--permission-mode",escapeShellArg2("auto")],env=buildClaudeGenieEnv(params);if(appendOtelEnv(env,params),params.nativeTeam?.enabled)appendNativeTeamFlags(parts,env,params.nativeTeam,params);appendSessionFlags(parts,params),appendSystemPromptFlags(parts,params),warnIfAllowRulesAreBypassed(params);let settingsObj=buildSettingsObject(params);if(Object.keys(settingsObj).length>0)parts.push("--settings",escapeShellArg2(JSON.stringify(settingsObj)));return appendDisallowedAndExtraArgs(parts,params),appendTraceContext(parts,env,params),{command:parts.join(" "),provider:"claude",env:Object.keys(env).length>0?env:void 0,meta:{role:params.role,skill:params.skill}}}function buildCodexAutoPrompt(params){let promptParts=[`Genie worker. Team: ${params.team}.`];if(params.role)promptParts.push(`Role: ${params.role}.`);if(params.skill)promptParts.push(`Execute the ${params.skill} skill instructions.`);return promptParts.join(" ")}function sanitizeCodexPromptFileStem(stem){return stem.replace(/[^a-zA-Z0-9._-]/g,"-")||Date.now().toString(36)}function splitCodexExtraArgs(extraArgs){let forwarded=[],promptFiles=[],args=extraArgs??[];for(let i2=0;i2<args.length;i2++){let arg=args[i2],equalsFlag=[...CODEX_PROMPT_FILE_FLAGS].find((flag)=>arg.startsWith(`${flag}=`));if(equalsFlag){let path=arg.slice(equalsFlag.length+1);if(!path)throw Error(`Missing path for ${equalsFlag}`);promptFiles.push(path);continue}if(CODEX_PROMPT_FILE_FLAGS.has(arg)){let path=args[i2+1];if(!path)throw Error(`Missing path after ${arg}`);promptFiles.push(path),i2++;continue}forwarded.push(arg)}return{forwarded,promptFiles}}function buildCodexMergedPrompt(params,extraPromptFiles){let{readFileSync:readFileSync6}=__require("fs"),sections=[];if(params.systemPromptFile!==void 0)sections.push(readFileSync6(params.systemPromptFile,"utf-8"));if(params.systemPrompt!==void 0)sections.push(params.systemPrompt);for(let promptFile of extraPromptFiles)sections.push(readFileSync6(promptFile,"utf-8"));if(params.initialPrompt!==void 0)sections.push(params.initialPrompt);return sections.length>0?sections.join(`
182
182
 
183
- `):null}function writeCodexPromptFile(params,content){let{mkdirSync:mkdirSync5,writeFileSync:writeFileSync6}=__require("fs"),{join:join10}=__require("path");mkdirSync5(CODEX_PROMPT_DIR,{recursive:!0});let stem=sanitizeCodexPromptFileStem(params.executorId??Date.now().toString(36)),promptFile=join10(CODEX_PROMPT_DIR,`${stem}.txt`);return writeFileSync6(promptFile,content,"utf-8"),promptFile}function buildCodexCommand(params){preflightCheck("codex");let parts=["codex"],env={};if(params.executorId)env.GENIE_EXECUTOR_ID=params.executorId;if(params.agentId)env.GENIE_AGENT_ID=params.agentId;if(params.role)env.GENIE_AGENT_NAME=params.role;else if(params.name)env.GENIE_AGENT_NAME=params.name;if(params.team)env.GENIE_TEAM=params.team;let{forwarded:extraArgs,promptFiles:extraPromptFiles}=splitCodexExtraArgs(params.extraArgs);parts.push("--yolo"),parts.push("--no-alt-screen");let codexModel=sanitizeModelForProvider("codex",params.model);if(codexModel)parts.push("--model",escapeShellArg2(codexModel));for(let arg of extraArgs)parts.push(escapeShellArg2(arg));let mergedPrompt=buildCodexMergedPrompt(params,extraPromptFiles);if(mergedPrompt!==null){let promptFile=writeCodexPromptFile(params,mergedPrompt);parts.push(`"$(cat ${escapeShellArg2(promptFile)})"`)}else parts.push(escapeShellArg2(buildCodexAutoPrompt(params)));return{command:parts.join(" "),provider:"codex",env:Object.keys(env).length>0?env:void 0,meta:{role:params.role,skill:params.skill}}}function buildLaunchCommand(params){let validated=validateSpawnParams(params);switch(validated.provider){case"claude":return buildClaudeCommand(validated);case"codex":return buildCodexCommand(validated);case"claude-sdk":return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:validated.role,skill:validated.skill}};default:throw Error(`Unknown provider "${validated.provider}". Valid providers: claude, codex, claude-sdk`)}}var CLAUDE_TEAM_COLORS,spawnParamsSchema,CODEX_PROMPT_DIR="/tmp/genie-codex-prompts",CODEX_PROMPT_FILE_FLAGS;var init_provider_adapters=__esm(()=>{init_zod();init_inject();init_builtin_agents();init_provider_models();init_trace_context();CLAUDE_TEAM_COLORS=["red","blue","green","yellow","purple","orange","pink","cyan"],spawnParamsSchema=exports_external.object({provider:exports_external.enum(["claude","codex","claude-sdk","app-pty"]),team:exports_external.string().min(1,"Team name is required"),role:exports_external.string().optional(),agentTemplate:exports_external.string().optional(),skill:exports_external.string().optional(),agentId:exports_external.string().optional(),executorId:exports_external.string().uuid().optional(),extraArgs:exports_external.array(exports_external.string()).optional(),nativeTeam:exports_external.object({enabled:exports_external.boolean(),parentSessionId:exports_external.string().optional(),color:exports_external.string().optional(),agentType:exports_external.string().optional(),planModeRequired:exports_external.boolean().optional(),permissionMode:exports_external.string().optional(),agentName:exports_external.string().optional()}).optional(),sessionId:exports_external.string().uuid().optional(),resume:exports_external.string().optional(),systemPromptFile:exports_external.string().optional(),systemPrompt:exports_external.string().optional(),promptMode:exports_external.enum(["system","append"]).optional(),model:exports_external.string().optional(),initialPrompt:exports_external.string().optional(),name:exports_external.string().optional(),permissions:exports_external.object({allow:exports_external.array(exports_external.string()).optional(),deny:exports_external.array(exports_external.string()).optional()}).optional(),disallowedTools:exports_external.array(exports_external.string()).optional(),otelPort:exports_external.number().optional(),otelLogPrompts:exports_external.boolean().optional(),otelWishSlug:exports_external.string().optional(),newWindow:exports_external.boolean().optional(),windowTarget:exports_external.string().optional()});CODEX_PROMPT_FILE_FLAGS=new Set(["--append-system-prompt-file","--system-prompt-file"])});var exports_audit={};__export(exports_audit,{recordAuditEvent:()=>recordAuditEvent,queryToolUsage:()=>queryToolUsage,queryTimeline:()=>queryTimeline,querySummary:()=>querySummary,queryErrorPatterns:()=>queryErrorPatterns,queryCostBreakdown:()=>queryCostBreakdown,queryAuditEvents:()=>queryAuditEvents,getActor:()=>getActor,generateTraceId:()=>generateTraceId,followAuditEvents:()=>followAuditEvents});async function recordAuditEvent(entityType,entityId,eventType,actor,details){try{if(!await isAvailable())return;let sql=await getConnection();await sql`
183
+ `):null}function writeCodexPromptFile(params,content){let{mkdirSync:mkdirSync5,writeFileSync:writeFileSync6}=__require("fs"),{join:join10}=__require("path");mkdirSync5(CODEX_PROMPT_DIR,{recursive:!0});let stem=sanitizeCodexPromptFileStem(params.executorId??Date.now().toString(36)),promptFile=join10(CODEX_PROMPT_DIR,`${stem}.txt`);return writeFileSync6(promptFile,content,"utf-8"),promptFile}function buildCodexCommand(params){preflightCheck("codex");let parts=["codex"],env={};if(params.executorId)env.GENIE_EXECUTOR_ID=params.executorId;if(params.agentId)env.GENIE_AGENT_ID=params.agentId;if(params.role)env.GENIE_AGENT_NAME=params.role;else if(params.name)env.GENIE_AGENT_NAME=params.name;if(params.team)env.GENIE_TEAM=params.team;let{forwarded:extraArgs,promptFiles:extraPromptFiles}=splitCodexExtraArgs(params.extraArgs);parts.push("--yolo"),parts.push("--no-alt-screen");let codexModel=sanitizeModelForProvider("codex",params.model);if(codexModel)parts.push("--model",escapeShellArg2(codexModel));for(let arg of extraArgs)parts.push(escapeShellArg2(arg));let mergedPrompt=buildCodexMergedPrompt(params,extraPromptFiles);if(mergedPrompt!==null){let promptFile=writeCodexPromptFile(params,mergedPrompt);parts.push(`"$(cat ${escapeShellArg2(promptFile)})"`)}else parts.push(escapeShellArg2(buildCodexAutoPrompt(params)));return{command:parts.join(" "),provider:"codex",env:Object.keys(env).length>0?env:void 0,meta:{role:params.role,skill:params.skill}}}function buildLaunchCommand(params){let validated=validateSpawnParams(params);switch(validated.provider){case"claude":return buildClaudeCommand(validated);case"codex":return buildCodexCommand(validated);case"claude-sdk":return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:validated.role,skill:validated.skill}};default:throw Error(`Unknown provider "${validated.provider}". Valid providers: claude, codex, claude-sdk`)}}var CLAUDE_TEAM_COLORS,spawnParamsSchema,CODEX_PROMPT_DIR="/tmp/genie-codex-prompts",CODEX_PROMPT_FILE_FLAGS;var init_provider_adapters=__esm(()=>{init_zod();init_inject();init_builtin_agents();init_provider_models();init_trace_context();CLAUDE_TEAM_COLORS=["red","blue","green","yellow","purple","orange","pink","cyan"],spawnParamsSchema=exports_external.object({provider:exports_external.enum(["claude","codex","claude-sdk","app-pty"]),team:exports_external.string().min(1,"Team name is required"),role:exports_external.string().optional(),agentTemplate:exports_external.string().optional(),skill:exports_external.string().optional(),agentId:exports_external.string().optional(),executorId:exports_external.string().uuid().optional(),extraArgs:exports_external.array(exports_external.string()).optional(),nativeTeam:exports_external.object({enabled:exports_external.boolean(),parentSessionId:exports_external.string().optional(),color:exports_external.string().optional(),agentType:exports_external.string().optional(),planModeRequired:exports_external.boolean().optional(),permissionMode:exports_external.string().optional(),agentName:exports_external.string().optional()}).optional(),sessionId:exports_external.string().uuid().optional(),resume:exports_external.string().optional(),systemPromptFile:exports_external.string().optional(),systemPrompt:exports_external.string().optional(),promptMode:exports_external.enum(["system","append"]).optional(),model:exports_external.string().optional(),initialPrompt:exports_external.string().optional(),name:exports_external.string().optional(),permissions:exports_external.object({allow:exports_external.array(exports_external.string()).optional(),deny:exports_external.array(exports_external.string()).optional()}).optional(),disallowedTools:exports_external.array(exports_external.string()).optional(),otelPort:exports_external.number().optional(),otelLogPrompts:exports_external.boolean().optional(),otelWishSlug:exports_external.string().optional(),newWindow:exports_external.boolean().optional(),windowTarget:exports_external.string().optional()});CODEX_PROMPT_FILE_FLAGS=new Set(["--append-system-prompt-file","--system-prompt-file"])});var exports_audit={};__export(exports_audit,{recordAuditEvent:()=>recordAuditEvent,queryToolUsageDetail:()=>queryToolUsageDetail,queryToolUsage:()=>queryToolUsage,queryTimeline:()=>queryTimeline,querySummary:()=>querySummary,queryErrorPatterns:()=>queryErrorPatterns,queryCostBreakdown:()=>queryCostBreakdown,queryAuditEvents:()=>queryAuditEvents,getActor:()=>getActor,generateTraceId:()=>generateTraceId,followAuditEvents:()=>followAuditEvents});async function recordAuditEvent(entityType,entityId,eventType,actor,details){try{if(!await isAvailable())return;let sql=await getConnection();await sql`
184
184
  INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
185
185
  VALUES (${entityType}, ${entityId}, ${eventType}, ${actor??null}, ${sql.json(details??{})})
186
186
  `}catch{}}function parseSince(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function queryAuditEvents(options={}){let sql=await getConnection(),conditions=[],values=[],paramIdx=1;if(options.type)conditions.push(`(event_type = $${paramIdx} OR entity_type = $${paramIdx})`),paramIdx++,values.push(options.type);if(options.entity)conditions.push(`(entity_type = $${paramIdx} OR entity_id = $${paramIdx})`),paramIdx++,values.push(options.entity);if(options.since)conditions.push(`created_at >= $${paramIdx++}::timestamptz`),values.push(parseSince(options.since));if(options.errorsOnly)conditions.push("event_type LIKE '%error%' OR (details::text LIKE '%error%')");let where=conditions.length>0?`WHERE ${conditions.join(" AND ")}`:"",limit=options.limit??50;return await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
@@ -251,7 +251,24 @@ ${readFileSync6(params.extraArgs[fileIdx+1],"utf-8")}`,params.extraArgs.splice(f
251
251
  AND created_at >= $1::timestamptz
252
252
  GROUP BY ${groupExpr}
253
253
  ORDER BY total_calls DESC
254
- LIMIT 100`,[sinceTs])}async function queryTimeline(entityId,options={}){let sql=await getConnection(),limit=Math.max(1,Math.min(MAX_TIMELINE_LIMIT,options.limit??DEFAULT_TIMELINE_LIMIT)),sinceMs=options.sinceMs===void 0?DEFAULT_TIMELINE_SINCE_MS:options.sinceMs;if(sinceMs===null)return await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
254
+ LIMIT 100`,[sinceTs])}async function queryToolUsageDetail(since,opts={}){let sql=await getConnection(),sinceTs=parseSince(since),limit=Math.max(1,Math.min(1e4,opts.limit??500)),toolFilter=opts.tool?"AND COALESCE(details->>'tool_name', entity_id) = $2::text":"",agentFilter=opts.agent?`AND COALESCE(actor, 'unknown') = $${opts.tool?3:2}::text`:"",params=[sinceTs];if(opts.tool)params.push(opts.tool);if(opts.agent)params.push(opts.agent);return(await sql.unsafe(`SELECT
255
+ created_at,
256
+ entity_id,
257
+ event_type,
258
+ actor,
259
+ COALESCE(details->>'tool_name', entity_id) AS tool_name,
260
+ details->'tool_input' AS tool_input,
261
+ details->'tool_parameters' AS tool_parameters,
262
+ NULLIF(details->>'duration_ms','')::numeric AS duration_ms,
263
+ details->>'error' AS error,
264
+ details
265
+ FROM audit_events
266
+ WHERE entity_type = 'otel_tool'
267
+ AND created_at >= $1::timestamptz
268
+ ${toolFilter}
269
+ ${agentFilter}
270
+ ORDER BY created_at DESC
271
+ LIMIT ${limit}`,params)).map((r)=>({...r,details:typeof r.details==="string"?JSON.parse(r.details):r.details}))}async function queryTimeline(entityId,options={}){let sql=await getConnection(),limit=Math.max(1,Math.min(MAX_TIMELINE_LIMIT,options.limit??DEFAULT_TIMELINE_LIMIT)),sinceMs=options.sinceMs===void 0?DEFAULT_TIMELINE_SINCE_MS:options.sinceMs;if(sinceMs===null)return await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
255
272
  FROM audit_events
256
273
  WHERE (entity_id = $1
257
274
  OR actor = $1
@@ -4049,8 +4066,9 @@ Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);i
4049
4066
  \u26A0 OTel-derived events only cover genie-spawned sessions.`),opts.empty){let port=null;try{port=getOtelPort()}catch{port=null}let endpoint=port?`http://127.0.0.1:${port}`:"http://127.0.0.1:<otel-port>";console.log(" If you expected user-session activity, export the OTel vars in your shell rc:"),console.log(` export OTEL_EXPORTER_OTLP_ENDPOINT=${endpoint}`),console.log(" export CLAUDE_CODE_ENABLE_TELEMETRY=1"),console.log(" Then restart your Claude Code session.")}else console.log(" User-initiated Claude Code sessions are not captured unless they export OTLP.")}function isOtelTypeFilter(type2){return typeof type2==="string"&&type2.startsWith("otel_")}function isOtelKindFilter(kind){return typeof kind==="string"&&kind.startsWith("tool")}function printErrorsTable(patterns2){if(patterns2.length===0){console.log("No error patterns found.");return}let headers=["Count","Event","Command","Error","Last Seen"],data=patterns2.map((p)=>[String(p.count),p.event_type,p.entity_id,p.error_message.slice(0,50),formatRelativeTimestamp(p.last_seen)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(50,Math.max(h.length,...colVals.map((v)=>v.length)))}),header=headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data){let line=row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | ");console.log(line)}console.log(`
4050
4067
  (${patterns2.length} pattern${patterns2.length===1?"":"s"})`)}function printV2EventsTable(rows){if(rows.length===0){console.log("No events found.");return}let headers=["Time","Subject","Agent","TraceId","SpanId","Severity","Duration"],data=rows.map((r)=>[formatRelativeTimestamp(r.created_at),r.subject??r.text??"-",r.agent,r.trace_id?r.trace_id.slice(0,8):"-",r.span_id?r.span_id.slice(0,8):"-",r.severity??"-",r.duration_ms!=null?`${r.duration_ms}ms`:"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))}),header=headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ");console.log(header),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data){let line=row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | ");console.log(line)}console.log(`
4051
4068
  (${rows.length} event${rows.length===1?"":"s"})`)}async function eventsListV2Command(options){try{let limit=options.limit?Number.parseInt(options.limit,10):50,rows=await queryV2Batch({kindPrefix:options.kind,severity:options.severity,since:options.since??"1h",limit});if(options.json)console.log(JSON.stringify(rows,null,2));else if(printV2EventsTable(rows),isOtelKindFilter(options.kind))printOtelScopeWarning({empty:rows.length===0})}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying v2 events: ${msg}`),process.exit(1)}}async function eventsListCommand(options){if(options.enriched||options.v2){if(options.v2&&!options.enriched)console.error("\u26A0\uFE0F `events list --v2` is deprecated; use `events list --enriched`.");return eventsListV2Command(options)}try{let queryOpts={type:options.type,entity:options.entity,since:options.since??"1h",errorsOnly:options.errorsOnly,limit:options.limit?Number.parseInt(options.limit,10):50};if(options.follow){let{followAuditEvents:followAuditEvents2}=await Promise.resolve().then(() => (init_audit(),exports_audit));console.log("Following audit events (Ctrl+C to stop)...");let handle=await followAuditEvents2(queryOpts,(row)=>{if(options.json)console.log(JSON.stringify(row));else{let time=formatRelativeTimestamp(row.created_at),entity=`${row.entity_type}:${row.entity_id}`.slice(0,40),event=row.event_type.padEnd(24),details=summarizeDetails(row.details).slice(0,60);console.log(`${time} ${event} ${entity} ${details}`)}}),shutdown3=()=>{handle.stop(),process.exit(0)};process.on("SIGINT",shutdown3),process.on("SIGTERM",shutdown3),await new Promise(()=>{});return}let rows=await queryAuditEvents(queryOpts);if(options.json)console.log(JSON.stringify(rows,null,2));else if(printEventsTable(rows),isOtelTypeFilter(options.type))printOtelScopeWarning({empty:rows.length===0})}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying events: ${msg}`),process.exit(1)}}var DEFAULT_HIDDEN_EVENT_TYPES=new Set(["command_success","sdk.hook.started"]);async function eventsStreamCommand(options){let{followAuditEvents:followAuditEvents2}=await Promise.resolve().then(() => (init_audit(),exports_audit)),{followRuntimeEvents:followRuntimeEvents2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events)),{color:color2}=await Promise.resolve().then(() => (init_term_format(),exports_term_format)),{renderAuditEvent:renderAuditEvent2,renderRuntimeEvent:renderRuntimeEvent2,formatEventLine:formatEventLine2}=await Promise.resolve().then(() => (init_event_renderer(),exports_event_renderer)),handles2=[],clockTime=(iso)=>{return(iso instanceof Date?iso:new Date(iso)).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})};if(!options.json){let sources=options.auditOnly?"audit":options.runtimeOnly?"runtime":"audit + runtime";console.log(color2("dim",`Streaming ${sources} events (Ctrl+C to stop)...`))}if(!options.runtimeOnly){let auditHandle=await followAuditEvents2({type:options.type,entity:options.entity,errorsOnly:options.errorsOnly},(row)=>{if(!options.all&&!options.type&&DEFAULT_HIDDEN_EVENT_TYPES.has(row.event_type))return;if(options.json){console.log(JSON.stringify({stream:"audit",...row}));return}console.log(formatEventLine2(clockTime(row.created_at),renderAuditEvent2({entity_type:row.entity_type,entity_id:row.entity_id,event_type:row.event_type,details:row.details})))});handles2.push(auditHandle)}if(!options.auditOnly){let validKinds=["user","assistant","message","state","tool_call","tool_result","system","qa"],kinds=options.kind&&validKinds.includes(options.kind)?[options.kind]:void 0,agentIds=options.agent?[options.agent]:void 0,runtimeHandle=await followRuntimeEvents2({kinds,agentIds,scopeMode:"any"},(event)=>{if(options.json){console.log(JSON.stringify({stream:"runtime",...event}));return}console.log(formatEventLine2(clockTime(event.timestamp),renderRuntimeEvent2({kind:event.kind,agent:event.agent,team:event.team,text:event.text})))},{pollIntervalMs:2000});handles2.push(runtimeHandle)}let shutdown3=async()=>{for(let h of handles2)await h.stop();process.exit(0)};process.on("SIGINT",shutdown3),process.on("SIGTERM",shutdown3),await new Promise(()=>{})}async function eventsErrorsCommand(options){try{let patterns2=await queryErrorPatterns(options.since);if(options.json)console.log(JSON.stringify(patterns2,null,2));else printErrorsTable(patterns2)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying error patterns: ${msg}`),process.exit(1)}}function resolveCostsGroupBy(options){if(options.byWish)return"wish";if(options.byModel)return"model";return"agent"}function printCostsTable(rows,groupBy){if(rows.length===0){console.log("No cost data found.");return}let headers=[groupBy==="agent"?"Agent":groupBy==="wish"?"Wish":"Model","Total Cost","Requests","Avg Cost"],data=rows.map((r)=>[r.group_key,`$${r.total_cost.toFixed(4)}`,String(r.request_count),`$${r.avg_cost.toFixed(4)}`]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCost=rows.reduce((sum,r)=>sum+r.total_cost,0),totalReqs=rows.reduce((sum,r)=>sum+r.request_count,0);console.log(`
4052
- Total: $${totalCost.toFixed(4)} across ${totalReqs} requests`)}async function eventsCostsCommand(options){try{let since=options.today?"24h":options.since??"24h",groupBy=resolveCostsGroupBy(options),rows=await queryCostBreakdown(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else if(printCostsTable(rows,groupBy),printOtelScopeWarning({empty:rows.length===0}),rows.length>0)console.log(" For full server costs: npx ccusage monthly")}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying costs: ${msg}`),process.exit(1)}}function printToolsTable(rows,groupBy){if(rows.length===0){console.log("No tool usage data found.");return}let headers=[groupBy==="tool"?"Tool":"Agent","Calls","Success","Errors","Avg Duration"],data=rows.map((r)=>[r.group_key,String(r.total_calls),String(r.success_count),String(r.error_count),r.avg_duration_ms!=null?`${r.avg_duration_ms.toFixed(0)}ms`:"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCalls=rows.reduce((sum,r)=>sum+r.total_calls,0);console.log(`
4053
- (${totalCalls} total tool calls)`)}async function eventsToolsCommand(options){try{let since=options.since??"24h",groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy),printOtelScopeWarning({empty:rows.length===0})}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}function parseTimelineSince(raw){let trimmed=raw.trim().toLowerCase();if(trimmed==="all")return null;let match=/^(\d+)\s*(s|m|h|d)$/.exec(trimmed);if(!match)throw Error(`invalid --since value "${raw}". Use e.g. 10m, 24h, 7d, or 'all' for full history.`);let n=Number.parseInt(match[1],10),unit=match[2];return n*{s:1000,m:60000,h:3600000,d:86400000}[unit]}async function eventsTimelineCommand(entityId,options){try{let limit=options.limit?Math.max(1,Number.parseInt(options.limit,10)):void 0;if(options.limit!==void 0&&Number.isNaN(Number(options.limit)))console.error(`Error: invalid --limit value "${options.limit}" (must be a positive integer)`),process.exit(1);let sinceMs=options.since!==void 0?parseTimelineSince(options.since):void 0,rows=await queryTimeline(entityId,{limit,sinceMs});if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else{printSummary(summary);let allOtelEmpty=summary.tool_calls===0&&summary.api_requests===0&&summary.total_cost===0;printOtelScopeWarning({empty:allOtelEmpty})}}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync7}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result2=spawnSync7("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result2.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result2.stdout)process.stdout.write(result2.stdout);if(result2.status!==0)process.exit(result2.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG. OTel-derived data (tools/summary/costs and otel_* list rows) is scoped to genie-spawned agents \u2014 user-initiated Claude Code sessions are not captured unless they export OTLP env vars.");events.command("list",{isDefault:!0}).description("List recent audit events (add --enriched for the genie_runtime_events surface)").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").option("-f, --follow","Follow mode \u2014 real-time streaming (alias: genie events stream)").option("--enriched","Use enriched genie_runtime_events surface with TraceId/SpanId/Severity/Duration columns").option("--v2","[DEPRECATED] alias for --enriched (removed next release)").option("--kind <prefix>","Filter enriched rows by kind/subject prefix (e.g., mailbox, agent.lifecycle)").option("--severity <level>","Filter enriched rows by severity (debug|info|warn|error|fatal)").action(async(options)=>{await eventsListCommand(options)}),events.command("timeline-v2 <trace-id>").description("Render causal tree for a trace_id from genie_runtime_events (v2 enriched surface)").option("--json","Output as JSON").action(async(traceId,options)=>{await timelineCommand(traceId,options)}),events.command("stream-follow").description("Follow-stream enriched genie_runtime_events via LISTEN/NOTIFY + id-cursor (v2)").option("--follow","Continuously follow the stream",!0).option("--kind <prefix>","Filter by subject/kind prefix (supports `*` globs, e.g. `detector.*`)").option("--severity <level>","Filter by severity (debug|info|warn|error|fatal)").option("--since <duration>","Seed window (e.g., 5m, 1h)").option("--consumer-id <id>","Persistent consumer id for cursor resume").option("--json","Output as NDJSON").action(async(options)=>{await streamCommand({...options,follow:!0})}),events.command("migrate").description("Backfill legacy audit_events rows into genie_runtime_events (one-shot)").option("--audit","Migrate audit_events \u2192 genie_runtime_events with sentinel source tag").option("--dry-run","Report row deltas without writing").option("--since <duration>","Only migrate rows created within this window").option("--limit <n>","Cap the number of rows migrated per run",(v)=>Number.parseInt(v,10)).option("--json","Output summary as JSON").action(async(options)=>{await migrateCommand(options)}),events.command("stream").description("Stream audit + runtime events in real-time (tail -f style)").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--errors-only","Show only error events").option("--kind <kind>","Filter runtime events by kind (tool_call, message, prompt, etc)").option("--agent <agent>","Filter runtime events by agent").option("--audit-only","Stream only audit_events (skip runtime)").option("--runtime-only","Stream only runtime events (skip audit)").option("--all","Show all events including noisy ones (command_success, etc)").option("--json","Output as JSON").action(async(options)=>{await eventsStreamCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Event timeline for a task, agent, wish, traceId, or session_id (default: last 24h)").option("--json","Output as JSON").option("--limit <n>","Max rows to return (default 200, hard cap 2000)").option("--since <duration>",'Time window \u2014 e.g. 10m, 24h, 7d, or "all" for full history. Default: 24h. '+'"all" reverts to the unbounded scan and may time out on production-sized audit logs.').action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)}),events.command("subscribe").description("Mint a signed subscription token for genie events stream --follow").requiredOption("--role <role>","RBAC role: events:admin|events:operator|events:subscriber|events:audit").option("--types <csv>","Comma-separated allowed event types (subset of role defaults)").option("--channels <csv>","Comma-separated allowed LISTEN channels (subset of role defaults)").option("--ttl <duration>","Token time-to-live (e.g., 30m, 1h, 24h). Defaults to 1h.").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--subscriber-id <id>","Stable id for the subscriber agent").option("--json","Output as JSON").action(async(options)=>{await subscribeCommand(options)});let admin=events.command("admin").description("Incident-response admin commands (sentinel H6 audited)");admin.command("revoke-subscriber").description("Add a subscription token to the revocation list").requiredOption("--token-id <id>","Token id from `genie events subscribe` output").option("--subscriber-id <id>","Subscriber id associated with the token").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--reason <text>","IR justification for revocation").option("--json","Output as JSON").action(async(options)=>{await revokeSubscriberCommand(options)}),admin.command("rotate-redaction-keys").description("Rotate redaction + audit HMAC keys, preserving prior versions for lookup").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--new-key <material>","Explicit key material (default: 32 bytes hex from /dev/urandom)").option("--target <scope>","redaction|audit|both (default: both)").option("--json","Output as JSON").action(async(options)=>{await rotateRedactionKeysCommand(options)}),admin.command("un-hash").description("Admin reverse-lookup for a Tier-A hash (emits audit.un_hash)").requiredOption("--namespace <ns>","Hash namespace (e.g., agent, actor, session)").requiredOption("--hashed-value <hash>","Tier-A hash tag to reverse").option("--candidates <csv>","Comma-separated candidate plaintexts to brute-force").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--reason <text>","IR justification (appears in audit.un_hash)").option("--ticket <ref>","Incident ticket reference").option("--json","Output as JSON").action(async(options)=>{await unHashCommand(options)}),admin.command("export-audit").description("Produce a signed audit-chain bundle (emits audit.export)").option("--signed","Require GENIE_AUDIT_EXPORT_SECRET for HMAC signing",!0).option("--since <duration>","Advisory time window (authoritative cursor is --since-id)").option("--since-id <n>","Authoritative id cursor to resume from").option("--limit <n>","Max rows to include").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--output <path>","Write bundle to file; omit to print to stdout").option("--reason <text>","IR justification (appears in audit.export)").option("--json","Output as JSON").action(async(options)=>{await exportAuditCommand(options)}),admin.command("verify-chain").description("Quick chain-integrity check (no export)").option("--since-id <n>","Start id","0").option("--limit <n>","Max rows to verify").option("--json","Output as JSON").action(async(options)=>{await verifyChainCommand(options)}),admin.command("list-revocations").description("List revoked token ids for a tenant").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--json","Output as JSON").action(async(options)=>{await listRevocationsCommand(options)})}init_genie_tokens();init_term_format();import{execSync as execSync11}from"child_process";import{existsSync as existsSync47,mkdirSync as mkdirSync20,readFileSync as readFileSync29,writeFileSync as writeFileSync20}from"fs";import{dirname as dirname14,join as join58}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService3()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate2(b2.name,22),24)} ${padRight(truncate2(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
4069
+ Total: $${totalCost.toFixed(4)} across ${totalReqs} requests`)}async function eventsCostsCommand(options){try{let since=options.today?"24h":options.since??"24h",groupBy=resolveCostsGroupBy(options),rows=await queryCostBreakdown(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else if(printCostsTable(rows,groupBy),printOtelScopeWarning({empty:rows.length===0}),rows.length>0)console.log(" For full server costs: npx ccusage monthly")}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying costs: ${msg}`),process.exit(1)}}function printToolsDetailTable(rows){if(rows.length===0){console.log("No tool invocations found.");return}let headers=["Time","Tool","Agent","Duration","Input"],data=rows.map((r)=>[formatRelativeTimestamp(typeof r.created_at==="string"?r.created_at:r.created_at.toISOString()),r.tool_name??r.entity_id,r.actor??"-",r.duration_ms!=null?`${Number(r.duration_ms).toFixed(0)}ms`:"-",previewJson(r.tool_input??r.tool_parameters)]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(60,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));console.log(`
4070
+ (${rows.length} row${rows.length===1?"":"s"})`)}function previewJson(value){if(value==null)return"-";try{return(typeof value==="string"?value:JSON.stringify(value)).replace(/\s+/g," ").slice(0,60)}catch{return"-"}}function printToolsTable(rows,groupBy){if(rows.length===0){console.log("No tool usage data found.");return}let headers=[groupBy==="tool"?"Tool":"Agent","Calls","Success","Errors","Avg Duration"],data=rows.map((r)=>[r.group_key,String(r.total_calls),String(r.success_count),String(r.error_count),r.avg_duration_ms!=null?`${r.avg_duration_ms.toFixed(0)}ms`:"-"]),widths=headers.map((h,i2)=>{let colVals=data.map((row)=>row[i2]);return Math.min(40,Math.max(h.length,...colVals.map((v)=>v.length)))});console.log(headers.map((h,i2)=>padRight(h,widths[i2])).join(" | ")),console.log(widths.map((w)=>"-".repeat(w)).join("-+-"));for(let row of data)console.log(row.map((v,i2)=>padRight(v.slice(0,widths[i2]),widths[i2])).join(" | "));let totalCalls=rows.reduce((sum,r)=>sum+r.total_calls,0);console.log(`
4071
+ (${totalCalls} total tool calls)`)}async function eventsToolsDetailCommand(since,options){let limit=options.limit?Number.parseInt(options.limit,10):void 0;if(options.limit&&(!Number.isFinite(limit)||limit<=0))console.error(`Invalid --limit value: ${options.limit}`),process.exit(1);let rows=await queryToolUsageDetail(since,{limit,tool:options.tool,agent:options.agent});if(options.json){console.log(JSON.stringify(rows,null,2));return}printToolsDetailTable(rows),printOtelScopeWarning({empty:rows.length===0})}async function eventsToolsCommand(options){try{let since=options.since??"24h";if(options.detail){await eventsToolsDetailCommand(since,options);return}let groupBy=options.byAgent?"agent":"tool",rows=await queryToolUsage(since,groupBy);if(options.json)console.log(JSON.stringify(rows,null,2));else printToolsTable(rows,groupBy),printOtelScopeWarning({empty:rows.length===0})}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying tool usage: ${msg}`),process.exit(1)}}function parseTimelineSince(raw){let trimmed=raw.trim().toLowerCase();if(trimmed==="all")return null;let match=/^(\d+)\s*(s|m|h|d)$/.exec(trimmed);if(!match)throw Error(`invalid --since value "${raw}". Use e.g. 10m, 24h, 7d, or 'all' for full history.`);let n=Number.parseInt(match[1],10),unit=match[2];return n*{s:1000,m:60000,h:3600000,d:86400000}[unit]}async function eventsTimelineCommand(entityId,options){try{let limit=options.limit?Math.max(1,Number.parseInt(options.limit,10)):void 0;if(options.limit!==void 0&&Number.isNaN(Number(options.limit)))console.error(`Error: invalid --limit value "${options.limit}" (must be a positive integer)`),process.exit(1);let sinceMs=options.since!==void 0?parseTimelineSince(options.since):void 0,rows=await queryTimeline(entityId,{limit,sinceMs});if(options.json)console.log(JSON.stringify(rows,null,2));else printEventsTable(rows)}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying timeline: ${msg}`),process.exit(1)}}function printSummary(summary){console.log("Event Summary"),console.log("============="),console.log(`Total events: ${summary.total_events}`),console.log(`Agents spawned: ${summary.agents_spawned}`),console.log(`Tasks moved: ${summary.tasks_moved}`),console.log(`API requests: ${summary.api_requests}`),console.log(`Tool calls: ${summary.tool_calls}`),console.log(`Total cost: $${summary.total_cost.toFixed(4)}`),console.log(`Errors: ${summary.error_count}`)}async function eventsSummaryCommand(options){try{let since=options.today?"24h":options.since??"24h",summary=await querySummary(since);if(options.json)console.log(JSON.stringify(summary,null,2));else{printSummary(summary);let allOtelEmpty=summary.tool_calls===0&&summary.api_requests===0&&summary.total_cost===0;printOtelScopeWarning({empty:allOtelEmpty})}}catch(err){let msg=err instanceof Error?err.message:String(err);console.error(`Error querying summary: ${msg}`),process.exit(1)}}async function eventsScanCommand(options){let{spawnSync:spawnSync7}=__require("child_process"),args=["ccusage","monthly"];if(options.since)args.push("--since",options.since);if(options.json)args.push("--json");if(options.breakdown)args.push("--breakdown");let result2=spawnSync7("npx",args,{stdio:options.json?"pipe":"inherit",timeout:30000,env:{...process.env,NODE_NO_WARNINGS:"1"}});if(result2.error)console.error("ccusage not available. Install with: npm install -g ccusage"),console.error("Or run directly: npx ccusage monthly"),process.exit(1);if(options.json&&result2.stdout)process.stdout.write(result2.stdout);if(result2.status!==0)process.exit(result2.status??1)}function registerEventsCommands(program2){let events=program2.command("events").description("Audit event log from PG. OTel-derived data (tools/summary/costs and otel_* list rows) is scoped to genie-spawned agents \u2014 user-initiated Claude Code sessions are not captured unless they export OTLP env vars.");events.command("list",{isDefault:!0}).description("List recent audit events (add --enriched for the genie_runtime_events surface)").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--since <duration>","Time window (e.g., 1h, 30m, 2d)","1h").option("--errors-only","Show only error events").option("--limit <n>","Max rows to return","50").option("--json","Output as JSON").option("-f, --follow","Follow mode \u2014 real-time streaming (alias: genie events stream)").option("--enriched","Use enriched genie_runtime_events surface with TraceId/SpanId/Severity/Duration columns").option("--v2","[DEPRECATED] alias for --enriched (removed next release)").option("--kind <prefix>","Filter enriched rows by kind/subject prefix (e.g., mailbox, agent.lifecycle)").option("--severity <level>","Filter enriched rows by severity (debug|info|warn|error|fatal)").action(async(options)=>{await eventsListCommand(options)}),events.command("timeline-v2 <trace-id>").description("Render causal tree for a trace_id from genie_runtime_events (v2 enriched surface)").option("--json","Output as JSON").action(async(traceId,options)=>{await timelineCommand(traceId,options)}),events.command("stream-follow").description("Follow-stream enriched genie_runtime_events via LISTEN/NOTIFY + id-cursor (v2)").option("--follow","Continuously follow the stream",!0).option("--kind <prefix>","Filter by subject/kind prefix (supports `*` globs, e.g. `detector.*`)").option("--severity <level>","Filter by severity (debug|info|warn|error|fatal)").option("--since <duration>","Seed window (e.g., 5m, 1h)").option("--consumer-id <id>","Persistent consumer id for cursor resume").option("--json","Output as NDJSON").action(async(options)=>{await streamCommand({...options,follow:!0})}),events.command("migrate").description("Backfill legacy audit_events rows into genie_runtime_events (one-shot)").option("--audit","Migrate audit_events \u2192 genie_runtime_events with sentinel source tag").option("--dry-run","Report row deltas without writing").option("--since <duration>","Only migrate rows created within this window").option("--limit <n>","Cap the number of rows migrated per run",(v)=>Number.parseInt(v,10)).option("--json","Output summary as JSON").action(async(options)=>{await migrateCommand(options)}),events.command("stream").description("Stream audit + runtime events in real-time (tail -f style)").option("--type <type>","Filter by event_type").option("--entity <entity>","Filter by entity_type or entity_id").option("--errors-only","Show only error events").option("--kind <kind>","Filter runtime events by kind (tool_call, message, prompt, etc)").option("--agent <agent>","Filter runtime events by agent").option("--audit-only","Stream only audit_events (skip runtime)").option("--runtime-only","Stream only runtime events (skip audit)").option("--all","Show all events including noisy ones (command_success, etc)").option("--json","Output as JSON").action(async(options)=>{await eventsStreamCommand(options)}),events.command("errors").description("Show aggregated error patterns").option("--since <duration>","Time window (e.g., 1h, 24h, 7d)").option("--json","Output as JSON").action(async(options)=>{await eventsErrorsCommand(options)}),events.command("costs").description("Cost breakdown from OTel API request events").option("--today","Show costs from the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-agent","Group by agent").option("--by-wish","Group by wish").option("--by-model","Group by model").option("--json","Output as JSON").action(async(options)=>{await eventsCostsCommand(options)}),events.command("tools").description("Tool usage analytics from OTel tool events").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--by-tool","Group by tool name (default)").option("--by-agent","Group by agent").option("--detail","Return individual tool invocations (with tool_input / tool_parameters) instead of aggregates").option("--tool <name>","Filter --detail rows by tool name").option("--agent <name>","Filter --detail rows by agent/actor").option("--limit <n>","Max rows for --detail (default 500, max 10000)").option("--json","Output as JSON").action(async(options)=>{await eventsToolsCommand(options)}),events.command("timeline <entity-id>").description("Event timeline for a task, agent, wish, traceId, or session_id (default: last 24h)").option("--json","Output as JSON").option("--limit <n>","Max rows to return (default 200, hard cap 2000)").option("--since <duration>",'Time window \u2014 e.g. 10m, 24h, 7d, or "all" for full history. Default: 24h. '+'"all" reverts to the unbounded scan and may time out on production-sized audit logs.').action(async(entityId,options)=>{await eventsTimelineCommand(entityId,options)}),events.command("summary").description("High-level stats: agents spawned, tasks moved, costs, errors").option("--today","Show summary for the last 24h").option("--since <duration>","Time window (e.g., 1h, 7d)","24h").option("--json","Output as JSON").action(async(options)=>{await eventsSummaryCommand(options)}),events.command("scan").description("Full server cost scan via ccusage (all CC sessions, not just genie-spawned)").option("--since <date>","Start date in YYYYMMDD format").option("--json","Output as JSON").option("--breakdown","Show per-model breakdown").action(async(options)=>{await eventsScanCommand(options)}),events.command("subscribe").description("Mint a signed subscription token for genie events stream --follow").requiredOption("--role <role>","RBAC role: events:admin|events:operator|events:subscriber|events:audit").option("--types <csv>","Comma-separated allowed event types (subset of role defaults)").option("--channels <csv>","Comma-separated allowed LISTEN channels (subset of role defaults)").option("--ttl <duration>","Token time-to-live (e.g., 30m, 1h, 24h). Defaults to 1h.").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--subscriber-id <id>","Stable id for the subscriber agent").option("--json","Output as JSON").action(async(options)=>{await subscribeCommand(options)});let admin=events.command("admin").description("Incident-response admin commands (sentinel H6 audited)");admin.command("revoke-subscriber").description("Add a subscription token to the revocation list").requiredOption("--token-id <id>","Token id from `genie events subscribe` output").option("--subscriber-id <id>","Subscriber id associated with the token").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--reason <text>","IR justification for revocation").option("--json","Output as JSON").action(async(options)=>{await revokeSubscriberCommand(options)}),admin.command("rotate-redaction-keys").description("Rotate redaction + audit HMAC keys, preserving prior versions for lookup").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--new-key <material>","Explicit key material (default: 32 bytes hex from /dev/urandom)").option("--target <scope>","redaction|audit|both (default: both)").option("--json","Output as JSON").action(async(options)=>{await rotateRedactionKeysCommand(options)}),admin.command("un-hash").description("Admin reverse-lookup for a Tier-A hash (emits audit.un_hash)").requiredOption("--namespace <ns>","Hash namespace (e.g., agent, actor, session)").requiredOption("--hashed-value <hash>","Tier-A hash tag to reverse").option("--candidates <csv>","Comma-separated candidate plaintexts to brute-force").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--reason <text>","IR justification (appears in audit.un_hash)").option("--ticket <ref>","Incident ticket reference").option("--json","Output as JSON").action(async(options)=>{await unHashCommand(options)}),admin.command("export-audit").description("Produce a signed audit-chain bundle (emits audit.export)").option("--signed","Require GENIE_AUDIT_EXPORT_SECRET for HMAC signing",!0).option("--since <duration>","Advisory time window (authoritative cursor is --since-id)").option("--since-id <n>","Authoritative id cursor to resume from").option("--limit <n>","Max rows to include").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--output <path>","Write bundle to file; omit to print to stdout").option("--reason <text>","IR justification (appears in audit.export)").option("--json","Output as JSON").action(async(options)=>{await exportAuditCommand(options)}),admin.command("verify-chain").description("Quick chain-integrity check (no export)").option("--since-id <n>","Start id","0").option("--limit <n>","Max rows to verify").option("--json","Output as JSON").action(async(options)=>{await verifyChainCommand(options)}),admin.command("list-revocations").description("List revoked token ids for a tenant").option("--tenant <id>",'Tenant id. Defaults to "default".').option("--json","Output as JSON").action(async(options)=>{await listRevocationsCommand(options)})}init_genie_tokens();init_term_format();import{execSync as execSync11}from"child_process";import{existsSync as existsSync47,mkdirSync as mkdirSync20,readFileSync as readFileSync29,writeFileSync as writeFileSync20}from"fs";import{dirname as dirname14,join as join58}from"path";var _boardService;async function getBoardService(){if(!_boardService)_boardService=await Promise.resolve().then(() => (init_board_service(),exports_board_service));return _boardService}var _taskService3;async function getTaskService3(){if(!_taskService3)_taskService3=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService3}var _templateService;async function getTemplateService(){if(!_templateService)_templateService=await Promise.resolve().then(() => (init_template_service(),exports_template_service));return _templateService}async function resolveProjectId(name){let project=await(await getTaskService3()).getProjectByName(name);if(!project)throw Error(`Project not found: ${name}`);return project.id}async function resolveBoard(name,projectName){let bs=await getBoardService(),projectId;if(projectName)projectId=await resolveProjectId(projectName);let board=await bs.getBoard(name,projectId);if(!board)throw Error(`Board not found: ${name}`);return board}function printBoardTable(boards,projectMap){console.log(` ${padRight("NAME",24)} ${padRight("PROJECT",20)} ${padRight("COLUMNS",10)} CREATED`),console.log(` ${"\u2500".repeat(70)}`);for(let b2 of boards){let projName=b2.projectId?projectMap.get(b2.projectId)??b2.projectId:"-",colCount=String(b2.columns.length),created=formatDate(b2.createdAt);console.log(` ${padRight(truncate2(b2.name,22),24)} ${padRight(truncate2(projName,18),20)} ${padRight(colCount,10)} ${created}`)}console.log(`
4054
4072
  ${boards.length} board${boards.length===1?"":"s"}`)}function printColumnPipeline(columns,header){console.log(`
4055
4073
  ${header}`),console.log("\u2500".repeat(60));let sorted=[...columns].sort((a,b2)=>a.position-b2.position);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],arrow=i2<sorted.length-1?" \u2192":"",gate=` [gate: ${c.gate}]`,action=c.action?` (action: ${c.action})`:"";console.log(` ${i2+1}. ${c.label??c.name}${gate}${action}${arrow}`)}console.log("")}function printTemplateTable(templates){console.log(` ${padRight("NAME",24)} ${padRight("COLUMNS",10)} ${padRight("BUILTIN",10)} DESCRIPTION`),console.log(` ${"\u2500".repeat(80)}`);for(let t of templates){let colCount=String(t.columns.length),builtin=t.isBuiltin?"yes":"no",desc=t.description?truncate2(t.description,30):"-";console.log(` ${padRight(truncate2(t.name,22),24)} ${padRight(colCount,10)} ${padRight(builtin,10)} ${desc}`)}console.log(`
4056
4074
  ${templates.length} template${templates.length===1?"":"s"}`)}async function handleBoardCreate(name,options){let bs=await getBoardService(),tmpl=await getTemplateService(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let columns;if(options.from){let template=await tmpl.getTemplate(options.from);if(!template)throw Error(`Template not found: ${options.from}`);columns=template.columns}else if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({name:colName.trim(),label:colName.trim(),gate:"human",position:i2}));let board=await bs.createBoard({name,projectId,description:options.description,columns});console.log(`Created board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleBoardList(options){let bs=await getBoardService(),ts3=await getTaskService3(),projectId;if(options.project)projectId=await resolveProjectId(options.project);let boards=await bs.listBoards(projectId,options.all);if(options.json){console.log(JSON.stringify(boards,null,2));return}let projects=await ts3.listProjects(),projectMap=new Map;for(let p of projects)projectMap.set(p.id,p.name);printBoardTable(boards,projectMap)}async function handleBoardShow(name,options){let ts3=await getTaskService3(),board=await resolveBoard(name,options.project);if(options.json){console.log(JSON.stringify(board,null,2));return}let projectName="-";if(board.projectId){let proj=(await ts3.listProjects()).find((p)=>p.id===board.projectId);if(proj)projectName=proj.name}if(console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260429.9",
3
+ "version": "4.260429.11",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260429.9",
3
+ "version": "4.260429.11",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260429.9",
3
+ "version": "4.260429.11",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -0,0 +1,63 @@
1
+ -- 057_hook_perf_baseline_view_filter_fix.sql — fix #1494 residual
2
+ --
3
+ -- Background: #1492 (the schema fix) accepts the new `event` key emitted by
4
+ -- runHandler post-#1485, so hook.delivery spans now insert into
5
+ -- genie_runtime_events without rejection. But dog-fooder-11eb's verdict
6
+ -- (state/evidence/followups-1490-1491-1493/REPORT.md) flagged a SECOND
7
+ -- bug: the hook_perf_baseline view created by migration 056 filters
8
+ -- `WHERE kind = 'hook.delivery'`, but the actual emitted row carries:
9
+ --
10
+ -- subject = 'hook.delivery' ← what we want to filter on
11
+ -- kind = 'system' ← what the view incorrectly filters on
12
+ -- data->>'_kind' = 'span' ← span marker
13
+ --
14
+ -- Net effect: rows accepted into the table never appear in the view, so
15
+ -- `genie doctor --perf` and `hook_perf_baseline` queries always returned
16
+ -- empty. The hookify-perf-foundation telemetry value (#1485) stayed inert.
17
+ --
18
+ -- Fix: replace the view to filter on `subject='hook.delivery'` AND require
19
+ -- `data->>'_kind' = 'span'` (so we only count completed spans, not other
20
+ -- runtime events that might happen to share the subject).
21
+
22
+ CREATE OR REPLACE VIEW hook_perf_baseline AS
23
+ WITH spans AS (
24
+ SELECT
25
+ COALESCE(data->>'event', '<unknown>') AS event_name,
26
+ COALESCE(data->>'tool', '<none>') AS tool_name,
27
+ COALESCE(data->>'hook_name', '<unknown>') AS handler_name,
28
+ NULLIF(data->>'duration_ms', '')::numeric AS duration_ms,
29
+ created_at
30
+ FROM genie_runtime_events
31
+ WHERE subject = 'hook.delivery'
32
+ AND data->>'_kind' = 'span'
33
+ AND data ? 'duration_ms'
34
+ )
35
+ SELECT
36
+ event_name,
37
+ tool_name,
38
+ handler_name,
39
+ PERCENTILE_CONT(0.5)
40
+ WITHIN GROUP (ORDER BY duration_ms)
41
+ FILTER (WHERE created_at >= now() - interval '1 hour') AS p50_1h,
42
+ PERCENTILE_CONT(0.99)
43
+ WITHIN GROUP (ORDER BY duration_ms)
44
+ FILTER (WHERE created_at >= now() - interval '1 hour') AS p99_1h,
45
+ PERCENTILE_CONT(0.5)
46
+ WITHIN GROUP (ORDER BY duration_ms)
47
+ FILTER (WHERE created_at >= now() - interval '24 hours') AS p50_24h,
48
+ PERCENTILE_CONT(0.99)
49
+ WITHIN GROUP (ORDER BY duration_ms)
50
+ FILTER (WHERE created_at >= now() - interval '24 hours') AS p99_24h,
51
+ PERCENTILE_CONT(0.5)
52
+ WITHIN GROUP (ORDER BY duration_ms)
53
+ FILTER (WHERE created_at >= now() - interval '7 days') AS p50_7d,
54
+ PERCENTILE_CONT(0.99)
55
+ WITHIN GROUP (ORDER BY duration_ms)
56
+ FILTER (WHERE created_at >= now() - interval '7 days') AS p99_7d,
57
+ COUNT(*) FILTER (WHERE created_at >= now() - interval '24 hours')::bigint AS sample_count_24h
58
+ FROM spans
59
+ WHERE duration_ms IS NOT NULL
60
+ GROUP BY event_name, tool_name, handler_name;
61
+
62
+ COMMENT ON VIEW hook_perf_baseline IS
63
+ 'Rolling P50/P99 per (event, tool, handler) over 1h/24h/7d windows. Source: hook.delivery spans in genie_runtime_events (filtered by subject + data._kind=span). Fixed in #1494 residual / migration 057 — original migration 056 filtered the wrong column.';
@@ -48,7 +48,7 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
48
48
  test('empty source data → view returns no rows (no error)', async () => {
49
49
  const sql = await getConnection();
50
50
  // Wipe any rows the migration runner might have left.
51
- await sql`DELETE FROM genie_runtime_events WHERE kind = 'hook.delivery'`;
51
+ await sql`DELETE FROM genie_runtime_events WHERE subject = 'hook.delivery' OR kind = 'hook.delivery'`;
52
52
  const rows = (await sql`SELECT * FROM hook_perf_baseline`) as unknown[];
53
53
  expect(rows.length).toBe(0);
54
54
  });
@@ -58,11 +58,12 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
58
58
  // Wipe and seed a known distribution: durations 1..101 ms for one
59
59
  // (event, tool, handler) tuple. Created 5 minutes ago so they fall
60
60
  // inside the 1h, 24h, AND 7d windows.
61
- await sql`DELETE FROM genie_runtime_events WHERE kind = 'hook.delivery'`;
61
+ await sql`DELETE FROM genie_runtime_events WHERE subject = 'hook.delivery' OR kind = 'hook.delivery'`;
62
62
 
63
63
  const fiveMinAgo = new Date(Date.now() - 5 * 60_000);
64
64
  for (let i = 1; i <= 101; i++) {
65
65
  const data = sql.json({
66
+ _kind: 'span',
66
67
  event: 'PreToolUse',
67
68
  tool: 'Bash',
68
69
  hook_name: 'branch-guard',
@@ -70,9 +71,9 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
70
71
  });
71
72
  await sql`
72
73
  INSERT INTO genie_runtime_events
73
- (repo_path, kind, source, agent, text, data, created_at)
74
+ (repo_path, subject, kind, source, agent, text, data, created_at)
74
75
  VALUES
75
- ('test', 'hook.delivery', 'hooks', 'test', '', ${data}, ${fiveMinAgo})
76
+ ('test', 'hook.delivery', 'system', 'hooks', 'test', '', ${data}, ${fiveMinAgo})
76
77
  `;
77
78
  }
78
79
 
@@ -120,7 +121,7 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
120
121
 
121
122
  test('rolling windows correctly partition rows by created_at', async () => {
122
123
  const sql = await getConnection();
123
- await sql`DELETE FROM genie_runtime_events WHERE kind = 'hook.delivery'`;
124
+ await sql`DELETE FROM genie_runtime_events WHERE subject = 'hook.delivery' OR kind = 'hook.delivery'`;
124
125
 
125
126
  // Two cohorts:
126
127
  // - 50 rows at 30 minutes ago, duration 10ms → falls in 1h, 24h, 7d
@@ -130,6 +131,7 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
130
131
 
131
132
  for (let i = 0; i < 50; i++) {
132
133
  const data = sql.json({
134
+ _kind: 'span',
133
135
  event: 'PreToolUse',
134
136
  tool: 'Read',
135
137
  hook_name: 'freshness',
@@ -137,13 +139,14 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
137
139
  });
138
140
  await sql`
139
141
  INSERT INTO genie_runtime_events
140
- (repo_path, kind, source, agent, text, data, created_at)
142
+ (repo_path, subject, kind, source, agent, text, data, created_at)
141
143
  VALUES
142
- ('test', 'hook.delivery', 'hooks', 'test', '', ${data}, ${thirtyMinAgo})
144
+ ('test', 'hook.delivery', 'system', 'hooks', 'test', '', ${data}, ${thirtyMinAgo})
143
145
  `;
144
146
  }
145
147
  for (let i = 0; i < 50; i++) {
146
148
  const data = sql.json({
149
+ _kind: 'span',
147
150
  event: 'PreToolUse',
148
151
  tool: 'Read',
149
152
  hook_name: 'freshness',
@@ -151,9 +154,9 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
151
154
  });
152
155
  await sql`
153
156
  INSERT INTO genie_runtime_events
154
- (repo_path, kind, source, agent, text, data, created_at)
157
+ (repo_path, subject, kind, source, agent, text, data, created_at)
155
158
  VALUES
156
- ('test', 'hook.delivery', 'hooks', 'test', '', ${data}, ${fiveDaysAgo})
159
+ ('test', 'hook.delivery', 'system', 'hooks', 'test', '', ${data}, ${fiveDaysAgo})
157
160
  `;
158
161
  }
159
162
 
@@ -186,27 +189,29 @@ describe.skipIf(!DB_AVAILABLE)('hook_perf_baseline view', () => {
186
189
 
187
190
  test('rows missing data.duration_ms are filtered out', async () => {
188
191
  const sql = await getConnection();
189
- await sql`DELETE FROM genie_runtime_events WHERE kind = 'hook.delivery'`;
192
+ await sql`DELETE FROM genie_runtime_events WHERE subject = 'hook.delivery' OR kind = 'hook.delivery'`;
190
193
 
191
194
  // One valid row + one row missing duration_ms.
192
195
  const fiveMinAgo = new Date(Date.now() - 5 * 60_000);
193
196
  const validData = sql.json({
197
+ _kind: 'span',
194
198
  event: 'Stop',
195
199
  tool: '<none>',
196
200
  hook_name: 'runtime-emit-assistant-response',
197
201
  duration_ms: 42,
198
202
  });
199
203
  const missingDurData = sql.json({
204
+ _kind: 'span',
200
205
  event: 'Stop',
201
206
  tool: '<none>',
202
207
  hook_name: 'runtime-emit-assistant-response',
203
208
  });
204
209
  await sql`
205
210
  INSERT INTO genie_runtime_events
206
- (repo_path, kind, source, agent, text, data, created_at)
211
+ (repo_path, subject, kind, source, agent, text, data, created_at)
207
212
  VALUES
208
- ('test', 'hook.delivery', 'hooks', 'test', '', ${validData}, ${fiveMinAgo}),
209
- ('test', 'hook.delivery', 'hooks', 'test', '', ${missingDurData}, ${fiveMinAgo})
213
+ ('test', 'hook.delivery', 'system', 'hooks', 'test', '', ${validData}, ${fiveMinAgo}),
214
+ ('test', 'hook.delivery', 'system', 'hooks', 'test', '', ${missingDurData}, ${fiveMinAgo})
210
215
  `;
211
216
 
212
217
  const rows = (await sql`