feat: split ask_user_question into separate planning and execution functions

Add distinct AskUserQuestionPlanning and AskUserQuestionExecution functions to enable user consultation during both planning and execution phases. Update system prompt to clarify when to seek user input vs. replan. Fix plan area rendering timing and improve execution flow handling.
This commit is contained in:
Andrew Beal 2026-01-07 20:00:40 +00:00
parent 7e71861a4d
commit 19cd82ad7d
13 changed files with 182 additions and 86 deletions

View file

@ -11,8 +11,9 @@ import { Replan } from "./Functions/Replan";
import { CompleteStep } from "./Functions/CompleteStep";
import { SubmitPlan } from "./Functions/SubmitPlan";
import { CancelPlan } from "./Functions/CancelPlan";
import { AskUserQuestion } from "./Functions/AskUserQuestion";
import { AskUserQuestionPlanning } from "./Functions/AskUserQuestionPlanning";
import { CompletePlan } from "./Functions/CompletePlan";
import { AskUserQuestionExecution } from "./Functions/AskUserQuestionExecution";
export abstract class AIFunctionDefinitions {
@ -45,7 +46,7 @@ export abstract class AIFunctionDefinitions {
// Definitions for the planning agent
public static planningAgentDefinitions(): IAIFunctionDefinition[] {
return [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, AskUserQuestion, SubmitPlan];
return [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, AskUserQuestionPlanning, SubmitPlan];
}
// Definitions for the main agent during plan execution
@ -59,6 +60,7 @@ export abstract class AIFunctionDefinitions {
DeleteVaultFiles,
MoveVaultFiles,
CompleteStep,
AskUserQuestionExecution,
Replan,
CompletePlan,
CancelPlan

View file

@ -0,0 +1,31 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
export const AskUserQuestionExecution: IAIFunctionDefinition = {
name: AIFunction.AskUserQuestionExecution,
description: `Asks the user a question during plan execution and waits for their response.
Use this function when you encounter situations during plan execution that require user input or decisions.
This allows interactive execution where unexpected circumstances or ambiguous situations can be resolved with user feedback.
Common use cases:
- Encountering unexpected file states or content that wasn't anticipated in the plan
- Discovering conflicts or duplicates that require user decision
- Handling errors or edge cases where multiple recovery strategies exist
- Confirming destructive or irreversible operations before proceeding
- Requesting missing information that only becomes apparent during execution
- Choosing between alternatives discovered while processing`,
parameters: {
type: "object",
properties: {
question: {
type: "string",
description: "The question to ask the user. Should be clear, specific, and provide enough context for the user to give a meaningful answer. Include relevant details about the current execution state or what was discovered. Use markdown formatting for emphasis, lists, code snippets, or file paths to make the question easier to read. Example: 'While updating `src/utils/parser.ts`, I found an **existing function** `parseMarkdown()` that conflicts with the one I was about to create.\n\nOptions:\n1. Rename the new function to `parseMarkdownExtended()`\n2. Replace the existing function entirely\n3. Merge the functionality into the existing function\n\nWhich approach would you prefer?'"
},
user_message: {
type: "string",
description: "A short message to be displayed to the user explaining what action you are taking. Examples: 'Requesting guidance on conflict resolution', 'Confirming before deleting duplicate notes', 'Asking about unexpected file format'"
}
},
required: ["question", "user_message"]
}
};

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
export const AskUserQuestion: IAIFunctionDefinition = {
name: AIFunction.AskUserQuestion,
export const AskUserQuestionPlanning: IAIFunctionDefinition = {
name: AIFunction.AskUserQuestionPlanning,
description: `Asks the user a question during the planning phase and waits for their response.
Use this function when you need clarification, user input, or decisions while creating the plan.
This allows interactive planning where user feedback shapes the plan before it's submitted.

View file

@ -60,7 +60,12 @@ export const ReplanArgsSchema = z.object({
user_message: z.string()
});
export const AskUserQuestionArgsSchema = z.object({
export const AskUserQuestionPlanningArgsSchema = z.object({
question: z.string(),
user_message: z.string()
});
export const AskUserQuestionExecutionArgsSchema = z.object({
question: z.string(),
user_message: z.string()
});
@ -95,7 +100,8 @@ export type MoveVaultFilesArgs = z.infer<typeof MoveVaultFilesArgsSchema>;
export type ListVaultFilesArgs = z.infer<typeof ListVaultFilesArgsSchema>;
export type CreatePlanArgs = z.infer<typeof CreatePlanArgsSchema>;
export type ReplanArgs = z.infer<typeof ReplanArgsSchema>;
export type AskUserQuestionArgs = z.infer<typeof AskUserQuestionArgsSchema>;
export type AskUserQuestionPlanningArgs = z.infer<typeof AskUserQuestionPlanningArgsSchema>;
export type AskUserQuestionExecutionArgs = z.infer<typeof AskUserQuestionExecutionArgsSchema>;
export type CancelPlanArgs = z.infer<typeof CancelPlanArgsSchema>;
export type CompleteStepArgs = z.infer<typeof CompleteStepArgsSchema>;
export type SubmitPlanArgs = z.infer<typeof SubmitPlanArgsSchema>;

View file

@ -44,6 +44,26 @@ When planning mode is enabled, provide the planning agent with:
2. **Signal completion** after each step to receive the next
3. **Continue until all steps are finished** or a replan is needed
#### Seeking User Input During Execution
While executing a plan, you may encounter situations that require the user's decision or clarification. You have the ability to pause execution and ask the user a question when:
- **Unexpected states**: You discover files, content, or structures that weren't anticipated in the plan
- **Conflicts or ambiguity**: Multiple valid paths exist and you cannot determine the user's preference
- **Destructive operations**: An action would overwrite, delete, or significantly alter existing content
- **Missing information**: Required details only become apparent mid-execution
- **Error recovery**: A step failed and multiple recovery strategies exist
When asking questions during execution:
- Provide clear context about what you discovered or encountered
- Explain why a decision is needed before proceeding
- Present concrete options when applicable
- Use markdown formatting to make the question easy to parse
**Example scenarios:**
- "While creating the project note, I found an existing '[[Project Alpha]]' with similar content. Should I merge these, replace the old one, or create a separate note?"
- "The folder structure specified in the plan doesn't exist. Should I create 'Projects/2024/Q1/' or would you prefer a different organization?"
#### Mandatory Replanning Triggers
**Request a replan when:**
@ -57,6 +77,11 @@ When planning mode is enabled, provide the planning agent with:
- Formatting issues
- Small scope clarifications within a step
**Seek user input (don't replan) when:**
- You need a preference between equally valid options
- Confirming before irreversible actions
- Clarifying ambiguous user intent discovered mid-execution
---
### 3. HISTORICAL CONTEXT INTERPRETATION
@ -171,9 +196,10 @@ For tasks where the user has enabled planning:
2. **Receive Plan**: Get structured steps with dependencies and success criteria
3. **Execute Sequentially**: Work through steps, gathering ground truth
4. **Monitor & Adapt**: Check progress; request replan if needed
5. **Mark Progress**: Signal completion after finishing each step to track progress
6. **Confirm Completion**: Once all steps are done, explicitly mark the plan as complete
7. **Synthesize Results**: Integrate findings across all steps
5. **Consult User When Necessary**: If execution reveals decisions only the user can make, pause and ask before proceeding
6. **Mark Progress**: Signal completion after finishing each step to track progress
7. **Confirm Completion**: Once all steps are done, explicitly mark the plan as complete
8. **Synthesize Results**: Integrate findings across all steps
### Synthesis Phase
@ -204,6 +230,11 @@ After multi-step execution:
- Problem-solving and explanations across any domain
- Programming, writing, and creative tasks with vault context
**Interactive Capabilities**
- Asking clarifying questions during planning to shape the approach
- Consulting the user during execution when decisions require their input
- Providing clear context and options when seeking user guidance
---
## Anti-Patterns to Avoid
@ -220,6 +251,8 @@ After multi-step execution:
Saying "I cannot see/interpret images"
Blindly following a plan when execution reveals it's no longer valid
Replanning for minor issues you can handle directly
Making assumptions about user preferences when the answer affects their data
Proceeding with destructive operations without confirmation when intent is ambiguous
---
@ -234,10 +267,11 @@ After multi-step execution:
6. "Can I infer the answer from related content I found?" Read and reason
7. "Has something changed that invalidates my current plan?" Consider replanning
8. "Am I adapting or do I need strategic guidance?" Replan only for significant pivots
9. "Does this decision require user input?" Ask when facing ambiguity, conflicts, or irreversible actions
**When uncertain**: Always search the vault first. Always try alternative strategies before concluding "not found." Complete the full request before concluding.
**When uncertain**: Always search the vault first. Always try alternative strategies before concluding "not found." Complete the full request before concluding. When execution reveals choices only the user can make, ask clearly and provide context.
---
**Core Philosophy**: Act decisively on user requests. Always use [[wiki-links]] for vault references. Search the vault proactively with progressive strategies never accept a single failed search as final. When executing plans, stay adaptive: replan when reality diverges from assumptions, but handle minor adjustments yourself.
**Core Philosophy**: Act decisively on user requests. Always use [[wiki-links]] for vault references. Search the vault proactively with progressive strategies never accept a single failed search as final. When executing plans, stay adaptive: replan when reality diverges from assumptions, consult the user when facing decisions that require their preference, but handle minor adjustments yourself.
`;

View file

@ -28,7 +28,8 @@
$: separatorHeight = stepElements[0]?.nextElementSibling?.clientHeight ?? 0;
$: if (steps) {
updateHeight();
tick().then(updateHeight);
setTimeout(updateHeight, 500);
}
$: if (activeStepIndex >= 0 && !isTransitioning) {
@ -50,16 +51,14 @@
}
async function updateHeight() {
setTimeout(() => {
if (!contentDiv || stepElements.length === 0) {
return;
}
if (!contentDiv || stepElements.length === 0) {
return;
}
expandedHeight = contentDiv.scrollHeight;
expandedHeight = contentDiv.scrollHeight;
const stepsToShow = Math.min(3, stepElements.length);
collapsedHeight = (stepsToShow * stepHeight) + (Math.max(0, stepsToShow - 1) * separatorHeight);
}, 1000);
const stepsToShow = Math.min(3, stepElements.length);
collapsedHeight = (stepsToShow * stepHeight) + (Math.max(0, stepsToShow - 1) * separatorHeight);
}
async function scrollToActiveStep() {
@ -88,7 +87,7 @@
<span id="chat-planning-in-progress-text">Planning in progress...</span>
</div>
{/if}
{#if $executionPlanState.plan}
{#if steps && steps?.length > 0}
<div id="chat-plan-area"
out:slide
on:click={toggleExpand}
@ -98,7 +97,7 @@
tabindex=0>
<div id="chat-plan-steps-wrapper" style:height="{expanded ? expandedHeight : collapsedHeight}px" bind:this={wrapperDiv}>
<div id="chat-plan-steps" bind:this={contentDiv}>
{#each $executionPlanState.plan.executionSteps as step, index }
{#each steps as step, index }
<div class="chat-plan-step" bind:this={stepElements[index]}>
{#if step.status === ExecutionStatus.Completed}
<div class="chat-plan-step-icon" use:setElementIcon={"circle-check"} style:color="var(--color-green)"></div>
@ -115,7 +114,7 @@
{`${step.step}. ${step.description}`}
</span>
</div>
{#if index < $executionPlanState.plan.executionSteps.length - 1}
{#if index < steps.length - 1}
<div class="chat-plan-step-icon"
use:setElementIcon={"ellipsis-vertical"}
style:opacity={step.status === ExecutionStatus.Completed ? 1 : 0.25}>
@ -124,7 +123,7 @@
{/each}
</div>
</div>
{#if $executionPlanState.plan.executionSteps.length > 2}
{#if steps.length > 2}
<div class="chat-plan-fade top-fade" transition:fade></div>
<div class="chat-plan-fade bottom-fade" transition:fade></div>
{/if}

View file

@ -126,7 +126,7 @@
onPlanningFinished: () => {
busyPlanning = false;
},
onPlanningQuestion: async (question) => {
onUserQuestion: async (question) => {
const displayEl = createEl("div");
const formattedHtml = streamingMarkdownService.formatText(question);
htmlService.setHTMLContent(displayEl, formattedHtml);
@ -141,7 +141,7 @@
onPlanStepUpdate: () => {
executionPlanStore.updatePlan();
},
onPlanComplete: () => {
onPlanReset: () => {
executionPlanStore.clearPlan();
},
onComplete: async () => {

View file

@ -17,7 +17,8 @@ export enum AIFunction {
CompleteStep = "complete_step",
CompletePlan = "complete_plan",
CancelPlan = "cancel_plan",
AskUserQuestion = "ask_user_question",
AskUserQuestionPlanning = "ask_user_question_planning",
AskUserQuestionExecution = "ask_user_question_execution",
Unknown = "unknown"
}

View file

@ -14,7 +14,7 @@ import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionD
import { AIFunction, isAIFunction } from "Enums/AIFunction";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import { Exception } from "Helpers/Exception";
import { AskUserQuestionArgsSchema, CancelPlanArgsSchema, CompletePlanArgsSchema, CompleteStepArgsSchema, CreatePlanArgsSchema, ReplanArgsSchema, SubmitPlanArgsSchema, type CreatePlanArgs, type ReplanArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { AskUserQuestionExecutionArgsSchema, AskUserQuestionPlanningArgsSchema, CancelPlanArgsSchema, CompletePlanArgsSchema, CompleteStepArgsSchema, CreatePlanArgsSchema, ReplanArgsSchema, SubmitPlanArgsSchema, type CreatePlanArgs, type ReplanArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { ExecutionPlan } from "Types/ExecutionPlan";
export class AIControllerService {
@ -63,7 +63,7 @@ export class AIControllerService {
const completedSuccessfully = await this.handlePlanningWorkflow(conversation, functionCall, callbacks);
return { shouldExit: completedSuccessfully };
} finally {
callbacks.onPlanComplete();
callbacks.onPlanReset();
}
}
@ -116,6 +116,7 @@ export class AIControllerService {
this.ai.toolDefinitions = AIFunctionDefinitions.planningAgentDefinitions();
this.planningDepth = 1;
callbacks.onPlanReset();
callbacks.onPlanningStarted();
const executionPlan = await this.runPlanningAgent(this.planningConversation, callbacks);
callbacks.onPlanningFinished();
@ -151,10 +152,11 @@ export class AIControllerService {
}
private async runPlanningAgent(planningConversation: Conversation, callbacks: IChatServiceCallbacks): Promise<ExecutionPlan> {
const isReplan = planningConversation.contents.length > 0;
let capturedPlan: ExecutionPlan | null = null;
if (this.planningDepth >= AIControllerService.MAX_AGENT_DEPTH) {
return new ExecutionPlan({ steps: [] });
return new ExecutionPlan({ steps: [] }, isReplan);
}
this.planningDepth++;
@ -170,18 +172,18 @@ export class AIControllerService {
return { shouldExit: false };
}
if (isAIFunction(functionCallName, AIFunction.AskUserQuestion)) {
const parseResult = AskUserQuestionArgsSchema.safeParse(functionCall.arguments);
if (isAIFunction(functionCallName, AIFunction.AskUserQuestionPlanning)) {
const parseResult = AskUserQuestionPlanningArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.AskUserQuestion}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AIFunction.AskUserQuestionPlanning}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
this.updateThought(functionCall, callbacks);
const answer = await callbacks.onPlanningQuestion(parseResult.data.question);
const answer = await callbacks.onUserQuestion(parseResult.data.question);
planningConversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ answer: answer },
@ -200,8 +202,7 @@ export class AIControllerService {
));
return { shouldExit: false };
}
capturedPlan = new ExecutionPlan(parseResult.data);
// Add response before exiting to avoid orphaned function call
capturedPlan = new ExecutionPlan(parseResult.data, isReplan);
planningConversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ message: "Plan received" },
@ -225,33 +226,38 @@ export class AIControllerService {
}));
return await this.runPlanningAgent(planningConversation, callbacks);
}
return capturedPlan || new ExecutionPlan({ steps: [] });
return capturedPlan || new ExecutionPlan({ steps: [] }, isReplan);
}
// The 'execution agent' is still the main agent but given specific tools related to plan execution
private async runExecutionAgent(conversation: Conversation, executionPlan: ExecutionPlan, callbacks: IChatServiceCallbacks
): Promise<{ planExecutionCancelled: boolean, replanData?: ReplanArgs }> {
const lastCall = conversation.contents[conversation.contents.length - 1];
if (this.executionDepth >= AIControllerService.MAX_AGENT_DEPTH) {
conversation.contents.push(new ConversationContent({
role: Role.User,
content: Copy.MaxExecutionDepthReached,
shouldDisplayContent: false
}));
return { planExecutionCancelled: true };
if (lastCall && lastCall.functionCall) {
conversation.contents.pop(); // remove function call (likely a replan call)
conversation.contents.push(new ConversationContent({
role: Role.User,
content: Copy.MaxExecutionDepthReached,
shouldDisplayContent: false
}));
return { planExecutionCancelled: true };
}
}
this.executionDepth++;
if (executionPlan.executionSteps.length > 0) {
callbacks.onPlanUpdate(executionPlan); // plan is being executed so inform UI
}
const lastCall = conversation.contents[conversation.contents.length - 1];
if (lastCall && lastCall.functionCall) {
if (executionPlan.isReplan && lastCall && lastCall.functionCall) {
const planningResponse = this.createPlanningResponse(lastCall, executionPlan);
if (planningResponse) {
conversation.addFunctionResponse(planningResponse);
}
}
if (executionPlan.executionSteps.length > 0) {
callbacks.onPlanUpdate(executionPlan); // plan is being executed so inform UI
}
let replanData: ReplanArgs | undefined;
let planExecutionCancelled = false;
@ -259,44 +265,24 @@ export class AIControllerService {
await this.runAgentLoop(conversation, callbacks, async (functionCall) => {
const functionCallName = functionCall.name;
if (isAIFunction(functionCallName, AIFunction.CancelPlan)) {
const parseResult = CancelPlanArgsSchema.safeParse(functionCall.arguments);
if (isAIFunction(functionCallName, AIFunction.AskUserQuestionExecution)) {
const parseResult = AskUserQuestionExecutionArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CancelPlan}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AIFunction.AskUserQuestionExecution}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
planExecutionCancelled = parseResult.data.confirm_cancellation;
this.updateThought(functionCall, callbacks);
const answer = await callbacks.onUserQuestion(parseResult.data.question);
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ message: planExecutionCancelled ? Copy.PlanExecutionCancelled : Copy.ConfirmationFalse },
{ answer: answer, completion_reminder: Copy.CompletionReminder },
functionCall.toolId
));
return { shouldExit: planExecutionCancelled };
}
if (isAIFunction(functionCallName, AIFunction.Replan)) {
const parseResult = ReplanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.Replan}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
// Capture replan data and exit execution loop to trigger replanning
replanData = parseResult.data;
// Add response before exiting to avoid orphaned function call
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ message: "Replanning initiated" },
functionCall.toolId
));
return { shouldExit: true };
return { shouldExit: false };
}
if (isAIFunction(functionCallName, AIFunction.CompleteStep)) {
@ -338,13 +324,47 @@ export class AIControllerService {
return { shouldExit: false };
}
if (isAIFunction(functionCallName, AIFunction.Replan)) {
const parseResult = ReplanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.Replan}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
// Capture replan data and exit execution loop to trigger replanning
replanData = parseResult.data;
return { shouldExit: true };
}
if (isAIFunction(functionCallName, AIFunction.CancelPlan)) {
const parseResult = CancelPlanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CancelPlan}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
planExecutionCancelled = parseResult.data.confirm_cancellation;
conversation.addFunctionResponse(new AIFunctionResponse(
functionCallName,
{ message: planExecutionCancelled ? Copy.PlanExecutionCancelled : Copy.ConfirmationFalse },
functionCall.toolId
));
return { shouldExit: planExecutionCancelled };
}
this.updateThought(functionCall, callbacks);
const functionResponse = await this.aiFunctionService.performAIFunction(functionCall);
conversation.addFunctionResponse(functionResponse);
return { shouldExit: false };
});
if (!executionPlan.completed()) {
if (!executionPlan.completed() && !replanData && !planExecutionCancelled) {
conversation.contents.push(new ConversationContent({
role: Role.User,
content: replaceCopy(Copy.IncompleteExecutionAttempt,
@ -455,7 +475,7 @@ export class AIControllerService {
if (capturedFunctionCall) {
conversationContent.functionCall = capturedFunctionCall.toConversationString();
conversationContent.toolId = capturedFunctionCall.toolId;
//conversationContent.shouldDisplayContent = false;
conversationContent.shouldDisplayContent = sanitizedContent.trim() !== "";
if (capturedFunctionCall.thoughtSignature) {
conversationContent.thoughtSignature = capturedFunctionCall.thoughtSignature;
}

View file

@ -124,7 +124,8 @@ export class AIFunctionService {
case AIFunction.CreatePlan:
case AIFunction.Replan:
case AIFunction.SubmitPlan:
case AIFunction.AskUserQuestion:
case AIFunction.AskUserQuestionPlanning:
case AIFunction.AskUserQuestionExecution:
case AIFunction.CompleteStep:
case AIFunction.CompletePlan:
case AIFunction.CancelPlan: {

View file

@ -23,10 +23,10 @@ export interface IChatServiceCallbacks {
onThoughtUpdate: (thought: string | null) => void;
onPlanningStarted: () => void;
onPlanningFinished: () => void;
onPlanningQuestion: (question: string) => Promise<string>;
onUserQuestion: (question: string) => Promise<string>;
onPlanUpdate: (executionPlan: ExecutionPlan) => void;
onPlanStepUpdate: () => void;
onPlanComplete: () => void;
onPlanReset: () => void;
onComplete: () => void;
}

View file

@ -5,9 +5,10 @@ import { Copy, replaceCopy } from "Enums/Copy";
export class ExecutionPlan {
public readonly isReplan: boolean;
public readonly executionSteps: ExecutionStep[] = [];
public constructor(plan: SubmitPlanArgs) {
public constructor(plan: SubmitPlanArgs, isReplan: boolean) {
for (const [index, step] of plan.steps.entries()) {
this.executionSteps.push(new ExecutionStep(
index + 1,
@ -19,6 +20,7 @@ export class ExecutionPlan {
if (this.executionSteps[0]) { // Mark first step as active
this.executionSteps[0].status = ExecutionStatus.Active;
}
this.isReplan = isReplan;
}
public completed(): boolean {

View file

@ -88,7 +88,7 @@ describe('AIControllerService - Integration Tests', () => {
onThoughtUpdate: vi.fn(),
onPlanningStarted: vi.fn(),
onPlanningFinished: vi.fn(),
onPlanningQuestion: vi.fn(),
onUserQuestion: vi.fn(),
onPlanUpdate: vi.fn(),
onPlanStepUpdate: vi.fn(),
onPlanComplete: vi.fn(),
@ -119,7 +119,7 @@ describe('AIControllerService - Integration Tests', () => {
onThoughtUpdate: vi.fn(),
onPlanningStarted: vi.fn(),
onPlanningFinished: vi.fn(),
onPlanningQuestion: vi.fn(),
onUserQuestion: vi.fn(),
onPlanUpdate: vi.fn(),
onPlanStepUpdate: vi.fn(),
onPlanComplete: vi.fn(),
@ -150,7 +150,7 @@ describe('AIControllerService - Integration Tests', () => {
onThoughtUpdate: vi.fn(),
onPlanningStarted: vi.fn(),
onPlanningFinished: vi.fn(),
onPlanningQuestion: vi.fn(),
onUserQuestion: vi.fn(),
onPlanUpdate: vi.fn(),
onPlanStepUpdate: vi.fn(),
onPlanComplete: vi.fn(),
@ -176,7 +176,7 @@ describe('AIControllerService - Integration Tests', () => {
onThoughtUpdate: vi.fn(),
onPlanningStarted: vi.fn(),
onPlanningFinished: vi.fn(),
onPlanningQuestion: vi.fn(),
onUserQuestion: vi.fn(),
onPlanUpdate: vi.fn(),
onPlanStepUpdate: vi.fn(),
onPlanComplete: vi.fn(),