@automagik/genie 4.260415.1 → 4.260416.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
|
@@ -299,7 +299,7 @@ Next steps:`),console.log(" 1. Reload tmux: tmux source ~/.tmux.conf"),console.
|
|
|
299
299
|
DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days';
|
|
300
300
|
DELETE FROM genie_runtime_events WHERE created_at < now() - interval '14 days';
|
|
301
301
|
`),retentionRan=!0}catch(retErr){retentionRan=!0;let msg=retErr instanceof Error?retErr.message:String(retErr);process.stderr.write(`[genie] retention cleanup warning: ${msg}
|
|
302
|
-
`)}}async function ensurePgserve(){if(activePort===null){let testPort=await resolveTestPort();if(testPort!==null)return activePort=testPort,process.env.GENIE_PG_AVAILABLE="true",testPort}if(ensurePromise)return ensurePromise;ensurePromise=_ensurePgserve();try{return await ensurePromise}finally{ensurePromise=null}}async function autoStartDaemon(){for(let pidName of["serve.pid","scheduler.pid"]){let pidPath=join14(GENIE_HOME2,pidName);try{let pidStr=readFileSync9(pidPath,"utf-8").trim(),pid=Number.parseInt(pidStr,10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,0);return}catch{}}catch{}}let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn3(bunPath,[genieBin,"serve","start","--headless"],{detached:!0,stdio:"ignore",env:{...process.env}}).unref()}async function resolveTestPort(){let raw=process.env.GENIE_TEST_PG_PORT;if(!raw)return null;let parsed=Number.parseInt(raw,10);if(Number.isNaN(parsed)||parsed<=0||parsed>=65536||!await isPostgresHealthy(parsed))throw Error(`GENIE_TEST_PG_PORT=${raw} set but not reachable`);return parsed}async function _ensurePgserve(){if(activePort!==null)return activePort;let port=getPort(),portFromFile=readLockfile();if(portFromFile!==null&&await isPostgresHealthy(portFromFile))return activePort=portFromFile,process.env.GENIE_PG_AVAILABLE="true",portFromFile;if(await isPostgresHealthy(port))return activePort=port,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;if(process.env.CI==="true")throw process.env.GENIE_PG_AVAILABLE="false",Error("pgserve not available in CI");if(process.env.GENIE_IS_DAEMON==="1"){mkdirSync7(DATA_DIR,{recursive:!0}),selfHealPostgres(DATA_DIR);try{let startedPort=await startPgserveOnPort(port);return registerExitHandler(),startedPort}catch(err){process.env.GENIE_PG_AVAILABLE="false";let message=err instanceof Error?err.message:String(err);throw Error(`pgserve failed to start: ${maskCredentials(message)}`)}}await autoStartDaemon();let deadline=Date.now()+16000;while(Date.now()<deadline){let p=readLockfile();if(p!==null&&await isPostgresHealthy(p))return activePort=p,process.env.GENIE_PG_AVAILABLE="true",p;await new Promise((r)=>setTimeout(r,500))}throw process.env.GENIE_PG_AVAILABLE="false",Error("Timed out waiting for genie serve to start pgserve (16s). Run: genie serve")}function findPgserveBin(){try{let resolved=__require.resolve("pgserve/bin/pgserve-wrapper.cjs");if(existsSync13(resolved))return resolved}catch{}let globalBin=join14(homedir13(),".bun","bin","pgserve");if(existsSync13(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync7(DATA_DIR,{recursive:!0});let child=spawn3(findPgserveBin(),["--port",String(port),"--host",DEFAULT_HOST,"--data",DATA_DIR,"--log","warn","--no-stats","--no-cluster","--pgvector"],{detached:!0,stdio:"ignore"});child.unref(),pgserveChild=child;let timeout=Number(process.env.GENIE_PGSERVE_TIMEOUT)||30000,deadline=Date.now()+timeout;while(Date.now()<deadline){if(await isPostgresHealthy(port))return activePort=port,ownsLockfile=!0,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;await new Promise((r)=>setTimeout(r,500))}try{child.kill("SIGTERM")}catch{}throw Error(`pgserve failed to start on port ${port} (timeout after ${timeout/1000}s)`)}function registerExitHandler(){if(exitHandlerRegistered)return;exitHandlerRegistered=!0;let cleanup=()=>{if(pgserveChild){try{pgserveChild.kill("SIGTERM")}catch{}pgserveChild=null}if(ownsLockfile)removeLockfile(),ownsLockfile=!1};process.on("exit",cleanup),process.on("SIGINT",()=>{cleanup(),process.exit(130)}),process.on("SIGTERM",()=>{cleanup(),process.exit(143)})}async function healthCheckCachedClient(){if(!sqlClient)return null;try{return await sqlClient`SELECT 1`,sqlClient}catch{try{await sqlClient.end({timeout:2})}catch{}return sqlClient=null,activePort=null,null}}async function runPostConnectSetup(client,testSchema,timings){let _t2=Date.now();if(!testSchema)await runMigrations(client);let _t3=Date.now();if(!testSchema&&needsSeed())await runSeed(client);let _t4=Date.now();if(!testSchema&&!retentionRan)await runRetention(client);let _t5=Date.now();if(process.env.GENIE_PROFILE_DB)console.error(`[db-profile] pgserve=${timings.t1-timings.t0}ms migrate=${_t3-_t2}ms seed=${_t4-_t3}ms retention=${_t5-_t4}ms total=${_t5-timings.t0}ms`)}async function getConnection(){let cached=await healthCheckCachedClient();if(cached)return cached;let _t0=Date.now(),port=await ensurePgserve(),_t1=Date.now(),pgModule=(await Promise.resolve().then(() => (init_src(),exports_src))).default,testSchema=process.env.GENIE_TEST_SCHEMA;sqlClient=pgModule({host:DEFAULT_HOST,port,database:DB_NAME,username:"postgres",password:"postgres",max:50,idle_timeout:1,connect_timeout:5,onnotice:()=>{},connection:{client_min_messages:"warning",...testSchema?{search_path:`${testSchema}, public`}:{}}});try{await runPostConnectSetup(sqlClient,testSchema,{t0:_t0,t1:_t1})}catch(err){try{await sqlClient.end({timeout:2})}catch{}throw sqlClient=null,activePort=null,err}return sqlClient}function isConnected(){return sqlClient!==null}async function resetConnection(){if(sqlClient)await
|
|
302
|
+
`)}}async function ensurePgserve(){if(activePort===null){let testPort=await resolveTestPort();if(testPort!==null)return activePort=testPort,process.env.GENIE_PG_AVAILABLE="true",testPort}if(ensurePromise)return ensurePromise;ensurePromise=_ensurePgserve();try{return await ensurePromise}finally{ensurePromise=null}}async function autoStartDaemon(){for(let pidName of["serve.pid","scheduler.pid"]){let pidPath=join14(GENIE_HOME2,pidName);try{let pidStr=readFileSync9(pidPath,"utf-8").trim(),pid=Number.parseInt(pidStr,10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,0);return}catch{}}catch{}}let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn3(bunPath,[genieBin,"serve","start","--headless"],{detached:!0,stdio:"ignore",env:{...process.env}}).unref()}async function resolveTestPort(){let raw=process.env.GENIE_TEST_PG_PORT;if(!raw)return null;let parsed=Number.parseInt(raw,10);if(Number.isNaN(parsed)||parsed<=0||parsed>=65536||!await isPostgresHealthy(parsed))throw Error(`GENIE_TEST_PG_PORT=${raw} set but not reachable`);return parsed}async function _ensurePgserve(){if(activePort!==null)return activePort;let port=getPort(),portFromFile=readLockfile();if(portFromFile!==null&&await isPostgresHealthy(portFromFile))return activePort=portFromFile,process.env.GENIE_PG_AVAILABLE="true",portFromFile;if(await isPostgresHealthy(port))return activePort=port,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;if(process.env.CI==="true")throw process.env.GENIE_PG_AVAILABLE="false",Error("pgserve not available in CI");if(process.env.GENIE_IS_DAEMON==="1"){mkdirSync7(DATA_DIR,{recursive:!0}),selfHealPostgres(DATA_DIR);try{let startedPort=await startPgserveOnPort(port);return registerExitHandler(),startedPort}catch(err){process.env.GENIE_PG_AVAILABLE="false";let message=err instanceof Error?err.message:String(err);throw Error(`pgserve failed to start: ${maskCredentials(message)}`)}}await autoStartDaemon();let deadline=Date.now()+16000;while(Date.now()<deadline){let p=readLockfile();if(p!==null&&await isPostgresHealthy(p))return activePort=p,process.env.GENIE_PG_AVAILABLE="true",p;await new Promise((r)=>setTimeout(r,500))}throw process.env.GENIE_PG_AVAILABLE="false",Error("Timed out waiting for genie serve to start pgserve (16s). Run: genie serve")}function findPgserveBin(){try{let resolved=__require.resolve("pgserve/bin/pgserve-wrapper.cjs");if(existsSync13(resolved))return resolved}catch{}let globalBin=join14(homedir13(),".bun","bin","pgserve");if(existsSync13(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync7(DATA_DIR,{recursive:!0});let child=spawn3(findPgserveBin(),["--port",String(port),"--host",DEFAULT_HOST,"--data",DATA_DIR,"--log","warn","--no-stats","--no-cluster","--pgvector"],{detached:!0,stdio:"ignore"});child.unref(),pgserveChild=child;let timeout=Number(process.env.GENIE_PGSERVE_TIMEOUT)||30000,deadline=Date.now()+timeout;while(Date.now()<deadline){if(await isPostgresHealthy(port))return activePort=port,ownsLockfile=!0,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;await new Promise((r)=>setTimeout(r,500))}try{child.kill("SIGTERM")}catch{}throw Error(`pgserve failed to start on port ${port} (timeout after ${timeout/1000}s)`)}function registerExitHandler(){if(exitHandlerRegistered)return;exitHandlerRegistered=!0;let cleanup=()=>{if(pgserveChild){try{pgserveChild.kill("SIGTERM")}catch{}pgserveChild=null}if(ownsLockfile)removeLockfile(),ownsLockfile=!1};process.on("exit",cleanup),process.on("SIGINT",()=>{cleanup(),process.exit(130)}),process.on("SIGTERM",()=>{cleanup(),process.exit(143)})}async function healthCheckCachedClient(){if(!sqlClient)return null;try{return await sqlClient`SELECT 1`,sqlClient}catch{try{await sqlClient.end({timeout:2})}catch{}return sqlClient=null,activePort=null,null}}async function runPostConnectSetup(client,testSchema,timings){let _t2=Date.now();if(!testSchema)await runMigrations(client);let _t3=Date.now();if(!testSchema&&needsSeed())await runSeed(client);let _t4=Date.now();if(!testSchema&&!retentionRan)await runRetention(client);let _t5=Date.now();if(process.env.GENIE_PROFILE_DB)console.error(`[db-profile] pgserve=${timings.t1-timings.t0}ms migrate=${_t3-_t2}ms seed=${_t4-_t3}ms retention=${_t5-_t4}ms total=${_t5-timings.t0}ms`)}async function getConnection(){let cached=await healthCheckCachedClient();if(cached)return cached;let _t0=Date.now(),port=await ensurePgserve(),_t1=Date.now(),pgModule=(await Promise.resolve().then(() => (init_src(),exports_src))).default,testSchema=process.env.GENIE_TEST_SCHEMA;sqlClient=pgModule({host:DEFAULT_HOST,port,database:DB_NAME,username:"postgres",password:"postgres",max:50,idle_timeout:1,connect_timeout:5,onnotice:()=>{},connection:{client_min_messages:"warning",...testSchema?{search_path:`${testSchema}, public`}:{}}});try{await runPostConnectSetup(sqlClient,testSchema,{t0:_t0,t1:_t1})}catch(err){try{await sqlClient.end({timeout:2})}catch{}throw sqlClient=null,activePort=null,err}return sqlClient}function isConnected(){return sqlClient!==null}async function resetConnection(){if(sqlClient){let dying=sqlClient;sqlClient=null,await dying.end({timeout:5})}}async function isAvailable(){try{return await(await getConnection())`SELECT 1`,!0}catch{return!1}}async function shutdown(){if(sqlClient)await sqlClient.end({timeout:5}),sqlClient=null;if(ownsLockfile)removeLockfile(),ownsLockfile=!1}function getDataDir(){return DATA_DIR}function getActivePort(){return activePort??getPort()}function getLockfilePath(){return LOCKFILE_PATH}var DEFAULT_PORT=19642,DEFAULT_HOST="127.0.0.1",GENIE_HOME2,DATA_DIR,LOCKFILE_PATH,DB_NAME="genie",pgserveChild=null,sqlClient=null,activePort=null,ensurePromise=null,ownsLockfile=!1,exitHandlerRegistered=!1,retentionRan=!1;var init_db=__esm(()=>{init_db_migrations();init_pg_seed();GENIE_HOME2=process.env.GENIE_HOME??join14(homedir13(),".genie"),DATA_DIR=join14(GENIE_HOME2,"data","pgserve"),LOCKFILE_PATH=join14(GENIE_HOME2,"pgserve.port")});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`
|
|
303
303
|
INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
|
|
304
304
|
VALUES (${entityType}, ${entityId}, ${eventType}, ${actor??null}, ${sql.json(details??{})})
|
|
305
305
|
`}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=[],values2=[],paramIdx=1;if(options.type)conditions.push(`event_type = $${paramIdx++}`),values2.push(options.type);if(options.entity)conditions.push(`(entity_type = $${paramIdx} OR entity_id = $${paramIdx})`),paramIdx++,values2.push(options.entity);if(options.since)conditions.push(`created_at >= $${paramIdx++}::timestamptz`),values2.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
|
|
@@ -1178,7 +1178,7 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1178
1178
|
ORDER BY created_at ASC
|
|
1179
1179
|
`}async function pollApprovalState(approvalId,defaultAction){let sql=await getConnection(),[approval]=await sql`
|
|
1180
1180
|
SELECT decision, timeout_at FROM approvals WHERE id = ${approvalId}
|
|
1181
|
-
`;if(!approval)return"deny";if(approval.decision==="allow"||approval.decision==="deny")return approval.decision;if(new Date(approval.timeout_at)<=new Date)return await resolveApproval(approvalId,defaultAction,"timeout"),defaultAction;return null}async function waitForResolution(approvalId,timeoutAt,defaultAction){let sql=await getConnection();return new Promise((resolve5)=>{let resolved=!1,listener=null,pollTimer=null,finish=(decision)=>{if(resolved)return;if(resolved=!0,listener)listener.unlisten().catch(()=>{});if(pollTimer)clearInterval(pollTimer);resolve5(decision)},checkResolution=async()=>{if(resolved)return;try{let result2=await pollApprovalState(approvalId,defaultAction);if(result2!==null)finish(result2)}catch{finish("deny")}};sql.listen("genie_approval_resolved",(payload)=>{if(payload===approvalId)checkResolution()}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollTimer=setInterval(checkResolution,5000);let msUntilTimeout=Math.max(0,timeoutAt.getTime()-Date.now()+1000);setTimeout(()=>{if(!resolved)resolveApproval(approvalId,defaultAction,"timeout").catch(()=>{}),finish(defaultAction)},msUntilTimeout).unref(),checkResolution()})}function createRemoteApprovalGate(config){let timeoutSec=config.permissions?.timeout??300,defaultAction=config.permissions?.defaultAction??"deny";return async(input)=>{let hookInput=input,toolName=hookInput.tool_name,toolInput=hookInput.tool_input??{},preview=JSON.stringify(toolInput).slice(0,500),timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(config.executorId,config.agentName,toolName,preview,timeoutAt);if(config.permissions?.omniChat&&config.permissions?.omniInstance)sendApprovalToOmni(approvalId,config.agentName,toolName,preview,config.permissions).catch(()=>{});return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:await waitForResolution(approvalId,timeoutAt,defaultAction)}}}}var recentOmniSends,BATCH_WINDOW_MS=1e4,BATCH_THRESHOLD=3;var init_claude_sdk_remote_approval=__esm(()=>{init_db();recentOmniSends=[]});var exports_claude_sdk={};__export(exports_claude_sdk,{translateSdkConfig:()=>translateSdkConfig,ClaudeSdkProvider:()=>ClaudeSdkProvider});import{readFileSync as readFileSync16}from"fs";import{query}from"@anthropic-ai/claude-agent-sdk";function translateSdkConfig(sdkConfig){let opts={};for(let key of SDK_TRUTHY_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];for(let key of SDK_NULLABLE_FIELDS)if(sdkConfig[key]!=null)opts[key]=sdkConfig[key];for(let key of SDK_CAST_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];return opts}function mergeHooks(...sources){let merged={};for(let src of sources){if(!src)continue;for(let[event,matchers]of Object.entries(src)){if(!matchers)continue;if(!merged[event])merged[event]=[...matchers];else merged[event].push(...matchers)}}return merged}class ClaudeSdkProvider{name="claude-sdk";transport="process";activeQueries=new Map;buildSpawnCommand(ctx){return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:ctx.role,skill:ctx.skill}}}runQuery(ctx,prompt2,permissionConfig,extraOptions,sdkConfig){ensureTeammateBypassPermissions();let abortController=new AbortController,tracker={abortController,done:!1};this.activeQueries.set(ctx.executorId,tracker);let permHooks;if(sdkConfig?.permissionMode==="remoteApproval"){let ws=findWorkspace(ctx.cwd),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0;permHooks={PreToolUse:[{matcher:"*",hooks:[createRemoteApprovalGate({executorId:ctx.executorId,agentName:ctx.agentId??ctx.role??"unknown",permissions})]}]}}else if(permissionConfig)permHooks={PreToolUse:[{matcher:"*",hooks:[createPermissionGate(permissionConfig)]}]};let translatedSdk=sdkConfig?translateSdkConfig(sdkConfig):void 0,mergedHooks=mergeHooks(permHooks,translatedSdk?.hooks,extraOptions?.hooks),hasHooks=Object.keys(mergedHooks).length>0,resolvedSystemPrompt=ctx.systemPrompt;if(!resolvedSystemPrompt&&ctx.systemPromptFile)try{resolvedSystemPrompt=readFileSync16(ctx.systemPromptFile,"utf-8")}catch{}let options={cwd:ctx.cwd,abortController,...ctx.model&&{model:ctx.model},...resolvedSystemPrompt&&{systemPrompt:resolvedSystemPrompt},...translatedSdk,...extraOptions,...hasHooks&&{hooks:mergedHooks},permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0},messages2=query({prompt:prompt2,options}),originalReturn=messages2.return.bind(messages2);messages2.return=async(value)=>{return tracker.done=!0,this.activeQueries.delete(ctx.executorId),originalReturn(value)};let self2=this,wrappedMessages=async function*(){try{for await(let msg of messages2)yield msg,routeSdkMessage(msg,ctx.executorId,ctx.agentId).catch(()=>{})}finally{tracker.done=!0,self2.activeQueries.delete(ctx.executorId)}}();return wrappedMessages.interrupt=messages2.interrupt.bind(messages2),wrappedMessages.setPermissionMode=messages2.setPermissionMode.bind(messages2),wrappedMessages.setModel=messages2.setModel.bind(messages2),wrappedMessages.return=messages2.return.bind(messages2),wrappedMessages.throw=messages2.throw.bind(messages2),{messages:wrappedMessages,abortController}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;return{sessionId}}async detectState(executor){let tracker=this.activeQueries.get(executor.id);if(!tracker)return"done";return tracker.done?"done":"running"}async terminate(executor){let tracker=this.activeQueries.get(executor.id);if(tracker)tracker.abortController.abort(),tracker.done=!0,this.activeQueries.delete(executor.id)}canResume(){return!1}}var SDK_TRUTHY_FIELDS,SDK_NULLABLE_FIELDS,SDK_CAST_FIELDS;var init_claude_sdk=__esm(()=>{init_claude_settings();init_workspace();init_claude_sdk_events();init_claude_sdk_permissions();init_claude_sdk_remote_approval();SDK_TRUTHY_FIELDS=["tools","allowedTools","disallowedTools","plugins","systemPrompt","betas","settingSources"],SDK_NULLABLE_FIELDS=["maxTurns","maxBudgetUsd","persistSession","enableFileCheckpointing","includePartialMessages","includeHookEvents","promptSuggestions","agentProgressSummaries"],SDK_CAST_FIELDS=["effort","thinking","agents","mcpServers","outputFormat","sandbox","settings"]});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty,claudeSdk;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_claude_sdk();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider,claudeSdk=new ClaudeSdkProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty);providers.set("claude-sdk",claudeSdk)});var exports_wish_state={};__export(exports_wish_state,{startGroup:()=>startGroup,resolveRepoPath:()=>resolveRepoPath,resetInProgressGroups:()=>resetInProgressGroups,resetGroup:()=>resetGroup,isWishComplete:()=>isWishComplete,getState:()=>getState,getOrCreateState:()=>getOrCreateState,getGroupState:()=>getGroupState,findGroupByAssignee:()=>findGroupByAssignee,findAnyGroupByAssignee:()=>findAnyGroupByAssignee,createState:()=>createState,completeGroup:()=>completeGroup});import{execSync as execSync8}from"child_process";import{existsSync as existsSync26}from"fs";import{dirname as dirname5}from"path";function normalizeGitPath(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync26(logicalPath)?logicalPath:path3}function resolveRepoPath(cwd){if(cwd)return cwd;try{let currentDir=process.cwd(),commonDir=execSync8("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",stdio:["pipe","pipe","pipe"],env:{...process.env,GIT_CEILING_DIRECTORIES:dirname5(currentDir)}}).trim();return normalizeGitPath(dirname5(commonDir))}catch{return normalizeGitPath(process.cwd())}}function wishFilePath(slug){return`.genie/wishes/${slug}/WISH.md`}function toISO(v){if(v==null)return;if(v instanceof Date)return v.toISOString();return String(v)}async function findParent(sql,slug,repoPath){let wishFile=wishFilePath(slug),rows=await sql`
|
|
1181
|
+
`;if(!approval)return"deny";if(approval.decision==="allow"||approval.decision==="deny")return approval.decision;if(new Date(approval.timeout_at)<=new Date)return await resolveApproval(approvalId,defaultAction,"timeout"),defaultAction;return null}async function waitForResolution(approvalId,timeoutAt,defaultAction){let sql=await getConnection();return new Promise((resolve5)=>{let resolved=!1,listener=null,pollTimer=null,finish=(decision)=>{if(resolved)return;if(resolved=!0,listener)listener.unlisten().catch(()=>{});if(pollTimer)clearInterval(pollTimer);resolve5(decision)},checkResolution=async()=>{if(resolved)return;try{let result2=await pollApprovalState(approvalId,defaultAction);if(result2!==null)finish(result2)}catch{finish("deny")}};sql.listen("genie_approval_resolved",(payload)=>{if(payload===approvalId)checkResolution()}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollTimer=setInterval(checkResolution,5000);let msUntilTimeout=Math.max(0,timeoutAt.getTime()-Date.now()+1000);setTimeout(()=>{if(!resolved)resolveApproval(approvalId,defaultAction,"timeout").catch(()=>{}),finish(defaultAction)},msUntilTimeout).unref(),checkResolution()})}function createRemoteApprovalGate(config){let timeoutSec=config.permissions?.timeout??300,defaultAction=config.permissions?.defaultAction??"deny";return async(input)=>{let hookInput=input,toolName=hookInput.tool_name,toolInput=hookInput.tool_input??{},preview=JSON.stringify(toolInput).slice(0,500),timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(config.executorId,config.agentName,toolName,preview,timeoutAt);if(config.permissions?.omniChat&&config.permissions?.omniInstance)sendApprovalToOmni(approvalId,config.agentName,toolName,preview,config.permissions).catch(()=>{});return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:await waitForResolution(approvalId,timeoutAt,defaultAction)}}}}var recentOmniSends,BATCH_WINDOW_MS=1e4,BATCH_THRESHOLD=3;var init_claude_sdk_remote_approval=__esm(()=>{init_db();recentOmniSends=[]});var exports_claude_sdk={};__export(exports_claude_sdk,{translateSdkConfig:()=>translateSdkConfig,ClaudeSdkProvider:()=>ClaudeSdkProvider});import{readFileSync as readFileSync16}from"fs";import{query}from"@anthropic-ai/claude-agent-sdk";function translateSdkConfig(sdkConfig){let opts={};for(let key of SDK_TRUTHY_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];for(let key of SDK_NULLABLE_FIELDS)if(sdkConfig[key]!=null)opts[key]=sdkConfig[key];for(let key of SDK_CAST_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];return opts}function mergeHooks(...sources){let merged={};for(let src of sources){if(!src)continue;for(let[event,matchers]of Object.entries(src)){if(!matchers)continue;if(!merged[event])merged[event]=[...matchers];else merged[event].push(...matchers)}}return merged}class ClaudeSdkProvider{name="claude-sdk";transport="process";activeQueries=new Map;buildSpawnCommand(ctx){return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:ctx.role,skill:ctx.skill}}}runQuery(ctx,prompt2,permissionConfig,extraOptions,sdkConfig){ensureTeammateBypassPermissions();let abortController=new AbortController,tracker={abortController,done:!1};this.activeQueries.set(ctx.executorId,tracker);let permHooks;if(sdkConfig?.permissionMode==="remoteApproval"){let ws=findWorkspace(ctx.cwd),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0;permHooks={PreToolUse:[{matcher:"*",hooks:[createRemoteApprovalGate({executorId:ctx.executorId,agentName:ctx.agentId??ctx.role??"unknown",permissions})]}]}}else if(permissionConfig)permHooks={PreToolUse:[{matcher:"*",hooks:[createPermissionGate(permissionConfig)]}]};let translatedSdk=sdkConfig?translateSdkConfig(sdkConfig):void 0,mergedHooks=mergeHooks(permHooks,translatedSdk?.hooks,extraOptions?.hooks),hasHooks=Object.keys(mergedHooks).length>0,resolvedSystemPrompt=ctx.systemPrompt;if(!resolvedSystemPrompt&&ctx.systemPromptFile)try{resolvedSystemPrompt=readFileSync16(ctx.systemPromptFile,"utf-8")}catch{}let options={cwd:ctx.cwd,abortController,...ctx.model&&{model:ctx.model},...resolvedSystemPrompt&&{systemPrompt:resolvedSystemPrompt},...translatedSdk,...extraOptions,...hasHooks&&{hooks:mergedHooks},permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0},messages2=query({prompt:prompt2,options}),originalReturn=messages2.return.bind(messages2);messages2.return=async(value)=>{return tracker.done=!0,this.activeQueries.delete(ctx.executorId),originalReturn(value)};let self2=this,wrappedMessages=async function*(){try{for await(let msg of messages2)yield msg,routeSdkMessage(msg,ctx.executorId,ctx.agentId).catch(()=>{})}finally{tracker.done=!0,self2.activeQueries.delete(ctx.executorId)}}();return wrappedMessages.interrupt=messages2.interrupt.bind(messages2),wrappedMessages.setPermissionMode=messages2.setPermissionMode.bind(messages2),wrappedMessages.setModel=messages2.setModel.bind(messages2),wrappedMessages.return=messages2.return.bind(messages2),wrappedMessages.throw=messages2.throw.bind(messages2),{messages:wrappedMessages,abortController}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;return{sessionId}}async detectState(executor){let tracker=this.activeQueries.get(executor.id);if(!tracker)return"done";return tracker.done?"done":"running"}async terminate(executor){let tracker=this.activeQueries.get(executor.id);if(tracker)tracker.abortController.abort(),tracker.done=!0,this.activeQueries.delete(executor.id)}canResume(){return!1}}var SDK_TRUTHY_FIELDS,SDK_NULLABLE_FIELDS,SDK_CAST_FIELDS;var init_claude_sdk=__esm(()=>{init_claude_settings();init_workspace();init_claude_sdk_events();init_claude_sdk_permissions();init_claude_sdk_remote_approval();SDK_TRUTHY_FIELDS=["tools","allowedTools","disallowedTools","plugins","systemPrompt","betas","settingSources"],SDK_NULLABLE_FIELDS=["maxTurns","maxBudgetUsd","persistSession","enableFileCheckpointing","includePartialMessages","includeHookEvents","promptSuggestions","agentProgressSummaries"],SDK_CAST_FIELDS=["effort","thinking","agents","mcpServers","outputFormat","sandbox","settings"]});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty,claudeSdk;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_claude_sdk();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider,claudeSdk=new ClaudeSdkProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty);providers.set("claude-sdk",claudeSdk)});var exports_wish_state={};__export(exports_wish_state,{wipeState:()=>wipeState,startGroup:()=>startGroup,resolveRepoPath:()=>resolveRepoPath,resetInProgressGroups:()=>resetInProgressGroups,resetGroup:()=>resetGroup,isWishComplete:()=>isWishComplete,getState:()=>getState,getOrCreateState:()=>getOrCreateState,getGroupState:()=>getGroupState,findGroupByAssignee:()=>findGroupByAssignee,findAnyGroupByAssignee:()=>findAnyGroupByAssignee,createState:()=>createState,computeGroupsSignature:()=>computeGroupsSignature,completeGroup:()=>completeGroup,WishStateMismatchError:()=>WishStateMismatchError});import{execSync as execSync8}from"child_process";import{createHash as createHash4}from"crypto";import{existsSync as existsSync26}from"fs";import{dirname as dirname5}from"path";function computeGroupsSignature(groups){let canonical=groups.map((g)=>({name:g.name,dependsOn:[...g.dependsOn??[]].sort()})).sort((a,b2)=>a.name<b2.name?-1:a.name>b2.name?1:0);return createHash4("sha256").update(JSON.stringify(canonical)).digest("hex")}function diffGroups(prev,next){let prevMap=new Map(prev.map((g)=>[g.name,[...g.dependsOn??[]].sort()])),nextMap=new Map(next.map((g)=>[g.name,[...g.dependsOn??[]].sort()])),added=[],removed=[],changed=[];for(let[name,deps]of nextMap)if(!prevMap.has(name))added.push(name);else if(JSON.stringify(prevMap.get(name))!==JSON.stringify(deps))changed.push(name);for(let name of prevMap.keys())if(!nextMap.has(name))removed.push(name);return{added:added.sort(),removed:removed.sort(),changed:changed.sort()}}function normalizeGitPath(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync26(logicalPath)?logicalPath:path3}function resolveRepoPath(cwd){if(cwd)return cwd;try{let currentDir=process.cwd(),commonDir=execSync8("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",stdio:["pipe","pipe","pipe"],env:{...process.env,GIT_CEILING_DIRECTORIES:dirname5(currentDir)}}).trim();return normalizeGitPath(dirname5(commonDir))}catch{return normalizeGitPath(process.cwd())}}function wishFilePath(slug){return`.genie/wishes/${slug}/WISH.md`}function toISO(v){if(v==null)return;if(v instanceof Date)return v.toISOString();return String(v)}async function findParent(sql,slug,repoPath){let wishFile=wishFilePath(slug),rows=await sql`
|
|
1182
1182
|
SELECT * FROM tasks
|
|
1183
1183
|
WHERE wish_file = ${wishFile} AND repo_path = ${repoPath} AND parent_id IS NULL
|
|
1184
1184
|
LIMIT 1
|
|
@@ -1186,9 +1186,12 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1186
1186
|
SELECT * FROM tasks
|
|
1187
1187
|
WHERE parent_id = ${parentId} AND group_name = ${groupName}
|
|
1188
1188
|
LIMIT 1
|
|
1189
|
-
`;return rows.length>0?rows[0]:null}function validateGroupRefs(groups){let groupNames=new Set(groups.map((g)=>g.name));for(let group of groups){if(group.dependsOn?.includes(group.name))throw Error(`Group "${group.name}" depends on itself`);for(let dep of group.dependsOn??[])if(!groupNames.has(dep))throw Error(`Group "${group.name}" depends on non-existent group "${dep}"`)}}function detectCycles(groups){let inDegree={},adjacency={};for(let group of groups)inDegree[group.name]=(group.dependsOn??[]).length,adjacency[group.name]=[];for(let group of groups)for(let dep of group.dependsOn??[])adjacency[dep].push(group.name);let queue=Object.entries(inDegree).filter(([,deg])=>deg===0).map(([name])=>name),processed=0;while(queue.length>0){let node=queue.shift();if(!node)break;processed++;for(let neighbor of adjacency[node])if(inDegree[neighbor]--,inDegree[neighbor]===0)queue.push(neighbor)}if(processed!==groups.length){let remaining=Object.entries(inDegree).filter(([,deg])=>deg>0).map(([name])=>name);throw Error(`Dependency cycle detected among groups: ${remaining.join(", ")}`)}}function validateGroups(groups){validateGroupRefs(groups),detectCycles(groups)}async function createState(slug,groups,cwd){validateGroups(groups);let sql=await getConnection(),repoPath=resolveRepoPath(cwd),wishFile=wishFilePath(slug),existingParent=await findParent(sql,slug,repoPath);if(existingParent)await sql`DELETE FROM tasks WHERE id = ${existingParent.id}`;let[parent]=await sql`
|
|
1190
|
-
INSERT INTO tasks (repo_path, title, wish_file, type_id, stage, status)
|
|
1191
|
-
VALUES (
|
|
1189
|
+
`;return rows.length>0?rows[0]:null}function validateGroupRefs(groups){let groupNames=new Set(groups.map((g)=>g.name));for(let group of groups){if(group.dependsOn?.includes(group.name))throw Error(`Group "${group.name}" depends on itself`);for(let dep of group.dependsOn??[])if(!groupNames.has(dep))throw Error(`Group "${group.name}" depends on non-existent group "${dep}"`)}}function detectCycles(groups){let inDegree={},adjacency={};for(let group of groups)inDegree[group.name]=(group.dependsOn??[]).length,adjacency[group.name]=[];for(let group of groups)for(let dep of group.dependsOn??[])adjacency[dep].push(group.name);let queue=Object.entries(inDegree).filter(([,deg])=>deg===0).map(([name])=>name),processed=0;while(queue.length>0){let node=queue.shift();if(!node)break;processed++;for(let neighbor of adjacency[node])if(inDegree[neighbor]--,inDegree[neighbor]===0)queue.push(neighbor)}if(processed!==groups.length){let remaining=Object.entries(inDegree).filter(([,deg])=>deg>0).map(([name])=>name);throw Error(`Dependency cycle detected among groups: ${remaining.join(", ")}`)}}function validateGroups(groups){validateGroupRefs(groups),detectCycles(groups)}async function createState(slug,groups,cwd){validateGroups(groups);let sql=await getConnection(),repoPath=resolveRepoPath(cwd),wishFile=wishFilePath(slug),existingParent=await findParent(sql,slug,repoPath);if(existingParent)await sql`DELETE FROM tasks WHERE id = ${existingParent.id}`;let groupsSignature=computeGroupsSignature(groups),[parent]=await sql`
|
|
1190
|
+
INSERT INTO tasks (repo_path, title, wish_file, type_id, stage, status, metadata)
|
|
1191
|
+
VALUES (
|
|
1192
|
+
${repoPath}, ${slug}, ${wishFile}, 'software', 'draft', 'ready',
|
|
1193
|
+
${sql.json({groupsSignature})}
|
|
1194
|
+
)
|
|
1192
1195
|
RETURNING *
|
|
1193
1196
|
`,childIds={};for(let group of groups){let status=(group.dependsOn??[]).length===0?"ready":"blocked",[child]=await sql`
|
|
1194
1197
|
INSERT INTO tasks (repo_path, title, parent_id, group_name, type_id, stage, status)
|
|
@@ -1247,7 +1250,12 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1247
1250
|
AND t.group_name IS NOT NULL
|
|
1248
1251
|
AND p.wish_file IS NOT NULL
|
|
1249
1252
|
ORDER BY t.created_at
|
|
1250
|
-
`;for(let row of rows){let assignee=row.assignee;if(assignee!==workerId&&!workerId.endsWith(`-${assignee}`))continue;let slugMatch=row.wish_file.match(/\.genie\/wishes\/([^/]+)\/WISH\.md/);if(!slugMatch)continue;let slug=slugMatch[1],groupName=row.group_name,state=await getState(slug,cwd);if(!state)continue;let group=state.groups[groupName];if(!group)continue;return{slug,groupName,group:{...group}}}return null}async function getOrCreateState(slug,groups,cwd){let existing=await getState(slug,cwd);if(existing)return existing;return createState(slug,groups,cwd)}async function
|
|
1253
|
+
`;for(let row of rows){let assignee=row.assignee;if(assignee!==workerId&&!workerId.endsWith(`-${assignee}`))continue;let slugMatch=row.wish_file.match(/\.genie\/wishes\/([^/]+)\/WISH\.md/);if(!slugMatch)continue;let slug=slugMatch[1],groupName=row.group_name,state=await getState(slug,cwd);if(!state)continue;let group=state.groups[groupName];if(!group)continue;return{slug,groupName,group:{...group}}}return null}async function getOrCreateState(slug,groups,cwd){let existing=await getState(slug,cwd);if(existing)return await validateSignatureOrThrow(slug,groups,cwd),existing;return createState(slug,groups,cwd)}async function validateSignatureOrThrow(slug,groups,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return;let stored=readStoredSignature(parent.metadata);if(!stored)return;let fresh=computeGroupsSignature(groups);if(stored===fresh)return;let prevGroups=await readGroupDefinitions(sql,parent.id),{added,removed,changed}=diffGroups(prevGroups,groups);throw new WishStateMismatchError(slug,added,removed,changed)}function readStoredSignature(metadata){if(metadata==null)return null;let parsed=metadata;if(typeof metadata==="string")try{parsed=JSON.parse(metadata)}catch{return null}if(typeof parsed!=="object"||parsed===null)return null;let sig=parsed.groupsSignature;return typeof sig==="string"?sig:null}async function readGroupDefinitions(sql,parentId){let children=await sql`SELECT id, group_name FROM tasks WHERE parent_id = ${parentId} ORDER BY created_at`;if(children.length===0)return[];let childIds=children.map((c)=>c.id),deps=await sql`
|
|
1254
|
+
SELECT td.task_id, t.group_name AS dep_group
|
|
1255
|
+
FROM task_dependencies td
|
|
1256
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
1257
|
+
WHERE td.task_id = ANY(${childIds})
|
|
1258
|
+
`,depsByTask={};for(let dep of deps){let taskId=dep.task_id;if(!depsByTask[taskId])depsByTask[taskId]=[];depsByTask[taskId].push(dep.dep_group)}return children.map((c)=>({name:c.group_name,dependsOn:depsByTask[c.id]??[]}))}async function getState(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return null;let children=await sql`SELECT * FROM tasks WHERE parent_id = ${parent.id} ORDER BY created_at`;if(children.length===0)return null;let childIds=children.map((c)=>c.id),deps=await sql`
|
|
1251
1259
|
SELECT td.task_id, t.group_name AS dep_group
|
|
1252
1260
|
FROM task_dependencies td
|
|
1253
1261
|
JOIN tasks t ON t.id = td.depends_on_id
|
|
@@ -1255,7 +1263,7 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1255
1263
|
`,depsMap={};for(let dep of deps){let taskId=dep.task_id;if(!depsMap[taskId])depsMap[taskId]=[];depsMap[taskId].push(dep.dep_group)}let actors=await sql`
|
|
1256
1264
|
SELECT task_id, actor_id FROM task_actors
|
|
1257
1265
|
WHERE task_id = ANY(${childIds}) AND role = 'assignee'
|
|
1258
|
-
`,assigneeMap={};for(let actor of actors)assigneeMap[actor.task_id]=actor.actor_id;let groups={};for(let child of children){let{id,group_name:groupName}=child;groups[groupName]={status:child.status,assignee:assigneeMap[id],dependsOn:depsMap[id]??[],startedAt:toISO(child.started_at),completedAt:toISO(child.ended_at)}}return{wish:slug,groups,createdAt:toISO(parent.created_at)??"",updatedAt:toISO(parent.updated_at)??""}}async function getGroupState(slug,groupName,cwd){let state=await getState(slug,cwd);if(!state)return null;return state.groups[groupName]??null}async function isWishComplete(slug,cwd){let state=await getState(slug,cwd);if(!state)return!1;let groups=Object.values(state.groups);return groups.length>0&&groups.every((g)=>g.status==="done")}async function resetInProgressGroups(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return 0;let now=new Date,inProgress=await sql`
|
|
1266
|
+
`,assigneeMap={};for(let actor of actors)assigneeMap[actor.task_id]=actor.actor_id;let groups={};for(let child of children){let{id,group_name:groupName}=child;groups[groupName]={status:child.status,assignee:assigneeMap[id],dependsOn:depsMap[id]??[],startedAt:toISO(child.started_at),completedAt:toISO(child.ended_at)}}return{wish:slug,groups,createdAt:toISO(parent.created_at)??"",updatedAt:toISO(parent.updated_at)??""}}async function getGroupState(slug,groupName,cwd){let state=await getState(slug,cwd);if(!state)return null;return state.groups[groupName]??null}async function isWishComplete(slug,cwd){let state=await getState(slug,cwd);if(!state)return!1;let groups=Object.values(state.groups);return groups.length>0&&groups.every((g)=>g.status==="done")}async function wipeState(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return!1;return await sql`DELETE FROM tasks WHERE id = ${parent.id}`,!0}async function resetInProgressGroups(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return 0;let now=new Date,inProgress=await sql`
|
|
1259
1267
|
SELECT id FROM tasks
|
|
1260
1268
|
WHERE parent_id = ${parent.id} AND status = 'in_progress'
|
|
1261
1269
|
`;if(inProgress.length===0)return 0;let ids=inProgress.map((r)=>r.id);return await sql`
|
|
@@ -1263,7 +1271,8 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1263
1271
|
WHERE id = ANY(${ids})
|
|
1264
1272
|
`,await sql`
|
|
1265
1273
|
DELETE FROM task_actors WHERE task_id = ANY(${ids}) AND role = 'assignee'
|
|
1266
|
-
`,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()})
|
|
1274
|
+
`,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema,WishStateMismatchError;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()});WishStateMismatchError=class WishStateMismatchError extends Error{slug;added;removed;changed;constructor(slug,added,removed,changed){let lines=[`Wish "${slug}" group structure has changed since state was created.`];if(added.length>0)lines.push(` + added: ${added.join(", ")}`);if(removed.length>0)lines.push(` - removed: ${removed.join(", ")}`);if(changed.length>0)lines.push(` ~ changed deps: ${changed.join(", ")}`);lines.push(""),lines.push(`Run \`genie reset ${slug}\` to recreate state from the current WISH.md.`);super(lines.join(`
|
|
1275
|
+
`));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps});import{exec as exec2}from"child_process";import{readFile as readFile6}from"fs/promises";import{join as join31}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let teamConfig=await getTeam(team);if(teamConfig?.nativeTeamParentSessionId)return teamConfig.nativeTeamParentSessionId;return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),session=(await getTeam(team))?.tmuxSessionName??await resolveRepoSession(repoPath)??await getCurrentSessionName()??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,claudeSessionId:effectiveSessionId,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude)await registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId);if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),await tryAutoBrain(workerId,repoPath),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await _deps.findAnyGroupByAssignee(workerId,repoPath)??await _deps.findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join31(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile6(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
|
|
1267
1276
|
${groupSection}`:"","",gitLog?`Last git log:
|
|
1268
1277
|
${gitLog}`:"","",gitStatus?`Uncommitted changes:
|
|
1269
1278
|
${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
|
|
@@ -3166,7 +3175,7 @@ No agents registered. Add one with: genie dir add <name> --dir <path>`),console.
|
|
|
3166
3175
|
`);return}if(entries.length>0)printRegisteredTable(entries),printResolvedTable(entries);if(includeBuiltins)printBuiltinsTable()}function listEntriesJson(entries,includeBuiltins){let result2=entries.map((e)=>({...e,builtin:!1}));if(includeBuiltins)for(let b2 of ALL_BUILTINS)result2.push({name:b2.name,description:b2.description,model:b2.model,category:b2.category,scope:"built-in",builtin:!0});let{writeSync}=__require("fs"),data=`${JSON.stringify(result2,null,2)}
|
|
3167
3176
|
`,CHUNK=4096;for(let i2=0;i2<data.length;i2+=CHUNK)writeSync(1,data.slice(i2,i2+CHUNK))}function registerSdkFlags(cmd){cmd.option("--sdk-permission-mode <mode>","SDK permission mode: default|acceptEdits|bypassPermissions|plan|dontAsk|auto").option("--sdk-tools <list>","SDK tools: comma-separated tool names").option("--sdk-allowed-tools <list>","SDK auto-approved tools: comma-separated").option("--sdk-disallowed-tools <list>","SDK blacklisted tools: comma-separated").option("--sdk-max-turns <n>","SDK max conversation turns").option("--sdk-max-budget <usd>","SDK max budget in USD").option("--sdk-effort <level>","SDK effort: low|medium|high|max").option("--sdk-thinking <config>","SDK thinking: adaptive|disabled|enabled[:budgetTokens]").option("--sdk-persist-session","SDK: enable session persistence").option("--no-sdk-persist-session","SDK: disable session persistence").option("--sdk-file-checkpointing","SDK: enable file checkpointing").option("--sdk-output-format <path>","SDK: path to JSON schema file for output format").option("--sdk-stream-partial","SDK: include partial messages in stream").option("--sdk-hook-events","SDK: include hook events in stream").option("--sdk-prompt-suggestions","SDK: enable prompt suggestions").option("--sdk-progress-summaries","SDK: enable agent progress summaries").option("--sdk-sandbox","SDK: enable sandbox").option("--sdk-betas <list>","SDK beta flags: comma-separated").option("--sdk-system-prompt <string>","SDK system prompt text").option("--sdk-mcp-server <spec>","SDK MCP server: name:command:args (repeatable)",collectRepeat,[]).option("--sdk-plugin <path>","SDK plugin path (repeatable)",collectRepeat,[]).option("--sdk-agent <name>","SDK main agent name").option("--sdk-subagent <spec>","SDK subagent: name:json (repeatable)",collectRepeat,[])}function collectRepeat(value,previous){return previous.concat([value])}function buildSdkConfig(options){let config={};return applyScalarSdkOptions(config,options),applyBooleanSdkOptions(config,options),applyRepeatableSdkOptions(config,options),Object.keys(config).length>0?config:void 0}function toSafeNumber(value,flagName){let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}function applyScalarSdkOptions(config,options){if(options.sdkPermissionMode!==void 0)config.permissionMode=options.sdkPermissionMode;if(options.sdkTools!==void 0)config.tools=splitComma(options.sdkTools);if(options.sdkAllowedTools!==void 0)config.allowedTools=splitComma(options.sdkAllowedTools);if(options.sdkDisallowedTools!==void 0)config.disallowedTools=splitComma(options.sdkDisallowedTools);if(options.sdkMaxTurns!==void 0)config.maxTurns=toSafeNumber(options.sdkMaxTurns,"--sdk-max-turns");if(options.sdkMaxBudget!==void 0)config.maxBudgetUsd=toSafeNumber(options.sdkMaxBudget,"--sdk-max-budget");if(options.sdkEffort!==void 0)config.effort=options.sdkEffort;if(options.sdkThinking!==void 0)config.thinking=parseThinkingConfig(options.sdkThinking);if(options.sdkBetas!==void 0)config.betas=splitComma(options.sdkBetas);if(options.sdkSystemPrompt!==void 0)config.systemPrompt=options.sdkSystemPrompt;if(options.sdkAgent!==void 0)config.agent=options.sdkAgent;if(options.sdkOutputFormat!==void 0)config.outputFormat={type:"json_schema",schema:{$ref:options.sdkOutputFormat}}}function applyBooleanSdkOptions(config,options){if(options.sdkPersistSession!==void 0)config.persistSession=options.sdkPersistSession;if(options.sdkFileCheckpointing===!0)config.enableFileCheckpointing=!0;if(options.sdkStreamPartial===!0)config.includePartialMessages=!0;if(options.sdkHookEvents===!0)config.includeHookEvents=!0;if(options.sdkPromptSuggestions===!0)config.promptSuggestions=!0;if(options.sdkProgressSummaries===!0)config.agentProgressSummaries=!0;if(options.sdkSandbox===!0)config.sandbox={enabled:!0}}function applyRepeatableSdkOptions(config,options){if(options.sdkMcpServer&&options.sdkMcpServer.length>0){config.mcpServers={};for(let spec of options.sdkMcpServer){let parsed=parseMcpServer(spec);config.mcpServers[parsed.name]=parsed.config}}if(options.sdkPlugin&&options.sdkPlugin.length>0)config.plugins=options.sdkPlugin.map((p)=>({type:"local",path:p}));if(options.sdkSubagent&&options.sdkSubagent.length>0)config.agents=parseSubagents(options.sdkSubagent)}function parseSubagents(specs){let agents={};for(let spec of specs){let colonIdx=spec.indexOf(":");if(colonIdx===-1)throw Error(`Invalid --sdk-subagent format: "${spec}". Expected "name:json".`);let agentName=spec.slice(0,colonIdx),jsonStr=spec.slice(colonIdx+1);try{agents[agentName]=JSON.parse(jsonStr)}catch{throw Error(`Invalid JSON in --sdk-subagent "${agentName}": ${jsonStr}`)}}return agents}function parseThinkingConfig(value){if(value==="adaptive")return{type:"adaptive"};if(value==="disabled")return{type:"disabled"};if(value==="enabled")return{type:"enabled"};if(value.startsWith("enabled:"))return{type:"enabled",budgetTokens:toSafeNumber(value.slice(8),"--sdk-thinking budgetTokens")};throw Error(`Invalid --sdk-thinking value: "${value}". Expected adaptive|disabled|enabled[:budgetTokens].`)}function parseMcpServer(spec){let firstColon=spec.indexOf(":");if(firstColon===-1)throw Error(`Invalid --sdk-mcp-server format: "${spec}". Expected "name:command:args".`);let name=spec.slice(0,firstColon),rest=spec.slice(firstColon+1),secondColon=rest.indexOf(":");if(secondColon===-1)throw Error(`Invalid --sdk-mcp-server format: "${spec}". Expected "name:command:args".`);let command=rest.slice(0,secondColon),argsStr=rest.slice(secondColon+1),args=argsStr?argsStr.split(",").map((s)=>s.trim()).filter(Boolean):[];return{name,config:{type:"stdio",command,args}}}function splitComma(value){return value.split(",").map((s)=>s.trim()).filter(Boolean)}function printSdkConfig(sdk){console.log(" SDK Config:");let lines=collectSdkDisplayLines(sdk);for(let line of lines)console.log(` ${line}`)}function collectSdkDisplayLines(sdk){let lines=[];if(sdk.permissionMode)lines.push(`Permission Mode: ${sdk.permissionMode}`);if(sdk.tools)lines.push(`Tools: ${Array.isArray(sdk.tools)?sdk.tools.join(", "):`preset:${sdk.tools.preset}`}`);if(sdk.allowedTools?.length)lines.push(`Allowed Tools: ${sdk.allowedTools.join(", ")}`);if(sdk.disallowedTools?.length)lines.push(`Disallowed Tools: ${sdk.disallowedTools.join(", ")}`);if(sdk.maxTurns!==void 0)lines.push(`Max Turns: ${sdk.maxTurns}`);if(sdk.maxBudgetUsd!==void 0)lines.push(`Max Budget: $${sdk.maxBudgetUsd.toFixed(2)}`);if(sdk.effort)lines.push(`Effort: ${sdk.effort}`);if(sdk.thinking)lines.push(`Thinking: ${formatThinking(sdk.thinking)}`);if(sdk.agent)lines.push(`Agent: ${sdk.agent}`);if(sdk.persistSession!==void 0)lines.push(`Persist Session: ${sdk.persistSession}`);if(sdk.enableFileCheckpointing)lines.push("File Checkpointing: enabled");if(sdk.outputFormat)lines.push(`Output Format: ${JSON.stringify(sdk.outputFormat.schema)}`);return collectSdkBooleanLines(sdk,lines),collectSdkComplexLines(sdk,lines),lines}function collectSdkBooleanLines(sdk,lines){if(sdk.includePartialMessages)lines.push("Stream Partial: enabled");if(sdk.includeHookEvents)lines.push("Hook Events: enabled");if(sdk.promptSuggestions)lines.push("Prompt Suggestions: enabled");if(sdk.agentProgressSummaries)lines.push("Progress Summaries: enabled");if(sdk.sandbox?.enabled)lines.push("Sandbox: enabled");if(sdk.betas?.length)lines.push(`Betas: ${sdk.betas.join(", ")}`)}function collectSdkComplexLines(sdk,lines){if(sdk.systemPrompt){let prompt2=typeof sdk.systemPrompt==="string"?sdk.systemPrompt:`preset:${sdk.systemPrompt.preset}`;lines.push(`System Prompt: ${prompt2.length>60?`${prompt2.slice(0,60)}...`:prompt2}`)}if(sdk.mcpServers)lines.push(`MCP Servers: ${Object.keys(sdk.mcpServers).join(", ")}`);if(sdk.plugins?.length)lines.push(`Plugins: ${sdk.plugins.map((p)=>p.path).join(", ")}`);if(sdk.agents)lines.push(`Subagents: ${Object.keys(sdk.agents).join(", ")}`)}function formatThinking(thinking){if(thinking.type==="enabled"&&thinking.budgetTokens)return`enabled:${thinking.budgetTokens}`;return thinking.type}function buildPermissions(permissionPreset,allow,bashAllow){if(!permissionPreset&&!allow&&!bashAllow)return;if(permissionPreset)return{preset:permissionPreset};return{...allow&&{allow:allow.split(",").map((s)=>s.trim()).filter(Boolean)},...bashAllow&&{bashAllowPatterns:bashAllow.split(",").map((s)=>s.trim()).filter(Boolean)}}}function normalizeRoles(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}function printRegisteredTable(entries){let repoValues=[],roleValues=[];for(let entry of entries)repoValues.push(entry.repo?contractPath(entry.repo):contractPath(entry.dir)),roleValues.push(entry.roles?.join(", ")||"-");let termW=process.stdout.columns||120,fixedW=54,maxRepoLen=Math.max(4,...repoValues.map((v)=>v.length)),repoW=Math.min(maxRepoLen+2,Math.max(30,termW-fixedW-20)),totalW=fixedW+repoW+20;console.log(""),console.log("REGISTERED AGENTS"),console.log("-".repeat(Math.max(90,totalW))),console.log(` ${"NAME".padEnd(22)}${"SCOPE".padEnd(10)}${"REPO".padEnd(repoW)}${"MODEL".padEnd(10)}${"PROVIDER".padEnd(10)}ROLES`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(repoW-2)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(20)}`);for(let i2=0;i2<entries.length;i2++){let entry=entries[i2],repo=repoValues[i2],roles=roleValues[i2];console.log(` ${entry.name.padEnd(22)}${entry.scope.padEnd(10)}${repo.padEnd(repoW)}${(entry.model||"-").padEnd(10)}${(entry.provider||"-").padEnd(10)}${roles}`)}console.log("")}function printResolvedTable(entries){if(entries.length===0)return;let nameW=22,declW=14,resolvedW=14;console.log("RESOLVED DEFAULTS"),console.log("-".repeat(70));for(let field of RESOLVED_FIELDS){let fieldUpper=field.toUpperCase();console.log(` ${"AGENT".padEnd(nameW)}${`${fieldUpper} (declared)`.padEnd(declW)}${`${fieldUpper} (resolved)`.padEnd(resolvedW)}SOURCE`),console.log(` ${"-".repeat(nameW-2)} ${"-".repeat(declW-2)} ${"-".repeat(resolvedW-2)} ${"-".repeat(16)}`);for(let entry of entries){let ctx=buildDirResolveContext(entry.name),result2=resolveFieldWithSource(entry,field,ctx),declared=entry[field]||"-";console.log(` ${entry.name.padEnd(nameW)}${declared.padEnd(declW)}${result2.value.padEnd(resolvedW)}${result2.source}`)}}console.log("")}function printBuiltinsTable(){console.log("BUILT-IN AGENTS"),console.log("-".repeat(80)),console.log(` ${"NAME".padEnd(22)}${"TYPE".padEnd(10)}${"MODEL".padEnd(8)}DESCRIPTION`),console.log(` ${"-".repeat(20)} ${"-".repeat(8)} ${"-".repeat(6)} ${"-".repeat(30)}`);for(let agent of ALL_BUILTINS)console.log(` ${agent.name.padEnd(22)}${agent.category.padEnd(10)}${(agent.model||"-").padEnd(8)}${agent.description}`);console.log("")}function validatePromptMode2(mode){if(mode!=="system"&&mode!=="append")throw Error(`Invalid prompt mode "${mode}". Must be "append" or "system".`);return mode}function normalizeRoles2(roles){if(!roles)return;return roles.flatMap((r)=>r.split(",")).map((r)=>r.trim()).filter(Boolean)}function printEntry2(entry){if(console.log(` Name: ${entry.name}`),console.log(` Dir: ${contractPath(entry.dir)}`),entry.repo)console.log(` Repo: ${contractPath(entry.repo)}`);if(console.log(` Prompt mode: ${entry.promptMode}`),entry.model)console.log(` Model: ${entry.model}`);if(entry.roles?.length)console.log(` Roles: ${entry.roles.join(", ")}`);console.log(` Registered: ${entry.registeredAt}`)}async function handleOmniRegistration(name,options){let omniUrl=await resolveOmniApiUrl();if(!omniUrl)return;console.log(`
|
|
3168
3177
|
Registering in Omni (${omniUrl})...`);let existingId=await findOmniAgent(name);if(existingId){console.log(` Agent already exists in Omni: ${existingId}`),await edit(name,{omniAgentId:existingId},{global:options.global}),console.log(" Linked existing Omni agent to directory entry.");return}let omniAgentId=await registerAgentInOmni(name,{model:options.model,roles:options.roles});if(omniAgentId)await edit(name,{omniAgentId},{global:options.global}),console.log(` Omni agent created: ${omniAgentId}`),console.log(" Session isolation: per-person + per-channel")}async function handleAgentRegister(name,options){let promptMode=validatePromptMode2(options.promptMode);if(options.repo)validateRepoPath(options.repo);let roles=normalizeRoles2(options.roles),entry=await add({name,dir:resolvePath2(options.dir),repo:options.repo?resolvePath2(options.repo):void 0,promptMode,model:options.model,roles},{global:options.global}),scope=options.global?"global":"project";if(console.log(`Agent "${entry.name}" registered (${scope}).`),printEntry2(entry),!options.skipOmni)await handleOmniRegistration(name,{...options,roles})}function registerAgentRegister(parent){parent.command("register <name>").description("Register an agent locally and auto-register in Omni when configured").requiredOption("--dir <path>","Agent folder (CWD + AGENTS.md)").option("--repo <path>","Default git repo (overridden by team)").option("--prompt-mode <mode>","Prompt mode: append or system","append").option("--model <model>","Default model (sonnet, opus, codex)").option("--roles <roles...>","Built-in roles this agent can orchestrate").option("--global","Write to global directory instead of project").option("--skip-omni","Skip Omni auto-registration").action(async(name,options)=>{try{await handleAgentRegister(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentResume(parent){parent.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_msg();function registerAgentSend(parent){parent.command("send <body>").description("Send a direct message to an agent (hierarchy-enforced)").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("--broadcast","Send to all direct reports").action(async(body,options)=>{try{let from=options.from??await detectSenderIdentity(options.team);if(options.broadcast){await handleBroadcast(from,body,options.team);return}await handleDirectMessage(from,options.to,body,options.team)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}async function isTeamLeader(agentName,teamName){try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leaderName=await resolveLeaderName2(teamName);return agentName===leaderName}catch{return!1}}async function checkHierarchy(from,to){if(from==="cli")return{allowed:!0};if(from===to)return{allowed:!0};try{let agents=await(await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry))).listAgents({}),sender=agents.find((a)=>a.customName===from||a.role===from||a.id===from),recipient=agents.find((a)=>a.customName===to||a.role===to||a.id===to);if(!sender||!recipient)return{allowed:!0};if(recipient.reportsTo===from||recipient.reportsTo===sender.id)return{allowed:!0};if(sender.reportsTo===to||sender.reportsTo===recipient.id)return{allowed:!0};if(sender.reportsTo&&sender.reportsTo===recipient.reportsTo)return{allowed:!0};if(sender.team&&sender.team===recipient.team&&await isTeamLeader(from,sender.team))return{allowed:!0};let manager=sender.reportsTo??"your manager";return{allowed:!1,reason:`Cannot reach "${to}". Escalate to ${manager}.`}}catch{return{allowed:!0}}}async function handleDirectMessage(from,to,body,team){let{allowed,reason}=await checkHierarchy(from,to);if(!allowed)console.error(`Error: ${reason}`),process.exit(1);let{checkSendScope:checkSendScope2}=await Promise.resolve().then(() => (init_msg(),exports_msg)),repoPath=process.cwd(),scopeError=await checkSendScope2(repoPath,from,to);if(scopeError)console.error(`Error: ${scopeError}`),process.exit(1);let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),mailbox=await Promise.resolve().then(() => (init_mailbox(),exports_mailbox)),senderActor={actorType:"local",actorId:from},recipientActor={actorType:"local",actorId:to},conv=await taskService.findOrCreateConversation({type:"dm",members:[senderActor,recipientActor],createdBy:senderActor});await taskService.addMember(conv.id,senderActor),await taskService.addMember(conv.id,recipientActor),await mailbox.send(repoPath,from,to,body);let msg=await taskService.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{}try{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=team??process.env.GENIE_TEAM;if(currentTeam){let nativeName=await nativeTeams.resolveNativeMemberName(currentTeam,to);if(nativeName)await nativeTeams.writeNativeInbox(currentTeam,nativeName,nativeMsg)}}catch{}console.log(`Message sent to "${to}".`),console.log(` ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}async function handleBroadcast(from,body,team){let taskService=await Promise.resolve().then(() => (init_task_service(),exports_task_service)),repoPath=process.cwd(),teamName=team??process.env.GENIE_TEAM;if(!teamName)console.error("Error: Could not detect team. Use --team <name>."),process.exit(1);let senderActor={actorType:"local",actorId:from},conv=await taskService.findOrCreateConversation({type:"group",name:`Team: ${teamName}`,linkedEntity:"team",linkedEntityId:teamName,createdBy:senderActor,members:[senderActor]});await taskService.addMember(conv.id,senderActor);let msg=await taskService.sendMessage(conv.id,senderActor,body);try{let{publishSubjectEvent:publishSubjectEvent2}=await Promise.resolve().then(() => (init_runtime_events(),exports_runtime_events));await publishSubjectEvent2(repoPath,"genie.msg.broadcast",{kind:"message",agent:from,direction:"out",peer:teamName,text:body,data:{messageId:msg.id,conversationId:conv.id,from,team:teamName},source:"mailbox"})}catch{}console.log(`Broadcast sent to team "${teamName}".`),console.log(` Message ID: ${msg.id}`),console.log(` Conversation: ${conv.id}`)}init_term_format();function printAgentFields(agent){if(console.log(""),console.log(`AGENT: ${agent.customName??agent.role??agent.id}`),console.log("\u2500".repeat(60)),console.log(` ${padRight("ID:",20)} ${agent.id}`),agent.role)console.log(` ${padRight("Role:",20)} ${agent.role}`);if(agent.customName)console.log(` ${padRight("Name:",20)} ${agent.customName}`);if(agent.team)console.log(` ${padRight("Team:",20)} ${agent.team}`);console.log(` ${padRight("Started:",20)} ${agent.startedAt}`)}function printExecutorFields(executor){if(console.log(""),console.log("Current Executor:"),console.log("\u2500".repeat(60)),console.log(` ${padRight("Executor ID:",20)} ${executor.id}`),console.log(` ${padRight("Provider:",20)} ${executor.provider}`),console.log(` ${padRight("Transport:",20)} ${executor.transport}`),console.log(` ${padRight("State:",20)} ${executor.state}`),executor.pid)console.log(` ${padRight("PID:",20)} ${executor.pid}`);if(executor.tmuxSession)console.log(` ${padRight("Tmux Session:",20)} ${executor.tmuxSession}`);if(executor.tmuxPaneId)console.log(` ${padRight("Tmux Pane:",20)} ${executor.tmuxPaneId}`);if(executor.worktree)console.log(` ${padRight("Worktree:",20)} ${executor.worktree}`);if(console.log(` ${padRight("Started:",20)} ${executor.startedAt}`),executor.endedAt)console.log(` ${padRight("Ended:",20)} ${executor.endedAt}`)}async function showAgent(name,json2){let registry=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),executorRegistry=await Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),agent=(await registry.listAgents({team:process.env.GENIE_TEAM})).find((a)=>a.customName===name||a.role===name||a.id===name);if(!agent)console.error(`Agent "${name}" not found.`),process.exit(1);if(json2){let executor=agent.currentExecutorId?await executorRegistry.getExecutor(agent.currentExecutorId):null;console.log(JSON.stringify({agent,executor},null,2));return}if(printAgentFields(agent),agent.currentExecutorId){let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(executor)printExecutorFields(executor)}else console.log(`
|
|
3169
|
-
No active executor.`);console.log("")}function registerAgentShow(parent){parent.command("show <name>").description("Show agent identity and current executor detail").option("--json","Output as JSON").action(async(name,options)=>{try{await showAgent(name,options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function parseNumericFlag(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}function registerAgentSpawn(parent){parent.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude, codex, or claude-sdk").option("--team <team>","Team name"
|
|
3178
|
+
No active executor.`);console.log("")}function registerAgentShow(parent){parent.command("show <name>").description("Show agent identity and current executor detail").option("--json","Output as JSON").action(async(name,options)=>{try{await showAgent(name,options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function parseNumericFlag(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}function registerAgentSpawn(parent){parent.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude, codex, or claude-sdk").option("--team <team>","Team name").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--prompt <prompt>","Initial prompt (first user message)").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").action(async(name,options)=>{if(options.prompt)options.initialPrompt=options.prompt;try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}init_agents();function registerAgentStop(parent){parent.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}function registerAgentCommands(program2){let agent=program2.command("agent").description("Agent lifecycle management");registerAgentSpawn(agent),registerAgentStop(agent),registerAgentResume(agent),registerAgentKill(agent),registerAgentList(agent),registerAgentShow(agent),registerAgentAnswer(agent),registerAgentRegister(agent),registerAgentDirectory(agent),registerAgentInbox(agent),registerAgentBrief(agent),registerAgentLog(agent),registerAgentSend(agent),agent.on("command:*",(operands)=>{let cmd=operands[0],available=agent.commands.map((c)=>c.name()).join(", ");agent.error(`Unknown agent command '${cmd}'. Available: ${available}`)})}init_agents();async function handleTuiMode(){let{isServeRunning:isServeRunning2,autoStartServe:autoStartServe2}=await Promise.resolve().then(() => (init_serve(),exports_serve));if(!isServeRunning2())console.log("Starting genie serve..."),await autoStartServe2();let{attachTuiSession:attachTuiSession2}=await Promise.resolve().then(() => (init_tmux2(),exports_tmux2));attachTuiSession2()}async function findTauriBinary(){let{existsSync:existsSync30}=await import("fs"),{join:join39,dirname:dirname7}=await import("path"),{execSync:execSync10}=await import("child_process"),appName="genie-desktop",rootDir=join39(dirname7(new URL(import.meta.url).pathname),"..",".."),localBin=[join39(rootDir,"packages","genie-app","src-tauri","target","release","genie-desktop"),join39(rootDir,"packages","genie-app","src-tauri","target","debug","genie-desktop"),join39(rootDir,"dist","app","genie-desktop"),"/usr/local/bin/genie-desktop"].find((p)=>existsSync30(p));if(localBin)return localBin;try{return execSync10("which genie-desktop",{stdio:"ignore"}),"genie-desktop"}catch{return}}function registerAppCommand(program2){program2.command("app").description("Launch Genie desktop app (backend sidecar + views)").option("--backend-only","Start only the backend sidecar (IPC on stdin/stdout)").option("--tui","Fall back to terminal UI mode").option("--dev","Development mode").action(async(options)=>{if(options.tui){await handleTuiMode();return}if(options.backendOnly){await Promise.resolve().then(() => (init_src_backend(),exports_src_backend));return}let tauriBin=await findTauriBinary();if(tauriBin){console.log("\x1B[35m\u25C6 Genie App\x1B[0m Launching desktop...");let{execFileSync}=await import("child_process");try{execFileSync(tauriBin,[],{stdio:"inherit"})}catch{}return}console.log("\x1B[35m\u25C6 Genie App\x1B[0m Starting backend sidecar..."),console.log("\x1B[2mDesktop binary not found \u2014 running in sidecar mode.\x1B[0m"),console.log("\x1B[2mPG bridge + PTY manager + IPC on stdin/stdout\x1B[0m"),console.log(`\x1B[2mUse --tui for terminal UI, or pipe to a frontend shell.\x1B[0m
|
|
3170
3179
|
`),await Promise.resolve().then(() => (init_src_backend(),exports_src_backend))})}init_claude_sdk_remote_approval();init_workspace();async function handleRequest(options){let ws=findWorkspace(),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0,timeoutSec=options.timeout?Number(options.timeout):permissions?.timeout??300,defaultAction=permissions?.defaultAction??"deny",timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(`cli-${process.pid}`,options.agent,options.tool,options.input,timeoutAt);if(console.log(`Approval created: ${approvalId}`),options.wait){console.log(`Waiting for resolution (timeout: ${timeoutSec}s, default: ${defaultAction})...`);let decision=await waitForResolution(approvalId,timeoutAt,defaultAction);if(console.log(`Decision: ${decision}`),decision==="deny")process.exit(1)}}async function handleResolve(id,options){if(options.decision!=="allow"&&options.decision!=="deny")console.error('Error: --decision must be "allow" or "deny"'),process.exit(1);let actor=process.env.GENIE_AGENT_NAME||options.by;if(await resolveApproval(id,options.decision,actor))console.log(`Approval ${id} resolved: ${options.decision} by ${options.by}`);else console.error(`Error: Approval ${id} not found or already resolved`),process.exit(1)}async function handleList(options){let rows=await listPendingApprovals(options.agent);if(options.json){console.log(JSON.stringify(rows,null,2));return}if(rows.length===0){console.log("No pending approvals.");return}console.log(` ${"ID".padEnd(38)} ${"AGENT".padEnd(20)} ${"TOOL".padEnd(15)} TIMEOUT`),console.log(` ${"\u2500".repeat(85)}`);for(let row of rows){let timeout=new Date(row.timeout_at).toLocaleTimeString();console.log(` ${String(row.id).padEnd(38)} ${String(row.agent_name).padEnd(20)} ${String(row.tool_name).padEnd(15)} ${timeout}`)}console.log(`
|
|
3171
3180
|
${rows.length} pending approval${rows.length===1?"":"s"}`)}function registerApprovalCommands(program2){let approval=program2.command("approval").description("Remote approval queue management");approval.command("request").description("Create an approval request (for tmux-path agents)").requiredOption("--tool <name>","Tool name requiring approval").requiredOption("--input <preview>","Tool input preview text").requiredOption("--agent <name>","Agent name requesting approval").option("--wait","Block until the approval is resolved").option("--timeout <seconds>","Timeout in seconds (overrides workspace config)").action(async(options)=>{try{await handleRequest(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),approval.command("resolve <id>").description("Resolve a pending approval").requiredOption("--decision <decision>","Decision: allow or deny").option("--by <actor>",'Display label for decision maker (defaults to GENIE_AGENT_NAME or "cli")',"cli").action(async(id,options)=>{try{await handleResolve(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),approval.command("list").description("List pending approvals").option("--agent <name>","Filter by agent name").option("--json","Output as JSON").action(async(options)=>{try{await handleList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_audit();init_term_format();function printEventsTable(rows){if(rows.length===0){console.log("No audit events found.");return}let sorted=[...rows].reverse(),headers=["Time","Type","Entity","Event","Actor","Details"],data=sorted.map((r)=>[formatRelativeTimestamp(r.created_at),r.entity_type,r.entity_id,r.event_type,r.actor??"-",summarizeDetails(r.details)]),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(`
|
|
3172
3181
|
(${rows.length} event${rows.length===1?"":"s"})`)}function summarizeDetails(details){if(!details)return"";if(typeof details==="string")try{return summarizeDetails(JSON.parse(details))}catch{return details.slice(0,40)}if(Object.keys(details).length===0)return"";let keys=Object.keys(details);if(keys.length===1){let val=details[keys[0]];if(typeof val==="string")return val.slice(0,40);return JSON.stringify(val).slice(0,40)}if(details.error)return`error: ${String(details.error).slice(0,35)}`;if(details.duration_ms)return`${details.duration_ms}ms`;return JSON.stringify(details).slice(0,40)}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(`
|
|
@@ -3258,9 +3267,9 @@ Done. ${results.length} migration${results.length===1?"":"s"} applied.`),await s
|
|
|
3258
3267
|
`,count=Number(rows[0].cnt);console.log(`Would delete ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}else{let result2=await sql`
|
|
3259
3268
|
DELETE FROM genie_runtime_events
|
|
3260
3269
|
WHERE created_at < now() - make_interval(secs => ${intervalSec})
|
|
3261
|
-
`,count=Number(result2.count);console.log(`Deleted ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Prune failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("prune-events").description("Prune old runtime events beyond retention period").option("--older-than <duration>","Delete events older than (e.g., 30d, 7d)","14d").option("--dry-run","Show count without deleting").action(dbPruneEventsCommand),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_protocol_router();import{execSync as execSync14}from"child_process";import{existsSync as existsSync39}from"fs";import{mkdir as mkdir6,readFile as readFile9,writeFile as writeFile3}from"fs/promises";import{tmpdir as tmpdir3}from"os";import{join as join48}from"path";init_tmux();import{existsSync as existsSync34}from"fs";import{join as join43}from"path";var REPOS_BASE="/home/genie/workspace/repos";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join43(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync34(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join43(REPOS_BASE,parsed.namespace);if(!existsSync34(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join43(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync34(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}init_wish_state();init_agents();init_term_format();init_wish_state();import{execSync as execSync12}from"child_process";import{existsSync as existsSync35}from"fs";import{readFile as readFile8}from"fs/promises";import{dirname as dirname9,join as join44}from"path";async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync35(logicalPath)?logicalPath:path3}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join44(base,".genie","wishes",slug,"WISH.md");if(existsSync35(cwdPath))return cwdPath;try{let commonDir=execSync12("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname9(commonDir));if(repoRoot!==base){let repoPath=join44(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync35(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile8(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync12("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync12("git add -A",{encoding:"utf-8"}),execSync12(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync12("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync12("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync12("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync12(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync12(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leader=await teamManager.resolveLeaderName(teamName),config=await teamManager.getTeam(teamName);return{leader,spawner:config?.spawner}}catch{return{leader:teamName}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result2=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result2&&typeof result2==="object"&&"delivered"in result2&&!result2.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result2=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result2.completedAt)console.log(` Completed at: ${formatTimestamp(result2.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry:registry2,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry2.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
|
|
3270
|
+
`,count=Number(result2.count);console.log(`Deleted ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Prune failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("prune-events").description("Prune old runtime events beyond retention period").option("--older-than <duration>","Delete events older than (e.g., 30d, 7d)","14d").option("--dry-run","Show count without deleting").action(dbPruneEventsCommand),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_protocol_router();import{execSync as execSync14}from"child_process";import{existsSync as existsSync39}from"fs";import{mkdir as mkdir6,readFile as readFile9,writeFile as writeFile3}from"fs/promises";import{tmpdir as tmpdir3}from"os";import{join as join48}from"path";init_tmux();import{existsSync as existsSync34}from"fs";import{join as join43}from"path";var REPOS_BASE="/home/genie/workspace/repos";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join43(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync34(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join43(REPOS_BASE,parsed.namespace);if(!existsSync34(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join43(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync34(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}init_wish_state();init_agents();init_interactivity();init_term_format();init_wish_state();import{execSync as execSync12}from"child_process";import{existsSync as existsSync35}from"fs";import{readFile as readFile8}from"fs/promises";import{dirname as dirname9,join as join44}from"path";async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync35(logicalPath)?logicalPath:path3}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join44(base,".genie","wishes",slug,"WISH.md");if(existsSync35(cwdPath))return cwdPath;try{let commonDir=execSync12("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname9(commonDir));if(repoRoot!==base){let repoPath=join44(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync35(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile8(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync12("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync12("git add -A",{encoding:"utf-8"}),execSync12(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync12("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync12("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync12("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync12(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync12(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leader=await teamManager.resolveLeaderName(teamName),config=await teamManager.getTeam(teamName);return{leader,spawner:config?.spawner}}catch{return{leader:teamName}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result2=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result2&&typeof result2==="object"&&"delivered"in result2&&!result2.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result2=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result2.completedAt)console.log(` Completed at: ${formatTimestamp(result2.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry:registry2,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry2.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
|
|
3262
3271
|
Active Executors:`),console.log("\u2500".repeat(60));for(let line of executorInfoLines)console.log(line)}}catch{}}async function statusCommand(slug){try{let state=await getState(slug)??await autoInitWishState(slug);console.log(`
|
|
3263
|
-
Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").description("Reset
|
|
3272
|
+
Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").option("-y, --yes","Skip confirmation prompt (required in non-interactive mode)").description("Reset wish state. <slug>#<group> resets one in-progress group; bare <slug> wipes the wish and recreates from current WISH.md").action(async(ref,options)=>{try{if(ref.includes("#")){let{slug,group}=parseRef(ref),result2=await resetGroup(slug,group);if(console.log(`\uD83D\uDD04 Group "${group}" reset to ready in wish "${slug}"`),result2.status==="ready")console.log(" Status: ready (assignee cleared)");return}await resetWishCommand(ref,options?.yes??!1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}})}async function resetWishCommand(slug,confirmed){let wishPath=resolveWishPath(slug);if(!wishPath)throw Error(`No WISH.md found for "${slug}" \u2014 searched cwd and repo root`);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)throw Error(`No execution groups found in ${wishPath}`);let existing=await getState(slug);if(existing){let groupCount=Object.keys(existing.groups).length,inProgress=Object.values(existing.groups).filter((g)=>g.status==="in_progress").length,summary=`Wipe all state for "${slug}" (${groupCount} groups, ${inProgress} in-progress)?`;if(!isInteractive()){if(!confirmed)console.error(`\u274C ${summary}`),console.error(" Refusing to wipe in non-interactive mode. Pass --yes to confirm."),process.exit(2)}else{let{confirm:confirm2}=await Promise.resolve().then(() => (init_esm14(),exports_esm));if(!await confirm2({message:summary,default:!1})){console.log("Aborted.");return}}}if(await wipeState(slug))console.log(`\uD83D\uDDD1\uFE0F Wiped existing state for wish "${slug}"`);else console.log(`\u2139\uFE0F No existing state for wish "${slug}" \u2014 creating fresh`);let state=await createState(slug,groups);console.log(`\uD83D\uDCDD Recreated state from ${wishPath} (${groups.length} groups)`),console.log(""),console.log(`Wish: ${state.wish}`),console.log("\u2500".repeat(60));for(let[name,group]of Object.entries(state.groups)){let icon=STATUS_ICONS[group.status]??"\u2753";console.log(` ${name} ${icon} ${group.status}`)}}async function writeContextFile(content){let dir=join48(tmpdir3(),"genie-dispatch");await mkdir6(dir,{recursive:!0});let ts3=Date.now().toString(36),rand=Math.random().toString(36).slice(2,8),filePath=join48(dir,`ctx-${ts3}-${rand}.md`);return await writeFile3(filePath,content),filePath}function extractGroup(content,groupName){let pattern=new RegExp(`^### Group ${escapeRegExp(groupName)}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start2=match.index,nextBoundary=content.slice(start2).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start2+1+nextBoundary:content.length;return content.slice(start2,end).trim()}function extractWishContext(content){let execGroupsIdx=content.indexOf("## Execution Groups");if(execGroupsIdx!==-1)return content.slice(0,execGroupsIdx).trim();return content.slice(0,2000).trim()}var SLUG_PATTERN=/^[a-zA-Z0-9._-]+$/;function validateSlug(slug){if(!SLUG_PATTERN.test(slug))console.error(`\u274C Invalid slug: "${slug}"`),console.error(" Slugs must match [a-zA-Z0-9._-]+ (no slashes, dots-dots, or special characters)"),process.exit(1)}function escapeRegExp(str5){return str5.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildContextPrompt(opts){let parts=[`# Dispatch Context (${opts.command})`,"",`**Source file:** \`${opts.filePath}\``,"(Read the full document at the path above for complete context)",""];if(opts.wishContext)parts.push("## Wish Context","",opts.wishContext,"");if(parts.push("## Assigned Section","",opts.sectionContent,""),opts.enrichedContext)parts.push(opts.enrichedContext);if(opts.skill)parts.push("## Initial Command","",`Run \`/${opts.skill}\` to begin.`,"");return parts.join(`
|
|
3264
3273
|
`)}function getGitDiff(){try{let diff=execSync14("git diff HEAD",{encoding:"utf-8",maxBuffer:1048576}),staged=execSync14("git diff --cached",{encoding:"utf-8",maxBuffer:1048576}),combined=[diff,staged].filter(Boolean).join(`
|
|
3265
3274
|
`);if(combined.length>50000)return`${combined.slice(0,50000)}
|
|
3266
3275
|
|
|
@@ -3320,7 +3329,7 @@ Event Throughput:`),console.log(` Emitted: ${data.events_emitted}`),console.lo
|
|
|
3320
3329
|
Project: ${p.name}`),console.log("\u2500".repeat(50)),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`);if(console.log(` Created: ${formatDate(p.createdAt)}`),console.log(` Tasks: ${tasks.length}`),tasks.length>0){console.log(`
|
|
3321
3330
|
By status:`);for(let[status,count]of Object.entries(byStatus).sort())console.log(` ${padRight(status,15)} ${count}`);console.log(`
|
|
3322
3331
|
By stage:`);for(let[stage,count]of Object.entries(byStage).sort())console.log(` ${padRight(stage,15)} ${count}`)}}function registerProjectCommands(program2){let project=program2.command("project").description("Project management \u2014 named task boards");project.command("list").description("List all projects").option("--all","Include archived projects").option("--json","Output as JSON").action(async(options)=>{try{let ts3=await getTaskService5(),projects=await ts3.listProjectsFiltered(options.all);if(options.json){console.log(JSON.stringify(projects,null,2));return}if(projects.length===0){console.log("No projects found. Projects are auto-created when you run `genie task create`.");return}await printProjectList(ts3,projects)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("create <name>").description("Create a new project").option("--virtual","Create a virtual project (not tied to a repo)").option("--repo <path>","Repo path for the project").option("--description <text>","Project description").action(async(name,options)=>{try{let ts3=await getTaskService5(),repoPath=options.virtual?null:options.repo??null,p=await ts3.createProject({name,repoPath,description:options.description});if(console.log(`Created project "${p.name}"`),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("show <name>").description("Show project detail with task stats").option("--json","Output as JSON").action(async(name,options)=>{try{let ts3=await getTaskService5(),p=await ts3.getProjectByName(name);if(!p){console.error(`Error: Project not found: ${name}`),process.exit(1);return}if(options.json){console.log(JSON.stringify(p,null,2));return}let tasks=await ts3.listTasks({projectName:name,allProjects:!0});printProjectDetail(p,tasks)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("archive <name>").description("Archive a project (cascades to boards and unfinished tasks)").action(async(name)=>{try{await(await getTaskService5()).archiveProject(name),console.log(`Archived project "${name}" and cascaded to boards + unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("unarchive <name>").description("Restore an archived project and its boards (tasks stay as-is)").action(async(name)=>{try{await(await getTaskService5()).unarchiveProject(name),console.log(`Unarchived project "${name}" and restored boards.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("set-default <name>").description("Set default project for when outside any repo").action(async(name)=>{try{if(!await(await getTaskService5()).getProjectByName(name))console.error(`Error: Project not found: ${name}`),process.exit(1);let{loadGenieConfig:loadGenieConfig2,saveGenieConfig:saveGenieConfig2}=await Promise.resolve().then(() => (init_genie_config2(),exports_genie_config)),config=await loadGenieConfig2();config.defaultProject=name,await saveGenieConfig2(config),console.log(`Default project set to "${name}"`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_registry();import{readFile as readFile12,stat as stat5}from"fs/promises";import{join as join56}from"path";import{readFile as readFile10}from"fs/promises";async function parseQaSpec(filePath){let content=await readFile10(filePath,"utf-8");return parseQaSpecContent(content,filePath)}var SECTION_MAP={setup:"setup",actions:"actions",expect:"expect"};function detectSection(line){for(let[keyword,section]of Object.entries(SECTION_MAP))if(new RegExp(`^##\\s+${keyword}`,"i").test(line))return section;if(line.startsWith("## "))return"none";return null}function parseQaSpecContent(content,filePath="<inline>"){let lines=content.split(`
|
|
3323
|
-
`),name="",currentSection="none",setup=[],actions=[],expect=[],parsers2={none:()=>{},setup:(line)=>{let step=parseSetupLine(line);if(step)setup.push(step)},actions:(line)=>{let step=parseActionLine(line);if(step)actions.push(step)},expect:(line)=>{let exp=parseExpectLine(line);if(exp)expect.push(exp)}};for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith("# ")&&!trimmed.startsWith("## ")){let match=trimmed.match(/^#\s+(?:Test:\s*)?(.+)$/);if(match)name=match[1].trim();continue}let section=detectSection(trimmed);if(section!==null){currentSection=section;continue}if(!trimmed||trimmed.startsWith("//")||trimmed.startsWith("<!--"))continue;parsers2[currentSection](trimmed)}if(!name)name=filePath.replace(/.*\//,"").replace(/\.md$/,"");return{name,file:filePath,setup,actions,expect}}function parseSetupLine(line){let text=stripListPrefix(line);if(!text)return null;let spawnMatch=text.match(/^spawn\s+(\S+)(?:\s+\((.+)\))?$/i);if(spawnMatch)return{kind:"spawn",target:spawnMatch[1],options:spawnMatch[2]?parseOptions2(spawnMatch[2]):{}};let followMatch=text.match(/^(?:start\s+)?follow(?:\s+(?:on\s+)?(\S+))?(?:\s+\((.+)\))?$/i);if(followMatch)return{kind:"follow",target:followMatch[1]||"team",options:followMatch[2]?parseOptions2(followMatch[2]):{}};return null}function parseActionLine(line){let text=stripListPrefix(line);if(!text)return null;let sendMatch=text.match(/^send\s+"([^"]+)"\s+to\s+(\S+)/i);if(sendMatch)return{kind:"send",message:sendMatch[1],to:sendMatch[2]};let waitMatch=text.match(/^wait\s+(?:for\s+.+?\s+)?\(?(?:max\s+)?(\d+)s\)?/i);if(waitMatch)return{kind:"wait",seconds:Number.parseInt(waitMatch[1],10)};let runMatch=text.match(/^run\s+(.+)$/i);if(runMatch)return{kind:"run",command:runMatch[1]};return null}function detectSource(text){if(/\binbox\b/i.test(text))return"inbox";if(/\blog\b/i.test(text))return"log";if(/\boutput\b/i.test(text))return"output";return"log"}function extractMatchers(text){let matchers={},matcherRegex=/(\w+)\s*([~!]?=)\s*(?:"([^"]+)"|(\S+))/g;for(let match of text.matchAll(matcherRegex)){let op=match[2]==="~="?"~":"",value=(match[3]??match[4]).trim();matchers[match[1]]=`${op}${value}`}return matchers}function parseExpectLine(line){let text=line.replace(/^[-*]\s*\[[ x]\]\s*/i,"").trim();if(!text)return null;return{description:text,source:detectSource(text),matchers:extractMatchers(text)}}function stripListPrefix(line){return line.replace(/^[-*]\s+/,"").replace(/^\d+\.\s+/,"").trim()}function parseOptions2(text){let opts={};for(let pair of text.split(",")){let[key,...rest]=pair.split(":");if(key&&rest.length>0)opts[key.trim()]=rest.join(":").trim()}return opts}import{cp as cp2,mkdir as mkdir8,rm as rm4,writeFile as writeFile5}from"fs/promises";import{tmpdir as tmpdir4}from"os";import{dirname as dirname11,join as join55,resolve as resolve11}from"path";var{$:$4}=globalThis.Bun;import{createHash as
|
|
3332
|
+
`),name="",currentSection="none",setup=[],actions=[],expect=[],parsers2={none:()=>{},setup:(line)=>{let step=parseSetupLine(line);if(step)setup.push(step)},actions:(line)=>{let step=parseActionLine(line);if(step)actions.push(step)},expect:(line)=>{let exp=parseExpectLine(line);if(exp)expect.push(exp)}};for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith("# ")&&!trimmed.startsWith("## ")){let match=trimmed.match(/^#\s+(?:Test:\s*)?(.+)$/);if(match)name=match[1].trim();continue}let section=detectSection(trimmed);if(section!==null){currentSection=section;continue}if(!trimmed||trimmed.startsWith("//")||trimmed.startsWith("<!--"))continue;parsers2[currentSection](trimmed)}if(!name)name=filePath.replace(/.*\//,"").replace(/\.md$/,"");return{name,file:filePath,setup,actions,expect}}function parseSetupLine(line){let text=stripListPrefix(line);if(!text)return null;let spawnMatch=text.match(/^spawn\s+(\S+)(?:\s+\((.+)\))?$/i);if(spawnMatch)return{kind:"spawn",target:spawnMatch[1],options:spawnMatch[2]?parseOptions2(spawnMatch[2]):{}};let followMatch=text.match(/^(?:start\s+)?follow(?:\s+(?:on\s+)?(\S+))?(?:\s+\((.+)\))?$/i);if(followMatch)return{kind:"follow",target:followMatch[1]||"team",options:followMatch[2]?parseOptions2(followMatch[2]):{}};return null}function parseActionLine(line){let text=stripListPrefix(line);if(!text)return null;let sendMatch=text.match(/^send\s+"([^"]+)"\s+to\s+(\S+)/i);if(sendMatch)return{kind:"send",message:sendMatch[1],to:sendMatch[2]};let waitMatch=text.match(/^wait\s+(?:for\s+.+?\s+)?\(?(?:max\s+)?(\d+)s\)?/i);if(waitMatch)return{kind:"wait",seconds:Number.parseInt(waitMatch[1],10)};let runMatch=text.match(/^run\s+(.+)$/i);if(runMatch)return{kind:"run",command:runMatch[1]};return null}function detectSource(text){if(/\binbox\b/i.test(text))return"inbox";if(/\blog\b/i.test(text))return"log";if(/\boutput\b/i.test(text))return"output";return"log"}function extractMatchers(text){let matchers={},matcherRegex=/(\w+)\s*([~!]?=)\s*(?:"([^"]+)"|(\S+))/g;for(let match of text.matchAll(matcherRegex)){let op=match[2]==="~="?"~":"",value=(match[3]??match[4]).trim();matchers[match[1]]=`${op}${value}`}return matchers}function parseExpectLine(line){let text=line.replace(/^[-*]\s*\[[ x]\]\s*/i,"").trim();if(!text)return null;return{description:text,source:detectSource(text),matchers:extractMatchers(text)}}function stripListPrefix(line){return line.replace(/^[-*]\s+/,"").replace(/^\d+\.\s+/,"").trim()}function parseOptions2(text){let opts={};for(let pair of text.split(",")){let[key,...rest]=pair.split(":");if(key&&rest.length>0)opts[key.trim()]=rest.join(":").trim()}return opts}import{cp as cp2,mkdir as mkdir8,rm as rm4,writeFile as writeFile5}from"fs/promises";import{tmpdir as tmpdir4}from"os";import{dirname as dirname11,join as join55,resolve as resolve11}from"path";var{$:$4}=globalThis.Bun;import{createHash as createHash5}from"crypto";import{mkdir as mkdir7,readFile as readFile11,readdir as readdir6,stat as stat4,writeFile as writeFile4}from"fs/promises";import{homedir as homedir31}from"os";import{join as join54,relative as relative5,resolve as resolve10}from"path";function repoHash(repoPath){return createHash5("sha256").update(resolve10(repoPath)).digest("hex").slice(0,12)}function resultsDir(repoPath){let base=process.env.GENIE_HOME??join54(homedir31(),".genie");return join54(base,"qa",repoHash(repoPath))}function resultsPath(repoPath){return join54(resultsDir(repoPath),"results.json")}async function loadResults(repoPath){try{let raw=await readFile11(resultsPath(repoPath),"utf-8");return JSON.parse(raw)}catch{return{}}}async function saveResult(repoPath,specKey,report){let dir=resultsDir(repoPath);await mkdir7(dir,{recursive:!0});let results=await loadResults(repoPath),specHash=await hashSpecFile(report.file);results[specKey]={lastRun:new Date().toISOString(),result:report.result,durationMs:report.durationMs,specHash,expectations:report.expectations,error:report.error},await writeFile4(resultsPath(repoPath),JSON.stringify(results,null,2))}async function isStale(repoPath,specKey,specFilePath){let stored=(await loadResults(repoPath))[specKey];if(!stored)return!1;return await hashSpecFile(specFilePath)!==stored.specHash}async function listAllSpecs(specDir){let entries=[];return await walkSpecs(specDir,specDir,entries),entries.sort((a,b2)=>{if(a.domain!==b2.domain)return a.domain.localeCompare(b2.domain);return a.name.localeCompare(b2.name)})}async function hashSpecFile(filePath){try{let content=await readFile11(filePath,"utf-8");return createHash5("sha256").update(content).digest("hex").slice(0,12)}catch{return"unknown"}}async function walkSpecs(baseDir,dir,entries){let items=await readdir6(dir);for(let item of items){let fullPath=join54(dir,item);if((await stat4(fullPath)).isDirectory())await walkSpecs(baseDir,fullPath,entries);else if(item.endsWith(".md")){let rel=relative5(baseDir,fullPath),parts=rel.replace(/\.md$/,"").split("/"),domain=parts.length>1?parts.slice(0,-1).join("/"):"(root)",name=parts[parts.length-1];entries.push({key:rel.replace(/\.md$/,""),domain,name,filePath:fullPath})}}}function specKeyFromPath(specDir,filePath){return relative5(specDir,filePath).replace(/\.md$/,"")}function formatTimeAgo(isoDate){let ms=Date.now()-new Date(isoDate).getTime(),seconds=Math.floor(ms/1000);if(seconds<60)return`${seconds}s ago`;let minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes}m ago`;let hours=Math.floor(minutes/60);if(hours<24)return`${hours}h ago`;return`${Math.floor(hours/24)}d ago`}init_runtime_events();init_team_manager();function emitNdjson(event){process.stdout.write(`${JSON.stringify(event)}
|
|
3324
3333
|
`)}async function publishQaEvent(repoPath,qaType,payload){let{specKey,domain,team,...rest}=payload;await publishSubjectEvent(repoPath,`genie.qa.${qaType}`,{kind:"qa",agent:"qa",team,text:`${qaType}: ${specKey}`,data:{qaType,specKey,domain,...rest},source:"hook"})}async function emitQaEvent(repoPath,qaType,payload,ndjson){if(await publishQaEvent(repoPath,qaType,payload),ndjson)emitNdjson(payload)}function parseStatusEntry(status,parts,index){if(status.startsWith("R")){let from=parts[index]??"",to=parts[index+1]??"";if(from&&to)return{op:{kind:"rename",from,to},nextIndex:index+2};return{op:null,nextIndex:index+2}}let path3=parts[index]??"";if(!path3)return{op:null,nextIndex:index+1};return{op:{kind:status.startsWith("D")?"delete":"copy",path:path3},nextIndex:index+1}}function parseNameStatusZ(output){if(!output)return[];let parts=output.split("\x00").filter(Boolean),ops=[],i2=0;while(i2<parts.length){let status=parts[i2++]??"";if(!status)break;let{op,nextIndex}=parseStatusEntry(status,parts,i2);if(i2=nextIndex,op)ops.push(op)}return ops}async function overlayDirtyWorkingTree(repoPath,worktreePath){let tracked=(await $4`git -C ${repoPath} diff --name-status --find-renames -z HEAD --`.quiet().nothrow().text()).trim(),untracked=(await $4`git -C ${repoPath} ls-files --others --exclude-standard -z`.quiet().nothrow().text()).trim(),ops=parseNameStatusZ(tracked);for(let path3 of untracked.split("\x00").filter(Boolean))ops.push({kind:"copy",path:path3});for(let op of ops){if(op.kind==="delete"){await rm4(join55(worktreePath,op.path),{recursive:!0,force:!0});continue}if(op.kind==="rename"){await rm4(join55(worktreePath,op.from),{recursive:!0,force:!0});let src2=join55(repoPath,op.to),dest2=join55(worktreePath,op.to);await mkdir8(dirname11(dest2),{recursive:!0}),await cp2(src2,dest2,{recursive:!0,force:!0});continue}let src=join55(repoPath,op.path),dest=join55(worktreePath,op.path);await mkdir8(dirname11(dest),{recursive:!0}),await cp2(src,dest,{recursive:!0,force:!0})}}async function runAllSpecs(specDir,options){let entries=await listAllSpecs(specDir);return runSpecEntries(entries,specDir,options)}async function runDomainSpecs(specDir,domain,options){let filtered=(await listAllSpecs(specDir)).filter((e)=>e.domain===domain);return runSpecEntries(filtered,specDir,options)}async function prepareTeams(entries,repoPath,ndjson){let prepared=[];console.error(`
|
|
3325
3334
|
[qa] Creating ${entries.length} teams...`);for(let entry of entries){let teamName=`qa-${Date.now().toString(36)}-${entry.name.slice(0,8)}`;try{let spec=await parseQaSpec(entry.filePath),config=await createTeam(teamName,repoPath);await overlayDirtyWorkingTree(repoPath,config.worktreePath),await hireAgent(teamName,"qa");let prompt2=buildTeamLeadPrompt(spec,teamName,repoPath),promptFile=join55(tmpdir4(),`genie-qa-${teamName}.md`);await writeFile5(promptFile,prompt2),prepared.push({entry,spec,teamName,worktreePath:config.worktreePath,promptFile}),console.error(` \u2713 ${entry.name}`),await emitQaEvent(repoPath,"team-created",{type:"qa:team-created",specKey:entry.key,domain:entry.domain,team:teamName},ndjson)}catch(err){console.error(` \u2717 ${entry.name}: ${err instanceof Error?err.message:err}`)}await new Promise((r)=>setTimeout(r,200))}return prepared}async function emitSpecDone(repoPath,specKey,domain,team,report,ndjson){await emitQaEvent(repoPath,"spec-done",{type:"qa:spec-done",specKey,domain,team,result:report.result,durationMs:report.durationMs,expectations:report.expectations,error:report.error},ndjson)}async function runSpecEntries(entries,specDir,options){let repoPath=resolve11(options?.repoPath??process.cwd()),maxConcurrency=options?.parallel??5,timeoutMs=(options?.timeout??3600)*1000,ndjson=options?.ndjson??!1,GREEN="\x1B[32m",RED="\x1B[31m",RESET2="\x1B[0m",DIM2="\x1B[90m",prepared=await prepareTeams(entries,repoPath,ndjson);if(prepared.length===0)return[];let timelineSub=await followRuntimeEvents({repoPath,teamPrefix:"qa-"},(event)=>{if(!event?.timestamp||!event?.kind)return;let team=event.team??"";if(!team.startsWith("qa-"))return;let match=prepared.find((p)=>p.teamName===team),specKey=match?.entry.key??team,specName=match?.entry.name??team,domain=match?.entry.domain??"",time=new Date(event.timestamp).toLocaleTimeString("en-US",{hour12:!1}),text=(event.text??"").slice(0,100);if(console.error(` \x1B[90m${time}\x1B[0m \x1B[90m[${event.kind}]\x1B[0m ${specName} \x1B[90m${text}\x1B[0m`),ndjson)emitNdjson({type:"qa:event",specKey,domain,team,event:{timestamp:event.timestamp,kind:event.kind,agent:event.agent??"",text:event.text??""}})},{pollIntervalMs:250});console.error(`
|
|
3326
3335
|
[qa] Running ${prepared.length} specs (max ${maxConcurrency} parallel)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260416.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"
|