Skip to content
Open
2 changes: 1 addition & 1 deletion packages/core/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ export class Agent<
}

// If cache matched but yamlWorkflow is empty, fall through to normal execution
const imagesIncludeCount: number | undefined = deepThink ? undefined : 2;
const imagesIncludeCount: number = deepThink ? 2 : 1;
const { output: actionOutput } = await this.taskExecutor.action(
taskPrompt,
modelConfigForPlanning,
Expand Down
89 changes: 80 additions & 9 deletions packages/core/src/ai-model/conversation-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class ConversationHistory {
private readonly messages: ChatCompletionMessageParam[] = [];
private subGoals: SubGoal[] = [];
private memories: string[] = [];
private historicalLogs: string[] = [];

public pendingFeedbackMessage: string;

Expand Down Expand Up @@ -38,6 +39,9 @@ export class ConversationHistory {

reset() {
this.messages.length = 0;
this.memories.length = 0;
this.subGoals.length = 0;
this.historicalLogs.length = 0;
}

/**
Expand Down Expand Up @@ -106,7 +110,8 @@ export class ConversationHistory {
}

/**
* Update a single sub-goal by index
* Update a single sub-goal by index.
* Clears logs if status or description actually changes.
* @returns true if the sub-goal was found and updated, false otherwise
*/
updateSubGoal(
Expand All @@ -118,23 +123,36 @@ export class ConversationHistory {
return false;
}

if (updates.status !== undefined) {
let changed = false;

if (updates.status !== undefined && updates.status !== goal.status) {
goal.status = updates.status;
changed = true;
}
if (updates.description !== undefined) {
if (
updates.description !== undefined &&
updates.description !== goal.description
) {
goal.description = updates.description;
changed = true;
}

if (changed) {
goal.logs = [];
}

return true;
}

/**
* Mark the first pending sub-goal as running
* Mark the first pending sub-goal as running.
* Clears logs since status changes.
*/
markFirstPendingAsRunning(): void {
const firstPending = this.subGoals.find((g) => g.status === 'pending');
if (firstPending) {
firstPending.status = 'running';
firstPending.logs = [];
}
}

Expand All @@ -152,16 +170,38 @@ export class ConversationHistory {
}

/**
* Mark all sub-goals as finished
* Mark all sub-goals as finished.
* Clears logs for any goal whose status actually changes.
*/
markAllSubGoalsFinished(): void {
for (const goal of this.subGoals) {
if (goal.status !== 'finished') {
goal.logs = [];
}
goal.status = 'finished';
}
}

/**
* Convert sub-goals to text representation
* Append a log entry to the currently running sub-goal.
* The log describes an action performed while working on the sub-goal.
*/
appendSubGoalLog(log: string): void {
if (!log) {
return;
}
const runningGoal = this.subGoals.find((g) => g.status === 'running');
if (runningGoal) {
if (!runningGoal.logs) {
runningGoal.logs = [];
}
runningGoal.logs.push(log);
}
}

/**
* Convert sub-goals to text representation.
* Includes actions performed (logs) for the current sub-goal.
*/
subGoalsToText(): string {
if (this.subGoals.length === 0) {
Expand All @@ -176,13 +216,44 @@ export class ConversationHistory {
const currentGoal =
this.subGoals.find((goal) => goal.status === 'running') ||
this.subGoals.find((goal) => goal.status === 'pending');
const currentGoalText = currentGoal
? `\nCurrent sub-goal is: ${currentGoal.description}`
: '';

let currentGoalText = '';
if (currentGoal) {
currentGoalText = `\nCurrent sub-goal is: ${currentGoal.description}`;
if (currentGoal.logs && currentGoal.logs.length > 0) {
const logLines = currentGoal.logs.map((log) => `- ${log}`).join('\n');
currentGoalText += `\nActions performed for current sub-goal:\n${logLines}`;
}
}

return `Sub-goals:\n${lines.join('\n')}${currentGoalText}`;
}

// Historical log management methods (used in non-deepThink mode)

/**
* Append a log entry to the historical logs list.
* Used in non-deepThink mode to track executed steps across planning rounds.
*/
appendHistoricalLog(log: string): void {
if (log) {
this.historicalLogs.push(log);
}
}

/**
* Convert historical logs to text representation.
* Provides context about previously executed steps to the model.
*/
historicalLogsToText(): string {
if (this.historicalLogs.length === 0) {
return '';
}

const logLines = this.historicalLogs.map((log) => `- ${log}`).join('\n');
return `Here are the steps that have been executed:\n${logLines}`;
}

// Memory management methods

/**
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/ai-model/llm-planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ export async function plan(

let latestFeedbackMessage: ChatCompletionMessageParam;

// Build sub-goal status text to include in the message (only when deepThink is enabled)
// Build sub-goal status text to include in the message
// In deepThink mode: show full sub-goals with logs
// In non-deepThink mode: show historical execution logs
const subGoalsText = includeSubGoals
? conversationHistory.subGoalsToText()
: '';
: conversationHistory.historicalLogsToText();
const subGoalsSection = subGoalsText ? `\n\n${subGoalsText}` : '';

// Build memories text to include in the message
Expand Down Expand Up @@ -318,6 +320,15 @@ export async function plan(
conversationHistory.markSubGoalFinished(index);
}
}
// Append the planning log to the currently running sub-goal
if (planFromAI.log) {
conversationHistory.appendSubGoalLog(planFromAI.log);
}
} else {
// In non-deepThink mode, accumulate logs as historical execution steps
if (planFromAI.log) {
conversationHistory.appendHistoricalLog(planFromAI.log);
}
}

// Append memory to conversation history if present
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/ai-model/prompt/llm-planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,10 @@ The previous action has been executed, here is the latest screenshot. Please con
Sub-goals:
1. Fill in the Name field with 'John' (running)
2. Fill in the Email field with 'john@example.com' (pending)
3. Return the filled email address (pending)
Current sub-goal is: Fill in the Name field with 'John'
Actions performed for current sub-goal:
- Click on the Name field to start filling the form

**Screenshot:** [Shows the form with Name field now focused/active]

Expand All @@ -504,7 +507,11 @@ The previous action has been executed, here is the latest screenshot. Please con
Sub-goals:
1. Fill in the Name field with 'John' (running)
2. Fill in the Email field with 'john@example.com' (pending)
3. Return the filled email address (pending)
Current sub-goal is: Fill in the Name field with 'John'
Actions performed for current sub-goal:
- Click on the Name field to start filling the form
- Typing 'John' into the Name field

**Screenshot:** [Shows the form with Name field containing 'John']

Expand All @@ -530,7 +537,10 @@ The previous action has been executed, here is the latest screenshot. Please con
Sub-goals:
1. Fill in the Name field with 'John' (finished)
2. Fill in the Email field with 'john@example.com' (running)
3. Return the filled email address (pending)
Current sub-goal is: Fill in the Email field with 'john@example.com'
Actions performed for current sub-goal:
- Moving to the Email field

**Screenshot:** [Shows the form with Name='John' and Email field focused]

Expand All @@ -554,6 +564,9 @@ Sub-goals:
2. Fill in the Email field with 'john@example.com' (running)
3. Return the filled email address (pending)
Current sub-goal is: Fill in the Email field with 'john@example.com'
Actions performed for current sub-goal:
- Moving to the Email field
- Typing email address into the Email field

**Screenshot:** [Shows the form with Name='John' and Email='john@example.com']

Expand Down
20 changes: 16 additions & 4 deletions packages/core/src/ai-model/service-caller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,26 @@ export async function callAI(
}

content = result.choices[0].message.content!;
if (!content) {
throw new Error('empty content from AI model');
}

accumulatedReasoning =
(result.choices[0].message as any)?.reasoning_content || '';
usage = result.usage;
requestId = result._request_id;

if (
!content &&
accumulatedReasoning &&
modelFamily === 'doubao-vision'
) {
console.warn(
'empty content from AI model, using reasoning content',
);
content = accumulatedReasoning;
}

if (!content) {
throw new Error('empty content from AI model');
}

break; // Success, exit retry loop
} catch (error) {
lastError = error as Error;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ export interface SubGoal {
index: number;
status: SubGoalStatus;
description: string;
logs?: string[];
}

export interface RawResponsePlanningAIResponse {
Expand Down
Loading