Files
botfights/server/src/engine/human-responses.ts
T

101 lines
2.6 KiB
TypeScript
Raw Normal View History

// In-memory store for pending human challenges.
// When the fight engine needs a human's response, it stores the challenge here
// and waits for the browser to submit the answer via REST.
import type { Challenge } from './challenges.js'
interface PendingChallenge {
fightId: string
botId: string
challenge: Challenge
roundNumber: number
createdAt: number
resolve: (response: { answer: string; trashTalk?: string }) => void
timeoutHandle: ReturnType<typeof setTimeout>
}
const pending = new Map<string, PendingChallenge>()
const HUMAN_TIMEOUT_MS = 8_000
export function isHumanPlayer(webhookUrl: string): boolean {
return webhookUrl === 'http://human.local/'
}
export function waitForHumanResponse(
fightId: string,
botId: string,
challenge: Challenge,
roundNumber: number,
): Promise<{ answer: string | null; trashTalk?: string; timedOut: boolean }> {
return new Promise((resolve) => {
const key = `${fightId}:${botId}`
const timeoutHandle = setTimeout(() => {
pending.delete(key)
console.log(`[human] ${key} timed out after ${HUMAN_TIMEOUT_MS}ms`)
resolve({ answer: null, timedOut: true })
}, HUMAN_TIMEOUT_MS)
pending.set(key, {
fightId,
botId,
challenge,
roundNumber,
createdAt: Date.now(),
resolve: (response) => {
clearTimeout(timeoutHandle)
pending.delete(key)
resolve({ answer: response.answer, trashTalk: response.trashTalk, timedOut: false })
},
timeoutHandle,
})
console.log(`[human] waiting for response: ${key} round=${roundNumber} type=${challenge.type}`)
})
}
export function submitHumanResponse(
fightId: string,
botId: string,
answer: string,
trashTalk?: string,
): boolean {
const key = `${fightId}:${botId}`
const entry = pending.get(key)
if (!entry) return false
console.log(`[human] response received: ${key} answer=${answer.slice(0, 80)}`)
entry.resolve({ answer: answer.slice(0, 2000), trashTalk: trashTalk?.slice(0, 200) })
return true
}
export function getPendingChallenge(
fightId: string,
botId: string,
): {
type: string
label: string
prompt: string
roundNumber: number
timeoutMs: number
remainingMs: number
scoring: string
} | null {
const key = `${fightId}:${botId}`
const entry = pending.get(key)
if (!entry) return null
const elapsed = Date.now() - entry.createdAt
const remaining = Math.max(0, HUMAN_TIMEOUT_MS - elapsed)
return {
type: entry.challenge.type,
label: entry.challenge.label,
prompt: entry.challenge.prompt,
roundNumber: entry.roundNumber,
timeoutMs: HUMAN_TIMEOUT_MS,
remainingMs: remaining,
scoring: entry.challenge.scoring,
}
}