repositories / will
will
owned by admin
src/agent/engine.test.ts
Rawimport assert from 'node:assert/strict'
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { test } from 'node:test'
import { createEngine, type SessionPort } from './engine.ts'
import { buildSystemPrompt, renderFacts, renderWakePrompt } from './prompt.ts'
import { WakeQueue } from './queue-store.ts'
import { AgentState } from './state.ts'
function fakeSession(): SessionPort & { prompts: string[]; failCount: number; hang: boolean } {
const s = {
prompts: [] as string[],
failCount: 0,
hang: false,
async prompt(text: string) {
s.prompts.push(text)
if (s.failCount > 0) {
s.failCount--
throw new Error('model unavailable')
}
if (s.hang) await new Promise(() => {})
},
async abort() {},
}
return s as unknown as SessionPort & { prompts: string[]; failCount: number; hang: boolean }
}
async function setup() {
const dir = await mkdtemp(join(tmpdir(), 'eng-'))
const state = await AgentState.load(join(dir, 'state.json'))
const queue = await WakeQueue.open(join(dir, 'wakeq.jsonl'))
const audits: { kind: string; payload: unknown }[] = []
return { dir, state, queue, audits }
}
const budget = { dailyCapCalls: 100, recoveryReserveFraction: 0.1 }
test('chat turn completes, budget spent, wake removed', async () => {
const { state, queue, audits } = await setup()
const session = fakeSession()
const engine = createEngine({
queue,
state,
session,
budgetConfig: budget,
buildFacts: async () => ({ timeUtc: '2026-09-12T04:00:00Z', revision: 'r1' }),
audit: async (kind, payload) => {
audits.push({ kind, payload })
},
})
await queue.enqueue('chat', { text: 'hello' })
const outcome = await engine.tick()
assert.equal(outcome.kind, 'completed')
assert.equal(queue.peek().length, 0)
assert.equal(state.data.budget.used, 1)
assert.ok(session.prompts[0]?.includes('hello'))
assert.ok(session.prompts[0]?.includes('deployment_revision: r1'))
assert.deepEqual(
audits.map((a) => a.kind),
['wake_start', 'wake_end'],
)
})
test('boot context appears only on the first turn', async () => {
const { state, queue } = await setup()
const session = fakeSession()
const engine = createEngine({
queue,
state,
session,
budgetConfig: budget,
buildFacts: async () => ({}),
audit: async () => {},
})
await queue.enqueue('chat', {})
await engine.tick()
await queue.enqueue('chat', {})
await engine.tick()
assert.match(session.prompts[0] ?? '', /<boot_context\b/)
assert.doesNotMatch(session.prompts[1] ?? '', /<boot_context\b/)
})
test('model failure retries with backoff, then defers after three attempts', async () => {
const { state, queue, audits } = await setup()
const session = fakeSession()
let nowMs = Date.parse('2026-09-12T04:00:00Z')
const engine = createEngine({
queue,
state,
session,
budgetConfig: budget,
buildFacts: async () => ({}),
audit: async (kind, payload) => {
audits.push({ kind, payload })
},
clock: {
now: () => new Date(nowMs),
},
backoffMs: () => 1000,
})
await queue.enqueue('chat', { text: 'x' })
session.failCount = 3
const r1 = await engine.tick()
assert.equal(r1.kind, 'failed')
assert.equal(queue.peek()[0]?.attempts, 1)
nowMs += 2000
const r2 = await engine.tick()
assert.equal(r2.kind, 'failed')
assert.equal(queue.peek()[0]?.attempts, 2)
nowMs += 2000
const r3 = await engine.tick()
assert.equal(r3.kind, 'failed')
assert.equal(queue.peek()[0]?.attempts, 0, 'deferred resets attempts for the later retry')
assert.ok(queue.peek()[0]?.deferredUntil)
})
test('turn timeout enqueues exactly one recovery wake and completes the original', async () => {
const { state, queue, audits } = await setup()
const session = fakeSession()
const engine = createEngine({
queue,
state,
session,
budgetConfig: budget,
buildFacts: async () => ({}),
audit: async (kind, payload) => {
audits.push({ kind, payload })
},
turnDeadlineMs: 50,
})
await queue.enqueue('chat', { text: 'slow' })
session.hang = true
const outcome = await engine.tick()
assert.equal(outcome.kind, 'timeout')
const pending = queue.peek()
assert.equal(pending.length, 1)
assert.equal(pending[0]?.kind, 'recovery')
assert.deepEqual(
(pending[0]?.payload as { interruptedWakeId?: string }).interruptedWakeId,
outcome.wake?.id,
)
assert.ok(audits.some((a) => a.kind === 'wake_timeout'))
session.hang = false
assert.equal((await engine.tick()).kind, 'completed')
const recoveryPrompt = session.prompts[1] ?? ''
assert.match(recoveryPrompt, /turn watchdog fired/)
assert.doesNotMatch(recoveryPrompt, /inspect|continue|replay/i)
})
test('budget cap blocks ordinary wakes but reserve admits recovery wakes', async () => {
const { state, queue } = await setup()
const session = fakeSession()
const engine = createEngine({
queue,
state,
session,
budgetConfig: { dailyCapCalls: 2, recoveryReserveFraction: 0.5 },
buildFacts: async () => ({}),
audit: async () => {},
})
await state.update((d) => {
d.budget = { day: new Date().toISOString().slice(0, 10), used: 2, usedRecovery: 0 }
})
await queue.enqueue('chat', {})
const blocked = await engine.tick()
assert.equal(blocked.kind, 'budget-blocked')
assert.equal(queue.peek().length, 1, 'wake stays queued')
await queue.enqueue('deploy_failure', { revision: 'r9' })
const recovery = await engine.tick()
assert.equal(recovery.kind, 'completed')
})
test('storage stop pauses ordinary wakes, recovery continues', async () => {
const { state, queue } = await setup()
const session = fakeSession()
const engine = createEngine({
queue,
state,
session,
budgetConfig: budget,
buildFacts: async () => ({}),
audit: async () => {},
})
engine.setStoragePaused(true)
await queue.enqueue('chat', {})
assert.equal((await engine.tick()).kind, 'storage-blocked')
await queue.enqueue('ci_failure', {})
assert.equal((await engine.tick()).kind, 'completed')
})
test('system prompt is exactly the lexically ordered personality', async () => {
const dir = await mkdtemp(join(tmpdir(), 'persona-'))
await mkdir(dir, { recursive: true })
await writeFile(join(dir, '10-identity.md'), '# identity\nI am will.', 'utf8')
await writeFile(join(dir, '20-body.md'), '# body\nI live in a pod.', 'utf8')
const prompt = await buildSystemPrompt(dir)
assert.equal(prompt, '# identity\nI am will.\n\n# body\nI live in a pod.')
assert.doesNotMatch(prompt, /available_tools|operational_rules/)
})
test('wake prompts render per-kind templates with facts', async () => {
const facts = { timeUtc: '2026-09-12T04:00:00Z', revision: 'r1', budgetUsed: 3, budgetCap: 100 }
const chat = renderWakePrompt(
{
kind: 'chat',
payload: {
messageId: 5,
senderName: 'friend',
text: 'look',
triggerReason: 'mention',
media: [{ digest: 'a'.repeat(64), type: 'image/png' }],
},
},
facts,
)
assert.match(chat, /from: friend/)
assert.match(chat, /image\/png/)
assert.match(chat, /model_budget_used: 3\/100/)
const hb = renderWakePrompt({ kind: 'heartbeat', payload: {} }, facts)
assert.match(hb, /<heartbeat_wake\/>/)
assert.doesNotMatch(hb, /Review|Act|quiet/)
const df = renderWakePrompt(
{ kind: 'deploy_failure', payload: { revision: 'r9', kind: 'probation-failed' } },
facts,
)
assert.match(df, /r9/)
assert.doesNotMatch(df, /diagnose|push|fix commit/i)
})
test('facts block omits missing values instead of rendering undefined', () => {
assert.equal(renderFacts({}), '<wake_facts>\n\n</wake_facts>')
assert.match(renderFacts({ worktreeHead: 'abc', worktreeDirty: true }), /abc \(dirty\)/)
})