Ground bot intentions in live game knowledge and verify outcomes
Core tests / core (push) Successful in 28s

This commit is contained in:
Codex
2026-09-25 13:28:03 +08:00
parent 20ccd0fced
commit 502cb1e4fd
17 changed files with 251 additions and 24 deletions
+10 -2
View File
@@ -6,10 +6,12 @@ import type {RuntimeConfig} from '@/core/types';
import {observeInsights,activityReason,reportWindow,summarize,DAY,type Cursor,type Evidence} from '@/core/realm-insights';
const recovery=z.object({id:z.number().int().positive(),guid:z.number().int().positive(),name:z.string().max(64),at:z.number().finite(),detail:z.string().max(255),status:z.enum(['pending','resumed','no_progress','unverified']).optional(),checkedAt:z.number().nonnegative().optional(),result:z.string().max(255).optional()});
const count=z.number().int().nonnegative();
const outcome=z.object({guid:count,name:z.string().max(64),quests:count,abandoned:count,lootEvents:count,lootItems:count,partyKills:count,rewarded:count,lootedCopper:count.default(0)});
const outcome=z.object({guid:count,name:z.string().max(64),quests:count,abandoned:count,lootEvents:count,lootItems:count,partyKills:count,rewarded:count,lootedCopper:count.default(0),nativeDeaths:count.optional()});
const measurement=z.number().finite().nonnegative();
const performance=z.object({at:measurement,enabled:z.boolean(),ticks:measurement,averageTickMs:measurement,rows:z.array(z.object({category:z.string().max(40),name:z.string().max(160),calls:measurement,totalMs:measurement,averageMs:measurement,maxMs:measurement,perTickMs:measurement})).max(20)});
async function ensure(){await env.DB!.batch([
env.DB!.prepare('CREATE TABLE IF NOT EXISTS insights_evaluation (id INTEGER PRIMARY KEY,started_at INTEGER)'),
env.DB!.prepare('INSERT OR IGNORE INTO insights_evaluation VALUES (1,?)').bind(Math.floor(Date.now()/1000)),
env.DB!.prepare('CREATE TABLE IF NOT EXISTS insights_native (id INTEGER PRIMARY KEY,payload TEXT)'),
env.DB!.prepare('CREATE TABLE IF NOT EXISTS insights_performance (at INTEGER PRIMARY KEY,payload TEXT)'),
env.DB!.prepare('CREATE TABLE IF NOT EXISTS insights_cursor (id INTEGER PRIMARY KEY,payload TEXT)'),
@@ -38,8 +40,14 @@ export async function GET(){
const totals:Record<string,number>={};for(const e of aggregates.results)totals[e.kind]=(totals[e.kind]??0)+e.amount;
const byBot=s.bots.filter(b=>b.autonomy?.managed&&b.status==='online').map(b=>({name:b.name,zone:b.zone,grouped:!!b.world?.groupId&&now-b.world.at<120,groupBots:(b.world?.groupNames??[]).filter(n=>s.bots.some(o=>o.name===n&&o.autonomy?.managed)),totals:Object.fromEntries([...counterKeys,'groupedSeconds','nearPartySeconds','sharedQuests'].map(k=>[k,aggregates.results.filter(e=>e.name===b.name&&e.kind===k).reduce((sum,e)=>sum+e.amount,0)]))}));
const publicRecoveries=attempts.map((r:Recovery)=>({name:r.name,at:r.at,detail:r.detail,status:r.status??'unverified',checkedAt:r.checkedAt??null,result:r.result??'No verification recorded'}));
const evaluationRow=await env.DB!.prepare('SELECT started_at FROM insights_evaluation WHERE id=1').first<{started_at:number}>();
const evaluationStart=Math.max(now-DAY,evaluationRow?.started_at??now);
const evaluationRows=await env.DB!.prepare("SELECT json_extract(payload,'$.kind') AS kind,SUM(json_extract(payload,'$.amount')) AS amount FROM insights_evidence WHERE at>=? GROUP BY kind").bind(evaluationStart).all<{kind:string;amount:number}>();
const evaluationTotals=Object.fromEntries(evaluationRows.results.map(r=>[r.kind,r.amount]));
const hours=(evaluationTotals.onlineSeconds??0)/3600;
const evaluation={since:evaluationStart,onlineBotHours:hours,activeBotHours:(evaluationTotals.activeSeconds??0)/3600,deaths:evaluationTotals.nativeDeaths??0,deathsPerBotHour:hours?(evaluationTotals.nativeDeaths??0)/hours:null,questTurnIns:evaluationTotals.rewarded??0,questTurnInsPerBotHour:hours?(evaluationTotals.rewarded??0)/hours:null,intentsObserved:evaluationTotals.intentObserved??0,intentsUnverified:evaluationTotals.intentUnverified??0,intentsInterrupted:evaluationTotals.intentInterrupted??0,nearbyPartyHours:(evaluationTotals.nearPartySeconds??0)/3600};
const attention=attentionItems(s,now,cursor?.at??null,native?.at??null,perf,attempts,health);
return Response.json({attention,outcomes:{at:native?.at??null,startedAt:native?.startedAt??null,windowStart:Math.max(now-DAY,native?.startedAt??now),totals,bots:byBot,recent:recent.slice(0,60)},recoveryResults:publicRecoveries,performance:{latest:perf,history:perfHistory.map(p=>({at:p.at,averageTickMs:p.averageTickMs,ticks:p.ticks})).reverse()},collectedAt:cursor?.at??null,startedAt:cursor?.startedAt??null,nextReportAt:reportWindow(now)+DAY,current:await report(reportWindow(now),now+1,cursor?.startedAt??now),reports:history.results.map(r=>JSON.parse(r.payload)),activity:s.bots.filter(b=>b.autonomy?.managed&&b.status==='online').map(b=>({name:b.name,zone:b.zone,action:b.lastDecision?.action??'unknown',observedAt:b.world?.at??null,stationarySince:b.world?.stationarySince??null,lastProgressAt:cursor?.bots[b.id]?.lastProgressAt??null,...activityReason(b,now),recoveries:attempts.filter(r=>r.guid===b.autonomy?.characterGuid).slice(0,3).map(({at,detail})=>({at,detail}))}))},{headers:{'Cache-Control':'no-store'}});
return Response.json({evaluation,attention,outcomes:{at:native?.at??null,startedAt:native?.startedAt??null,windowStart:Math.max(now-DAY,native?.startedAt??now),totals,bots:byBot,recent:recent.slice(0,60)},recoveryResults:publicRecoveries,performance:{latest:perf,history:perfHistory.map(p=>({at:p.at,averageTickMs:p.averageTickMs,ticks:p.ticks})).reverse()},collectedAt:cursor?.at??null,startedAt:cursor?.startedAt??null,nextReportAt:reportWindow(now)+DAY,current:await report(reportWindow(now),now+1,cursor?.startedAt??now),reports:history.results.map(r=>JSON.parse(r.payload)),activity:s.bots.filter(b=>b.autonomy?.managed&&b.status==='online').map(b=>({name:b.name,zone:b.zone,action:b.lastDecision?.action??'unknown',observedAt:b.world?.at??null,stationarySince:b.world?.stationarySince??null,lastProgressAt:cursor?.bots[b.id]?.lastProgressAt??null,...activityReason(b,now),intentResults:(b.autonomy?.intentChecks??[]).slice(-3).map(({action,status,detail,startedAt,checkedAt})=>({action,status,detail,startedAt,checkedAt})),recoveries:attempts.filter(r=>r.guid===b.autonomy?.characterGuid).slice(0,3).map(({at,detail})=>({at,detail}))}))},{headers:{'Cache-Control':'no-store'}});
}
export async function POST(request:Request){
const token=(env as unknown as RuntimeConfig).AUTONOMY_TOKEN;if(!token||request.headers.get('authorization')!==`Bearer ${token}`)return Response.json({error:'Unauthorized'},{status:401});