/** * Tests for `agenthub work` — the auto-claim primitive (TSK-0018). * - immediate: claims an already-open addressed task without waiting (local). * - wait: blocks on SSE until a matching task is created, then claims it. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, rmSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { init } from '../src/cli/commands/init.js'; import { workAgent } from '../src/cli/commands/work.js'; import { startServer } from '../src/server/index.js'; import { createTask, getTask } from '../src/core/services/taskService.js'; import type { Task } from '../src/core/schema.js'; describe('agenthub work — immediate claim (local)', () => { let cwd: string; beforeEach(() => { cwd = mkdtempSync(join(tmpdir(), 'ah-work-')); init(cwd, { projectName: 'work-test', yes: true }); }); afterEach(() => { rmSync(cwd, { recursive: true, force: true }); }); it('claims an already-open addressed task without waiting', async () => { const mine = createTask(cwd, { title: 'kimi: already open', role: 'implementer' }); await workAgent({ projectCwd: cwd, agent: 'kimi', role: 'implementer', timeoutSec: 2 }); const { task } = getTask(cwd, mine.id); expect(task.status).toBe('in_progress'); expect(task.assignedTo).toBe('kimi'); }, 4000); }); describe('agenthub work — wait then auto-claim (server SSE)', () => { let cwd: string; let server: Awaited>; beforeEach(async () => { cwd = mkdtempSync(join(tmpdir(), 'ah-work-wait-')); init(cwd, { projectName: 'work-wait', yes: true }); server = await startServer(cwd, { host: '127.0.0.1', port: 0 }); }); afterEach(async () => { await server.app.close(); rmSync(cwd, { recursive: true, force: true }); }); it('claims a task created AFTER work starts waiting', async () => { // Start waiting; do NOT await yet — no task is addressed to kimi initially. const workDone = workAgent({ serverUrl: server.url, projectCwd: cwd, agent: 'kimi', role: 'implementer', timeoutSec: 3, }); // Give work a moment to reach the SSE wait, then delegate a task to kimi. await new Promise((r) => setTimeout(r, 200)); await fetch(`${server.url}/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'kimi: delegated after wait', role: 'implementer' }), }); await workDone; // resolves once work has claimed the task (or on timeout) const tasks = (await fetch(`${server.url}/tasks`).then((r) => r.json())) as Task[]; const mine = tasks.find((t) => (t.title ?? '').startsWith('kimi:')); expect(mine).toBeDefined(); expect(mine?.status).toBe('in_progress'); expect(mine?.assignedTo).toBe('kimi'); }, 6000); });