Skip to content

Commit 54d0c57

Browse files
gewenyu99claude
andcommitted
feat(orchestrator): enqueue_task, complete_task, read_handoffs tools with guards
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5159678 commit 54d0c57

3 files changed

Lines changed: 484 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import * as fs from 'fs';
2+
import * as os from 'os';
3+
import * as path from 'path';
4+
import { QueueStore, type QueuedTask } from '@lib/programs/orchestrator/queue';
5+
import {
6+
applyComplete,
7+
applyEnqueue,
8+
applyReadHandoffs,
9+
checkEnqueueGuards,
10+
type OrchestratorToolsContext,
11+
} from '@lib/programs/orchestrator/queue-tools';
12+
13+
function tmpDir(): string {
14+
return fs.mkdtempSync(path.join(os.tmpdir(), 'queue-tools-test-'));
15+
}
16+
17+
const VALID = ['install', 'init', 'capture'];
18+
19+
describe('checkEnqueueGuards', () => {
20+
let dir: string;
21+
let store: QueueStore;
22+
let ctx: OrchestratorToolsContext;
23+
24+
beforeEach(() => {
25+
dir = tmpDir();
26+
store = new QueueStore(dir, 'run-1');
27+
ctx = {
28+
store,
29+
validTypes: VALID,
30+
getCurrentTaskId: () => undefined,
31+
limits: { maxTasks: 3, maxDepth: 2, maxPerType: 1 },
32+
};
33+
});
34+
35+
afterEach(() => fs.rmSync(dir, { recursive: true, force: true }));
36+
37+
it('rejects an unknown type', () => {
38+
const r = checkEnqueueGuards(ctx, { type: 'nope', reason: 'x' }, undefined);
39+
expect(r).toMatchObject({ ok: false, guard: 'unknown-type' });
40+
});
41+
42+
it('rejects an unknown dependency', () => {
43+
const r = checkEnqueueGuards(
44+
ctx,
45+
{ type: 'init', dependsOn: ['ghost'], reason: 'x' },
46+
undefined,
47+
);
48+
expect(r).toMatchObject({ ok: false, guard: 'unknown-dep' });
49+
});
50+
51+
it('trips the depth guard past maxDepth', () => {
52+
const parent = { depth: 2 } as QueuedTask;
53+
const r = checkEnqueueGuards(ctx, { type: 'init', reason: 'x' }, parent);
54+
expect(r).toMatchObject({ ok: false, guard: 'depth' });
55+
});
56+
57+
it('trips dedup on the same type and inputs', async () => {
58+
await store.enqueue({ type: 'install', inputs: { pkg: 'posthog-js' } });
59+
const r = checkEnqueueGuards(
60+
ctx,
61+
{ type: 'install', inputs: { pkg: 'posthog-js' }, reason: 'x' },
62+
undefined,
63+
);
64+
expect(r).toMatchObject({ ok: false, guard: 'dedup' });
65+
});
66+
67+
it('trips the per-type cap', async () => {
68+
await store.enqueue({ type: 'capture', inputs: { a: 1 } });
69+
const r = checkEnqueueGuards(
70+
ctx,
71+
{ type: 'capture', inputs: { a: 2 }, reason: 'x' },
72+
undefined,
73+
);
74+
expect(r).toMatchObject({ ok: false, guard: 'per-type' });
75+
});
76+
77+
it('trips the total budget', async () => {
78+
await store.enqueue({ type: 'install' });
79+
await store.enqueue({ type: 'init' });
80+
await store.enqueue({ type: 'capture' });
81+
const r = checkEnqueueGuards(ctx, { type: 'init', reason: 'x' }, undefined);
82+
expect(r).toMatchObject({ ok: false, guard: 'budget' });
83+
});
84+
85+
it('allows a valid enqueue', () => {
86+
const r = checkEnqueueGuards(ctx, { type: 'init', reason: 'x' }, undefined);
87+
expect(r).toEqual({ ok: true });
88+
});
89+
});
90+
91+
describe('apply functions', () => {
92+
let dir: string;
93+
let store: QueueStore;
94+
let currentId: string | undefined;
95+
let ctx: OrchestratorToolsContext;
96+
97+
beforeEach(() => {
98+
dir = tmpDir();
99+
store = new QueueStore(dir, 'run-1');
100+
currentId = undefined;
101+
ctx = {
102+
store,
103+
validTypes: VALID,
104+
getCurrentTaskId: () => currentId,
105+
};
106+
});
107+
108+
afterEach(() => fs.rmSync(dir, { recursive: true, force: true }));
109+
110+
it('seeds at depth 0 by the orchestrator', async () => {
111+
const r = await applyEnqueue(ctx, { type: 'install', reason: 'seed' });
112+
expect(r.ok).toBe(true);
113+
if (!r.ok) return;
114+
expect(r.task.depth).toBe(0);
115+
expect(r.task.enqueuedBy).toBe('orchestrator');
116+
});
117+
118+
it('enqueues a child one level deeper, attributed to the running task', async () => {
119+
const parent = await store.enqueue({ type: 'init' });
120+
currentId = parent.id;
121+
const r = await applyEnqueue(ctx, { type: 'capture', reason: 'follow-up' });
122+
expect(r.ok).toBe(true);
123+
if (!r.ok) return;
124+
expect(r.task.depth).toBe(parent.depth + 1);
125+
expect(r.task.enqueuedBy).toBe(parent.id);
126+
});
127+
128+
it('complete_task fails when no task is running', async () => {
129+
const r = await applyComplete(ctx, {
130+
status: 'done',
131+
handoff: { goals: 'g', did: 'd', forNextAgent: 'n' },
132+
});
133+
expect(r.ok).toBe(false);
134+
});
135+
136+
it('complete_task marks the running task done and stores the handoff', async () => {
137+
const t = await store.enqueue({ type: 'install' });
138+
currentId = t.id;
139+
await store.start(t.id);
140+
const r = await applyComplete(ctx, {
141+
status: 'done',
142+
handoff: { goals: 'g', did: 'added sdk', forNextAgent: 'env next' },
143+
});
144+
expect(r.ok).toBe(true);
145+
expect(store.get(t.id)?.status).toBe('done');
146+
expect(store.readHandoff(t.id)?.did).toBe('added sdk');
147+
});
148+
149+
it('read_handoffs returns a dependency handoff for the running task', async () => {
150+
const dep = await store.enqueue({ type: 'install' });
151+
await store.start(dep.id);
152+
await store.complete(dep.id, {
153+
goals: 'g',
154+
did: 'installed',
155+
forNextAgent: 'now init',
156+
});
157+
const t = await store.enqueue({ type: 'init', dependsOn: [dep.id] });
158+
currentId = t.id;
159+
160+
const handoffs = applyReadHandoffs(ctx, {});
161+
expect(handoffs).toHaveLength(1);
162+
expect(handoffs[0].did).toBe('installed');
163+
});
164+
});

0 commit comments

Comments
 (0)