Skip to content

Commit 1a78457

Browse files
committed
feat(core): implement detailed failure context builder for AI fallback execution
1 parent 3097cae commit 1a78457

File tree

3 files changed

+123
-94
lines changed

3 files changed

+123
-94
lines changed

packages/core/src/agent/agent.ts

Lines changed: 5 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,99 +1264,15 @@ export class Agent<
12641264
await player.run();
12651265

12661266
if (player.status === 'error') {
1267-
const totalTasks = player.taskStatusList.length;
1268-
1269-
// Collect completed tasks
1270-
const completedTasks = player.taskStatusList
1271-
.filter((task) => task.status === 'done')
1272-
.map((task) => ({ index: task.index, name: task.name }));
1273-
1274-
// Collect failed tasks
1275-
const failedTasks = player.taskStatusList
1276-
.filter((task) => task.status === 'error')
1277-
.map((task) => ({
1278-
index: task.index,
1279-
name: task.name,
1280-
error: task.error,
1281-
currentStep: task.currentStep,
1282-
totalSteps: task.totalSteps,
1283-
}));
1284-
1285-
// Collect pending tasks
1286-
const pendingTasks = player.taskStatusList
1287-
.filter((task) => task.status === 'init')
1288-
.map((task) => ({ index: task.index, name: task.name }));
1289-
1290-
// Build detailed fallback context for AI
1291-
const fallbackContextParts: string[] = [];
1292-
1293-
// Title
1294-
if (failedTasks.length > 0) {
1295-
const firstFailed = failedTasks[0];
1296-
fallbackContextParts.push(
1297-
`Previous cached workflow execution failed at step ${firstFailed.index + 1}/${totalTasks}:\n`,
1298-
);
1299-
} else {
1300-
fallbackContextParts.push(
1301-
'Previous cached workflow execution failed.\n',
1302-
);
1303-
}
1304-
1305-
// Completed steps
1306-
if (completedTasks.length > 0) {
1307-
fallbackContextParts.push('Completed successfully:');
1308-
for (const task of completedTasks) {
1309-
fallbackContextParts.push(
1310-
` ✓ Step ${task.index + 1}/${totalTasks}: "${task.name}"`,
1311-
);
1312-
}
1313-
fallbackContextParts.push('');
1314-
}
1315-
1316-
// Failed steps
1317-
if (failedTasks.length > 0) {
1318-
fallbackContextParts.push('Failed:');
1319-
for (const task of failedTasks) {
1320-
const stepInfo =
1321-
task.currentStep !== undefined
1322-
? ` (at substep ${task.currentStep + 1}/${task.totalSteps})`
1323-
: '';
1324-
fallbackContextParts.push(
1325-
` ✗ Step ${task.index + 1}/${totalTasks}: "${task.name}"${stepInfo}`,
1326-
);
1327-
fallbackContextParts.push(
1328-
` Error: ${task.error?.message || 'Unknown error'}`,
1329-
);
1330-
}
1331-
fallbackContextParts.push('');
1332-
}
1333-
1334-
// Pending steps
1335-
if (pendingTasks.length > 0) {
1336-
fallbackContextParts.push('Remaining steps (not executed):');
1337-
for (const task of pendingTasks) {
1338-
fallbackContextParts.push(
1339-
` - Step ${task.index + 1}/${totalTasks}: "${task.name}"`,
1340-
);
1341-
}
1342-
fallbackContextParts.push('');
1343-
}
1344-
1345-
// Guidance
1346-
if (failedTasks.length > 0) {
1347-
const firstFailed = failedTasks[0];
1348-
fallbackContextParts.push(
1349-
`Please continue from Step ${firstFailed.index + 1} and avoid repeating the successful steps.`,
1350-
);
1351-
}
1352-
1353-
const fallbackContext = fallbackContextParts.join('\n');
1267+
const { fallbackContext, completedTasks, failedTasks, pendingTasks } =
1268+
player.buildFailureContext();
13541269

13551270
// Build error message for logging
1271+
const totalTasks = player.taskStatusList.length;
13561272
const errors = failedTasks
13571273
.map(
1358-
(task) =>
1359-
`task ${task.index + 1}/${totalTasks} "${task.name}": ${task.error?.message}`,
1274+
(t) =>
1275+
`task ${t.index + 1}/${totalTasks} "${t.name}": ${t.error?.message}`,
13601276
)
13611277
.join('\n');
13621278

packages/core/src/yaml/player.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,101 @@ export class ScriptPlayer<T extends MidsceneYamlScriptEnv> {
202202
this.errorInSetup = error;
203203
}
204204

205+
/**
206+
* Build detailed failure context for AI fallback when execution fails
207+
*/
208+
buildFailureContext(): {
209+
fallbackContext: string;
210+
completedTasks: { index: number; name: string }[];
211+
failedTasks: {
212+
index: number;
213+
name: string;
214+
error?: Error;
215+
currentStep?: number;
216+
totalSteps: number;
217+
}[];
218+
pendingTasks: { index: number; name: string }[];
219+
} {
220+
const totalTasks = this.taskStatusList.length;
221+
222+
const completedTasks = this.taskStatusList
223+
.filter((t) => t.status === 'done')
224+
.map((t) => ({ index: t.index, name: t.name }));
225+
226+
const failedTasks = this.taskStatusList
227+
.filter((t) => t.status === 'error')
228+
.map((t) => ({
229+
index: t.index,
230+
name: t.name,
231+
error: t.error,
232+
currentStep: t.currentStep,
233+
totalSteps: t.totalSteps,
234+
}));
235+
236+
const pendingTasks = this.taskStatusList
237+
.filter((t) => t.status === 'init')
238+
.map((t) => ({ index: t.index, name: t.name }));
239+
240+
const parts: string[] = [];
241+
242+
// Title
243+
if (failedTasks.length > 0) {
244+
parts.push(
245+
`Previous cached workflow execution failed at step ${failedTasks[0].index + 1}/${totalTasks}:\n`,
246+
);
247+
} else {
248+
parts.push('Previous cached workflow execution failed.\n');
249+
}
250+
251+
// Completed
252+
if (completedTasks.length > 0) {
253+
parts.push('Completed successfully:');
254+
for (const t of completedTasks) {
255+
parts.push(` ✓ Step ${t.index + 1}/${totalTasks}: "${t.name}"`);
256+
}
257+
parts.push('');
258+
}
259+
260+
// Failed
261+
if (failedTasks.length > 0) {
262+
parts.push('Failed:');
263+
for (const t of failedTasks) {
264+
const stepInfo =
265+
t.currentStep !== undefined
266+
? ` (at substep ${t.currentStep + 1}/${t.totalSteps})`
267+
: '';
268+
parts.push(
269+
` ✗ Step ${t.index + 1}/${totalTasks}: "${t.name}"${stepInfo}`,
270+
);
271+
parts.push(` Error: ${t.error?.message || 'Unknown error'}`);
272+
}
273+
parts.push('');
274+
}
275+
276+
// Pending
277+
if (pendingTasks.length > 0) {
278+
parts.push('Remaining steps (not executed):');
279+
for (const t of pendingTasks) {
280+
parts.push(` - Step ${t.index + 1}/${totalTasks}: "${t.name}"`);
281+
}
282+
parts.push('');
283+
}
284+
285+
// Guidance
286+
if (failedTasks.length > 0) {
287+
parts.push(
288+
`Please continue from Step ${failedTasks[0].index + 1} and avoid repeating the successful steps.`,
289+
);
290+
}
291+
292+
return {
293+
fallbackContext: parts.join('\n'),
294+
completedTasks,
295+
failedTasks,
296+
pendingTasks,
297+
};
298+
}
299+
205300
private notifyCurrentTaskStatusChange(taskIndex?: number) {
206301
const taskIndexToNotify =
207302
typeof taskIndex === 'number' ? taskIndex : this.currentTaskIndex;

packages/core/tests/unit-test/agent-cache-fallback.test.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ Please continue from Step 3 and avoid repeating the successful steps.`,
119119
{ index: 1, name: 'Enter username' },
120120
],
121121
failedTasksDetailed: [
122-
{ index: 2, name: 'Enter password', error: new Error('Element not found'), totalSteps: 1 },
122+
{
123+
index: 2,
124+
name: 'Enter password',
125+
error: new Error('Element not found'),
126+
totalSteps: 1,
127+
},
123128
],
124129
pendingTasks: [],
125130
};
@@ -156,7 +161,9 @@ Please continue from Step 3 and avoid repeating the successful steps.`,
156161
const actionCall = (agent.taskExecutor.action as any).mock.calls[0];
157162
const contextParam = actionCall[4]; // 5th parameter is aiActContext
158163

159-
expect(contextParam).toContain('Previous cached workflow execution failed at step 3/3');
164+
expect(contextParam).toContain(
165+
'Previous cached workflow execution failed at step 3/3',
166+
);
160167
expect(contextParam).toContain('Step 1/3:');
161168
expect(contextParam).toContain('Step 2/3:');
162169
expect(contextParam).toContain('Step 3/3:');
@@ -191,7 +198,9 @@ Please continue from Step 3 and avoid repeating the successful steps.`,
191198
const mockError = new Error('YAML execution failed');
192199
(mockError as any).executionContext = {
193200
successfulTasks: ['Click login'],
194-
failedTasks: [{ name: 'Enter username', error: new Error('Field not found') }],
201+
failedTasks: [
202+
{ name: 'Enter username', error: new Error('Field not found') },
203+
],
195204
totalTasks: 2,
196205
fallbackContext: `Previous cached workflow execution failed at step 2/2:
197206
@@ -204,7 +213,14 @@ Failed:
204213
205214
Please continue from Step 2 and avoid repeating the successful steps.`,
206215
completedTasks: [{ index: 0, name: 'Click login' }],
207-
failedTasksDetailed: [{ index: 1, name: 'Enter username', error: new Error('Field not found'), totalSteps: 1 }],
216+
failedTasksDetailed: [
217+
{
218+
index: 1,
219+
name: 'Enter username',
220+
error: new Error('Field not found'),
221+
totalSteps: 1,
222+
},
223+
],
208224
pendingTasks: [],
209225
};
210226

@@ -236,7 +252,9 @@ Please continue from Step 2 and avoid repeating the successful steps.`,
236252

237253
expect(contextParam).toContain('User is on login page');
238254
expect(contextParam).toContain('--- Cache Execution Failed ---');
239-
expect(contextParam).toContain('Previous cached workflow execution failed at step 2/2');
255+
expect(contextParam).toContain(
256+
'Previous cached workflow execution failed at step 2/2',
257+
);
240258
expect(contextParam).toContain('✓ Step 1/2: "Click login"');
241259
expect(contextParam).toContain('✗ Step 2/2: "Enter username"');
242260
});

0 commit comments

Comments
 (0)