Skip to content
76 changes: 67 additions & 9 deletions packages/core/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,10 @@ export class Agent<
isVlmUiTars || cacheable === false
? undefined
: this.taskCache?.matchPlanCache(taskPrompt);

// Use local variable for context to avoid modifying instance state
let contextForPlanning = this.aiActContext;

if (
matchedCache &&
this.taskCache?.isCacheResultUsed &&
Expand All @@ -879,8 +883,41 @@ export class Agent<
);

debug('matched cache, will call .runYaml to run the action');
const yaml = matchedCache.cacheContent.yamlWorkflow;
return this.runYaml(yaml);
const yamlContent = matchedCache.cacheContent.yamlWorkflow;
try {
return await this.runYaml(yamlContent);
} catch (cacheError) {
// Cache execution failed, fall back to normal AI planning
debug(
'cache execution failed, falling back to AI planning:',
cacheError instanceof Error ? cacheError.message : String(cacheError),
);

// Extract execution context
const executionContext = (cacheError as any)?.executionContext;
let fallbackContext: string;

if (executionContext?.fallbackContext) {
// Use pre-built context from runYaml
fallbackContext = executionContext.fallbackContext;
} else {
// Fallback: no detailed context available
const errorMessage =
cacheError instanceof Error
? cacheError.message
: String(cacheError);
fallbackContext = [
'Previous cached workflow execution failed.',
`Error: ${errorMessage}`,
'Please retry with a different approach.',
].join('\n');
}

// Append failure context to original aiActContext using local variable
contextForPlanning = this.aiActContext
? `${this.aiActContext}\n\n--- Cache Execution Failed ---\n${fallbackContext}`
: fallbackContext;
}
}

// If cache matched but yamlWorkflow is empty, fall through to normal execution
Expand All @@ -895,7 +932,7 @@ export class Agent<
modelConfigForPlanning,
defaultIntentModelConfig,
includeBboxInPlanning,
this.aiActContext,
contextForPlanning,
cacheable,
replanningCycleLimit,
imagesIncludeCount,
Expand Down Expand Up @@ -1227,13 +1264,34 @@ export class Agent<
await player.run();

if (player.status === 'error') {
const errors = player.taskStatusList
.filter((task) => task.status === 'error')
.map((task) => {
return `task - ${task.name}: ${task.error?.message}`;
})
const { fallbackContext, completedTasks, failedTasks, pendingTasks } =
player.buildFailureContext();

// Build error message for logging
const totalTasks = player.taskStatusList.length;
const errors = failedTasks
.map(
(t) =>
`task ${t.index + 1}/${totalTasks} "${t.name}": ${t.error?.message}`,
)
.join('\n');
throw new Error(`Error(s) occurred in running yaml script:\n${errors}`);

const error = new Error(
`Error(s) occurred in running yaml script:\n${errors}`,
);

// Attach execution context (backward compatible + new fields)
(error as any).executionContext = {
successfulTasks: completedTasks.map((t) => t.name),
failedTasks: failedTasks.map((t) => ({ name: t.name, error: t.error })),
totalTasks,
fallbackContext,
completedTasks,
failedTasksDetailed: failedTasks,
pendingTasks,
};

throw error;
}

return {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export interface FreeFn {
}

export interface ScriptPlayerTaskStatus extends MidsceneYamlTask {
index: number;
status: ScriptPlayerStatusValue;
currentStep?: number;
totalSteps: number;
Expand Down
95 changes: 95 additions & 0 deletions packages/core/src/yaml/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,101 @@ export class ScriptPlayer<T extends MidsceneYamlScriptEnv> {
this.errorInSetup = error;
}

/**
* Build detailed failure context for AI fallback when execution fails
*/
buildFailureContext(): {
fallbackContext: string;
completedTasks: { index: number; name: string }[];
failedTasks: {
index: number;
name: string;
error?: Error;
currentStep?: number;
totalSteps: number;
}[];
pendingTasks: { index: number; name: string }[];
} {
const totalTasks = this.taskStatusList.length;

const completedTasks = this.taskStatusList
.filter((t) => t.status === 'done')
.map((t) => ({ index: t.index, name: t.name }));

const failedTasks = this.taskStatusList
.filter((t) => t.status === 'error')
.map((t) => ({
index: t.index,
name: t.name,
error: t.error,
currentStep: t.currentStep,
totalSteps: t.totalSteps,
}));

const pendingTasks = this.taskStatusList
.filter((t) => t.status === 'init')
.map((t) => ({ index: t.index, name: t.name }));

const parts: string[] = [];

// Title
if (failedTasks.length > 0) {
parts.push(
`Previous cached workflow execution failed at step ${failedTasks[0].index + 1}/${totalTasks}:\n`,
);
} else {
parts.push('Previous cached workflow execution failed.\n');
}

// Completed
if (completedTasks.length > 0) {
parts.push('Completed successfully:');
for (const t of completedTasks) {
parts.push(` ✓ Step ${t.index + 1}/${totalTasks}: "${t.name}"`);
}
parts.push('');
}

// Failed
if (failedTasks.length > 0) {
parts.push('Failed:');
for (const t of failedTasks) {
const stepInfo =
t.currentStep !== undefined
? ` (at substep ${t.currentStep + 1}/${t.totalSteps})`
: '';
parts.push(
` ✗ Step ${t.index + 1}/${totalTasks}: "${t.name}"${stepInfo}`,
);
parts.push(` Error: ${t.error?.message || 'Unknown error'}`);
}
parts.push('');
}

// Pending
if (pendingTasks.length > 0) {
parts.push('Remaining steps (not executed):');
for (const t of pendingTasks) {
parts.push(` - Step ${t.index + 1}/${totalTasks}: "${t.name}"`);
}
parts.push('');
}

// Guidance
if (failedTasks.length > 0) {
parts.push(
`Please continue from Step ${failedTasks[0].index + 1} and avoid repeating the successful steps.`,
);
}

return {
fallbackContext: parts.join('\n'),
completedTasks,
failedTasks,
pendingTasks,
};
}

private notifyCurrentTaskStatusChange(taskIndex?: number) {
const taskIndexToNotify =
typeof taskIndex === 'number' ? taskIndex : this.currentTaskIndex;
Expand Down
Loading