Luigit
repositories / will

will

owned by admin

src/agent/e2e.int.test.ts

Raw
import assert from 'node:assert/strict'
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { test } from 'node:test'
import { startService } from '../service/main.ts'
import type { TgUpdateRaw } from '../shared/telegram.ts'
import { loadAgentConfig } from './config.ts'
import { startAgent } from './main.ts'

/**
 * Walking skeleton (vertical slice): telegram mention -> durable wake ->
 * fake resident session turn -> readiness flips healthy. Uses the real
 * service, real telegram runtime against a fake Telegram, real queue and
 * spool; only the model session is faked (WILL_SESSION_MODE=fake).
 */

test('end to end: mention reaches the resident session and readiness is healthy', async () => {
  const updates: TgUpdateRaw[] = []
  const sent: { method: string }[] = []
  const tgServer = createServer((req, res) => {
    const url = new URL(req.url ?? '/', 'http://x')
    let data = ''
    req.on('data', (c: Buffer) => {
      data += c.toString()
    })
    req.on('end', () => {
      let body: Record<string, unknown> = {}
      try {
        body = data === '' ? {} : (JSON.parse(data) as Record<string, unknown>)
      } catch {
        body = {}
      }
      const json = (p: unknown) => {
        res.writeHead(200, { 'content-type': 'application/json' })
        res.end(JSON.stringify(p))
      }
      const method = url.pathname.split('/').pop() ?? ''
      switch (method) {
        case 'getMe':
          return json({ ok: true, result: { id: 42, username: 'will_bot' } })
        case 'getUpdates':
          return json({
            ok: true,
            result: updates.filter((u) => u.update_id >= ((body.offset as number) ?? 0)),
          })
        case 'getFile':
          return json({ ok: true, result: { file_id: 'f', file_path: 'x' } })
        default:
          sent.push({ method })
          return json({ ok: true, result: { message_id: 1 } })
      }
    })
  })
  await new Promise<void>((r) => tgServer.listen(0, '127.0.0.1', () => r()))
  const tgUrl = `http://127.0.0.1:${(tgServer.address() as { port: number }).port}`

  const dir = await mkdtemp(join(tmpdir(), 'e2e-'))
  const webDist = join(dir, 'web-dist')
  const resourceDir = join(dir, 'resources')
  await mkdir(webDist, { recursive: true })
  await mkdir(join(resourceDir, 'persona'), { recursive: true })
  await writeFile(join(resourceDir, 'persona', '10-identity.md'), '# identity\nI am will.', 'utf8')

  const service = await startService({
    dataDir: join(dir, 'data'),
    publicAddr: '127.0.0.1:0',
    internalAddr: '127.0.0.1:0',
    webDist,
    docsDist: webDist,
    mediaMaxBytes: 100 << 20,
    storageWarnPct: 20,
    storageStopPct: 10,
    workerUrl: join(import.meta.dirname, '../service/worker.ts'),
  })
  const intPort = (service.internalServer.address() as { port: number }).port

  const prev = { ...process.env }
  process.env.WILL_SESSION_MODE = 'fake'
  process.env.WILL_AGENT_DIR = join(dir, 'agent')
  process.env.WILL_RESOURCE_DIR = resourceDir
  process.env.WILL_SERVICE_INTERNAL_URL = `http://127.0.0.1:${intPort}`
  process.env.WILL_AGENT_READY_ADDR = '127.0.0.1:0'
  process.env.WILL_TELEGRAM_TOKEN = 'TOKEN'
  process.env.WILL_TELEGRAM_CHAT_ID = '-100'
  process.env.WILL_TELEGRAM_OLIVER_ID = '1'
  process.env.WILL_TELEGRAM_API_BASE = tgUrl
  process.env.ZAI_BASE_URL = tgUrl // unused without a key; keeps config total
  delete process.env.WILL_VCS_GIT_URL

  try {
    updates.push({
      update_id: 1,
      message: {
        message_id: 7,
        date: Math.floor(Date.now() / 1000),
        chat: { id: -100 },
        from: { id: 5, username: 'friend' },
        text: 'hey @will_bot are you alive',
        entities: [{ type: 'mention', offset: 4, length: 9 }],
      },
    })

    const agent = await startAgent(loadAgentConfig())
    // wait for the wake to be consumed by the fake session
    const fake = agent.session as unknown as { prompts: string[] }
    const deadline = Date.now() + 15_000
    while ((fake.prompts.length === 0 || !fake.prompts[0]) && Date.now() < deadline) {
      await new Promise((r) => setTimeout(r, 100))
    }
    assert.ok(fake.prompts[0], 'resident session received a wake prompt')
    assert.match(fake.prompts[0], /<chat_wake/)
    assert.match(fake.prompts[0], /are you alive/)

    const statusRes = await fetch(`http://127.0.0.1:${intPort}/v1/status`)
    const status = (await statusRes.json()) as { counts: { chat: number } }
    assert.equal(status.counts.chat, 1, 'message archived in service')

    await agent.close()
    assert.ok(agent.config.serviceUrl.includes('127.0.0.1'))
  } finally {
    for (const [k, v] of Object.entries(prev)) {
      if (v === undefined) delete process.env[k]
      else process.env[k] = v
    }
    await service.close()
    tgServer.close()
  }
})