Closes the addressing gap that left a waiting `agenthub work` blocked: the remaining Win tasks were titled "Win L2: …" / unassigned, so they matched no agent and the daemon waited forever. `agenthub task assign <id> --agent <name>` sets assignedTo WITHOUT claiming (status stays open) and fires task/updated — so an agent's blocked `work` re-checks, matches via assignedTo, and auto-claims it. The architect can now route a specific open task to a specific agent and have its daemon pick it up. - taskService.assignTask; PATCH /tasks/:id handles assignedTo-without-status; remoteClient.assignTask; CLI `task assign`. - test: assign keeps status open + sets assignedTo; start/work then claims it via the assignedTo match. 122/122 green. Bump 0.5.0 -> 0.6.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
/**
|
|
* Tests for `agenthub start` — one-command onboarding that announces and
|
|
* auto-claims the task addressed to the agent (local path; the server path
|
|
* uses the same logic via remoteClient).
|
|
*/
|
|
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 { startAgent } from '../src/cli/commands/start.js';
|
|
import { createTask, getTask, assignTask } from '../src/core/services/taskService.js';
|
|
|
|
describe('agenthub start — onboarding auto-claim', () => {
|
|
let cwd: string;
|
|
|
|
beforeEach(() => {
|
|
cwd = mkdtempSync(join(tmpdir(), 'ah-start-'));
|
|
init(cwd, { projectName: 'start-test', yes: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(cwd, { recursive: true, force: true });
|
|
});
|
|
|
|
it('claims the open implementer task addressed to the agent (title prefix)', async () => {
|
|
const mine = createTask(cwd, { title: 'kimi: backend tests', role: 'implementer' });
|
|
createTask(cwd, { title: 'codex: magic audit', role: 'implementer' }); // addressed to someone else
|
|
|
|
await startAgent({ projectCwd: cwd, agent: 'kimi', role: 'implementer' });
|
|
|
|
const { task } = getTask(cwd, mine.id);
|
|
expect(task.status).toBe('in_progress');
|
|
expect(task.assignedTo).toBe('kimi');
|
|
});
|
|
|
|
it('does NOT claim a task addressed to a different agent', async () => {
|
|
const other = createTask(cwd, { title: 'codex: magic audit', role: 'implementer' });
|
|
|
|
await startAgent({ projectCwd: cwd, agent: 'kimi', role: 'implementer' });
|
|
|
|
const { task } = getTask(cwd, other.id);
|
|
expect(task.status).toBe('open');
|
|
expect(task.assignedTo).toBeUndefined();
|
|
});
|
|
|
|
it('claims nothing when there are no open tasks (just announces)', async () => {
|
|
// Should not throw; nothing to claim.
|
|
await expect(startAgent({ projectCwd: cwd, agent: 'kimi', role: 'implementer' })).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('assignTask addresses an open task; start then claims it via assignedTo', async () => {
|
|
// A generically-titled task, NOT name-addressed to the agent.
|
|
const t = createTask(cwd, { title: 'Win L2: generic bypass task', role: 'implementer' });
|
|
|
|
assignTask(cwd, t.id, 'windows-claude');
|
|
const afterAssign = getTask(cwd, t.id).task;
|
|
expect(afterAssign.status).toBe('open'); // assign does NOT claim
|
|
expect(afterAssign.assignedTo).toBe('windows-claude');
|
|
|
|
// Now the agent's start/work finds it via the assignedTo match and claims it.
|
|
await startAgent({ projectCwd: cwd, agent: 'windows-claude', role: 'implementer' });
|
|
expect(getTask(cwd, t.id).task.status).toBe('in_progress');
|
|
expect(getTask(cwd, t.id).task.assignedTo).toBe('windows-claude');
|
|
});
|
|
});
|