File size: 4,618 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { IAgentTaskService } from '#/agent/task/task';
import { SubagentTask } from '#/agent/tools/agent/subagent-task';
import { createTestAgent, type TestAgentContext } from '../../harness';
function agentTask(
completion: Promise<{ result: string }>,
description: string,
): SubagentTask {
return new SubagentTask(
{ agentId: 'agent-child', profileName: 'coder', completion },
description,
new AbortController(),
);
}
describe('SubagentTask — timeoutMs', () => {
let ctx: TestAgentContext;
let background: IAgentTaskService;
beforeEach(() => {
ctx = createTestAgent();
background = ctx.get(IAgentTaskService);
});
afterEach(async () => {
vi.useRealTimers();
try {
await ctx.expectResumeMatches();
} finally {
await ctx.dispose();
}
});
it('external deadline marks task timed_out', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
const hangForever = new Promise<{ result: string }>(() => {});
const taskId = background.registerTask(agentTask(hangForever, 'hang'), {
timeoutMs: 2_000,
});
const terminalPromise = background.wait(taskId);
await vi.advanceTimersByTimeAsync(7_100);
const info = await terminalPromise;
expect(info?.status).toBe('timed_out');
expect(info?.stopReason).toBeUndefined();
});
it('omitting timeoutMs lets the task run to completion without a manager deadline', async () => {
let resolveFn!: (r: { result: string }) => void;
const completion = new Promise<{ result: string }>((res) => {
resolveFn = res;
});
const taskId = background.registerTask(agentTask(completion, 'no deadline'));
resolveFn({ result: 'finished' });
const info = await background.wait(taskId);
expect(info?.status).toBe('completed');
expect(info?.stopReason).toBeUndefined();
});
it('internal TimeoutError rejection = generic failure with error reason', async () => {
const internalErr = new Error('aiohttp sock_read timeout');
internalErr.name = 'TimeoutError';
const rejecting = Promise.reject(internalErr);
const taskId = background.registerTask(agentTask(rejecting, 'internal timeout'), {
timeoutMs: 900_000,
});
const info = await background.wait(taskId);
expect(info?.status).toBe('failed');
expect(info?.stopReason).toBe('aiohttp sock_read timeout');
});
it('explicit timeoutMs is persisted on the task info', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
let resolveFn!: (r: { result: string }) => void;
const completion = new Promise<{ result: string }>((res) => {
resolveFn = res;
});
const taskId = background.registerTask(
agentTask(completion, 'persist timeout'),
{ timeoutMs: 1_800_000 },
);
const info = background.getTask(taskId);
expect((info as unknown as { timeoutMs?: number }).timeoutMs).toBe(1_800_000);
resolveFn({ result: 'finished' });
await expect(background.wait(taskId)).resolves.toMatchObject({ status: 'completed' });
});
it('omitted timeoutMs leaves the task info field undefined', async () => {
let resolveFn!: (r: { result: string }) => void;
const completion = new Promise<{ result: string }>((res) => {
resolveFn = res;
});
const taskId = background.registerTask(agentTask(completion, 'default timeout'));
const info = background.getTask(taskId);
expect((info as unknown as { timeoutMs?: number }).timeoutMs).toBeUndefined();
resolveFn({ result: 'finished' });
await expect(background.wait(taskId)).resolves.toMatchObject({ status: 'completed' });
});
it('timeoutMs=0 is preserved on the task info and does not arm a deadline', async () => {
let resolveFn!: (r: { result: string }) => void;
const completion = new Promise<{ result: string }>((res) => {
resolveFn = res;
});
const taskId = background.registerTask(agentTask(completion, 'zero timeout'), {
timeoutMs: 0,
});
const initial = background.getTask(taskId);
expect((initial as unknown as { timeoutMs?: number }).timeoutMs).toBe(0);
const info = await background.wait(taskId, 5);
const raced = info === undefined ? undefined : {
status: info.status,
stopReason: info.stopReason,
};
expect(raced?.status).toBe('running');
expect(raced?.stopReason).toBeUndefined();
resolveFn({ result: 'finished' });
await expect(background.wait(taskId)).resolves.toMatchObject({ status: 'completed' });
});
});
|