blob: 3d882be9e6b697c347f947679b516f4ef6618947 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import {
buildHeavyTaskProgressTools,
createHeavyTaskProgressRecorder,
heavyTaskInventorySubmitSchema,
heavyTaskTodoUpdateSchema,
renderHeavyTaskProgressForPrompt,
} from '../heavy-task-progress.js';
import type { HeavyTaskInventoryState, HeavyTaskTodoState } from '../task-contracts.js';
import { createInMemoryTaskRunStore } from '../task-run-store.js';
const toolContext = {
sessionId: 'session-1',
turnId: 'turn-1',
cwd: '/workspace',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
};
describe('heavy-task progress tools', () => {
test('inventory_submit records a typed inventory event and returns structured state', async () => {
const store = createInMemoryTaskRunStore();
const tools = buildHeavyTaskProgressTools(
createHeavyTaskProgressRecorder({
taskRunId: 'run-1',
attemptId: 'attempt-1',
store,
now: () => 123,
newId: idFactory(),
}),
);
const inventorySubmit = tools.find((tool) => tool.name === 'inventory_submit');
assert.ok(inventorySubmit);
const result = (await inventorySubmit.impl(
{
summary: 'Inspected public source files.',
items: [
{ path: 'src/app.js', kind: 'file', status: 'observed', purpose: 'main app entry' },
{ path: 'build-output.log', kind: 'artifact', status: 'planned' },
],
openQuestions: ['Need to inspect README.md'],
},
toolContext,
)) as { accepted: boolean; inventory: HeavyTaskInventoryState };
assert.equal(result.accepted, true);
assert.equal(result.inventory.taskRunId, 'run-1');
assert.equal(result.inventory.source.toolCallId, 'tool-1');
const events = await store.readEvents('run-1');
assert.equal(events[0]?.type, 'heavy_task_inventory_recorded');
});
test('todo_update records a typed todo snapshot and rejects corrupt progress state', async () => {
const store = createInMemoryTaskRunStore();
const tools = buildHeavyTaskProgressTools(
createHeavyTaskProgressRecorder({
taskRunId: 'run-2',
store,
now: () => 456,
newId: idFactory(),
}),
);
const todoUpdate = tools.find((tool) => tool.name === 'todo_update');
assert.ok(todoUpdate);
const result = (await todoUpdate.impl(
{
items: [
{ id: 'inspect', content: 'Inspect public files', status: 'completed', priority: 'high' },
{
id: 'artifact',
kind: 'runnable_artifact',
content: 'Patch implementation',
status: 'in_progress',
priority: 'high',
},
{
id: 'check',
kind: 'public_check',
content: 'Run public check',
status: 'pending',
priority: 'high',
},
],
},
toolContext,
)) as { accepted: boolean; todos: HeavyTaskTodoState };
assert.equal(result.accepted, true);
assert.equal(result.todos.items[1]?.status, 'in_progress');
assert.equal(result.todos.items[1]?.kind, 'runnable_artifact');
assert.equal(result.todos.items[2]?.kind, 'public_check');
assert.throws(
() =>
heavyTaskTodoUpdateSchema.parse({
items: [
{ id: 'same', content: 'First', status: 'pending', priority: 'medium' },
{ id: 'same', content: 'Second', status: 'pending', priority: 'low' },
],
}),
/duplicate todo id/,
);
assert.throws(
() =>
heavyTaskTodoUpdateSchema.parse({
items: [
{ id: 'one', content: 'First', status: 'in_progress', priority: 'medium' },
{ id: 'two', content: 'Second', status: 'in_progress', priority: 'low' },
],
}),
/at most one todo item/,
);
assert.throws(() =>
heavyTaskInventorySubmitSchema.parse({
summary: 'x'.repeat(2_001),
items: [],
}),
);
});
test('renders compact replay state for prompts', () => {
const rendered = renderHeavyTaskProgressForPrompt({
latestHeavyTaskInventory: {
schemaVersion: 1,
inventoryId: 'inventory-1',
taskRunId: 'run-3',
ts: 1,
summary: 'Inspected source and README.',
items: [{ path: 'README.md', kind: 'file', status: 'observed' }],
source: { kind: 'model_tool', toolCallId: 'tool-1' },
},
latestHeavyTaskTodos: {
schemaVersion: 1,
todoSetId: 'todos-1',
taskRunId: 'run-3',
ts: 2,
items: [
{
id: 'artifact',
kind: 'runnable_artifact',
content: 'Patch implementation',
status: 'in_progress',
priority: 'high',
},
{
id: 'check',
kind: 'public_check',
content: 'Run public check',
status: 'pending',
priority: 'high',
},
],
source: { kind: 'model_tool', toolCallId: 'tool-2' },
},
});
assert.match(rendered ?? '', /Heavy-task progress state/);
assert.match(rendered ?? '', /README\.md/);
assert.match(rendered ?? '', /Active todo: artifact/);
assert.match(rendered ?? '', /runnable_artifact/);
assert.match(rendered ?? '', /public_check/);
});
});
function idFactory(): () => string {
let i = 0;
return () => `id-${++i}`;
}