Fix conversation failure handling and confirmed delivery memories

This commit is contained in:
Richard Clark
2026-09-22 14:17:06 -04:00
parent 5f0fc86cf9
commit 86d57ec9ae
5 changed files with 76 additions and 21 deletions
+14 -17
View File
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { queueAleCommand } from './ale-store';
import { luaString } from './ale';
import { cleanupConversations,completeConversation,failConversation,leaseConversation,type ConversationRequest } from './conversation-store';
import { cleanupConversations,completeConversation,failConversation,leaseConversation,awaitDelivery,pendingDeliveries,type ConversationRequest } from './conversation-store';
import type { Bot,RealmEvent,RuntimeConfig,State,StateStore } from './types';
const replySchema=z.object({reply:z.string().min(1).max(500)}).strict();
@@ -54,14 +54,8 @@ function chooseTarget(state:State,request:ConversationRequest){
return undefined;
}
function fallbackReply(bot:Bot,request:ConversationRequest){
if(/\b(hello|hi|hey|yo)\b/i.test(request.message))return `Hello, ${request.speakerName}.`;
if(request.message.includes('?'))return `I'm not sure, ${request.speakerName}. We can figure it out together.`;
return `I hear you, ${request.speakerName}.`;
}
async function modelReply(bot:Bot,request:ConversationRequest,state:State,config:RuntimeConfig){
if(!config.LLM_URL||state.settings.provider==='deterministic')return fallbackReply(bot,request);
if(!config.LLM_URL||state.settings.provider==='deterministic')throw new Error('Conversation model is not configured; no canned reply was sent');
const brains=state.settings.brains;
const hero=bot.tier==='hero';
@@ -133,8 +127,9 @@ async function remember(store:StateStore,botId:string,request:ConversationReques
const revision=state.revision;
const bot=state.bots.find(x=>x.id===botId);
if(!bot)return;
if(bot.memories.some(memory=>memory.id===request.id))return;
bot.memories.push({
id:crypto.randomUUID(),
id:request.id,
text:`${request.speakerName} said: "${request.message.slice(0,180)}" I replied: "${reply.slice(0,180)}"`,
importance:request.channel==='whisper'?7:6,
source:'observation',
@@ -153,6 +148,14 @@ async function remember(store:StateStore,botId:string,request:ConversationReques
}
export async function runConversationTick(store:StateStore,db:D1Database,config:RuntimeConfig){
for(const delivery of await pendingDeliveries(db)){
if(delivery.status==='completed'){
await remember(store,delivery.botId,delivery.request,delivery.reply);
await completeConversation(db,delivery.request.id,JSON.stringify({commandId:delivery.commandId,delivered:true}));
}else if(delivery.status==='failed'){
await failConversation(db,delivery.request.id,delivery.error||'Game rejected conversation delivery');
}
}
const request=await leaseConversation(db);
if(!request)return {ok:true,ran:false,reason:'No player conversation waiting'};
@@ -164,18 +167,12 @@ export async function runConversationTick(store:StateStore,db:D1Database,config:
return {ok:true,ran:false,reason:'No addressed managed Playerbot matched',speaker:request.speakerName};
}
let reply:string;
try{
reply=await modelReply(bot,request,state,config);
}catch{
reply=fallbackReply(bot,request);
}
let reply=await modelReply(bot,request,state,config);
reply=cleanReply(reply);
if(!reply)throw new Error('Conversation reply was empty');
const command=await queueAleCommand(db,compileReply(bot.name,request.speakerName,reply,request.channel));
await remember(store,bot.id,request,reply);
await completeConversation(db,request.id,JSON.stringify({bot:bot.name,reply,commandId:command.id}));
await awaitDelivery(db,request,bot.id,reply,command.id);
await cleanupConversations(db);
return {ok:true,ran:true,speaker:request.speakerName,name:bot.name,channel:request.channel,reply,commandId:command.id};