feat: add shadow LangGraph orchestrator runtime

This commit is contained in:
john
2026-07-24 21:25:40 +08:00
parent 46ea22b342
commit 24336d0178
29 changed files with 3247 additions and 25 deletions
+92
View File
@@ -374,6 +374,98 @@ test('agent run creation stores code tool metadata in the queued message', async
});
});
test('code run shadow observation is fire-and-forget and records completion separately', async () => {
const pool = createFakePool();
let releaseObservation;
let observedInput = null;
const observation = new Promise((resolve) => {
releaseObservation = resolve;
});
const gateway = createAgentRunGateway({
pool,
userAuth: {},
tkmindProxy: {},
autoDispatch: false,
observeWorkflowRun: async (input) => {
observedInput = input;
return observation;
},
});
const run = await gateway.createRun('user-shadow', {
requestId: 'req-shadow',
sessionId: 'session-shadow',
userMessage: { role: 'user', content: [{ type: 'text', text: 'refactor this' }] },
toolMode: 'code',
taskType: 'repo_refactor',
});
assert.equal(run.status, 'queued');
await waitFor(() => observedInput != null);
assert.equal(observedInput.runId, run.id);
assert.equal(observedInput.workflowName, 'code-run-v1');
assert.equal(
pool.events.some((event) => event.eventType === 'workflow_shadow_completed'),
false,
);
releaseObservation({
observed: true,
engine: 'langgraph',
mode: 'shadow',
configVersion: 3,
shadowRun: { status: 'succeeded', phase: 'completed' },
});
await waitFor(() => pool.events.some(
(event) => event.eventType === 'workflow_shadow_completed',
));
const event = pool.events.find((item) => item.eventType === 'workflow_shadow_completed');
assert.deepEqual(JSON.parse(event.dataJson), {
engine: 'langgraph',
mode: 'shadow',
configVersion: 3,
status: 'succeeded',
phase: 'completed',
});
});
test('shadow observer failure cannot fail a Native run and chat runs are not observed', async () => {
const pool = createFakePool();
let observations = 0;
const gateway = createAgentRunGateway({
pool,
userAuth: {},
tkmindProxy: {},
autoDispatch: false,
observeWorkflowRun: async () => {
observations += 1;
const error = new Error('shadow unavailable');
error.code = 'SHADOW_UNAVAILABLE';
throw error;
},
});
const codeRun = await gateway.createRun('user-shadow', {
requestId: 'req-shadow-failure',
userMessage: { role: 'user', content: 'change code' },
toolMode: 'code',
});
const chatRun = await gateway.createRun('user-shadow', {
requestId: 'req-chat-no-shadow',
userMessage: { role: 'user', content: 'hello' },
toolMode: 'chat',
});
assert.equal(codeRun.status, 'queued');
assert.equal(chatRun.status, 'queued');
await waitFor(() => pool.events.some(
(event) => event.eventType === 'workflow_shadow_failed',
));
assert.equal(observations, 1);
const failure = pool.events.find((event) => event.eventType === 'workflow_shadow_failed');
assert.equal(JSON.parse(failure.dataJson).code, 'SHADOW_UNAVAILABLE');
});
test('agent run starts a session and marks submitted reply as succeeded', async () => {
const pool = createFakePool();
const submitted = [];