Skip to content

Commit bc60e81

Browse files
Gucc111cursoragent
andcommitted
fix(plan-todo): replace hard gate with soft reminder after 10 tool calls
The stale-todo mechanism previously blocked every non-read-only tool call after the first successful one until todo_write was called, causing repeated failures in batched tool execution. Replace the boolean requiresRefresh flag with a counter (toolCallsSinceLastTodoWrite) and emit a non-blocking prompt addendum after 10+ calls without an update. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 13d4dcc commit bc60e81

2 files changed

Lines changed: 12 additions & 17 deletions

File tree

src/agent/runtime/PlanTodoState.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
type SessionPlanTodoState = {
88
approvedPlan?: string;
99
requiresInitialization: boolean;
10-
requiresRefresh: boolean;
10+
toolCallsSinceLastTodoWrite: number;
1111
lastMarkdown?: string;
1212
todos: PilotDeckTodoItem[];
1313
};
@@ -26,7 +26,7 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
2626
if (!state) {
2727
state = {
2828
requiresInitialization: false,
29-
requiresRefresh: false,
29+
toolCallsSinceLastTodoWrite: 0,
3030
todos: [],
3131
};
3232
states.set(sessionId, state);
@@ -38,7 +38,7 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
3838
return {
3939
approvedPlan: state.approvedPlan,
4040
requiresInitialization: state.requiresInitialization,
41-
requiresRefresh: state.requiresRefresh,
41+
toolCallsSinceLastTodoWrite: state.toolCallsSinceLastTodoWrite,
4242
lastMarkdown: state.lastMarkdown,
4343
todos: state.todos,
4444
};
@@ -53,11 +53,12 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
5353
"Represent completed items as `- [x]` and remaining items as `- [ ]`.",
5454
].join("\n");
5555
}
56-
if (state.requiresRefresh) {
56+
if (state.toolCallsSinceLastTodoWrite >= 10) {
5757
return [
58-
"Your todo checklist is stale.",
59-
`Before the next non-read-only tool call, you MUST call \`${TODO_WRITE_TOOL_NAME}\` again and update the markdown checklist to reflect the latest completed steps.`,
60-
].join("\n");
58+
`You haven't updated the todo list in a while (${state.toolCallsSinceLastTodoWrite} tool calls since last update).`,
59+
`Consider calling \`${TODO_WRITE_TOOL_NAME}\` to reflect your current progress.`,
60+
"This is a gentle reminder — ignore if not applicable.",
61+
].join(" ");
6162
}
6263
return undefined;
6364
}
@@ -76,12 +77,6 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
7677
`Call \`${TODO_WRITE_TOOL_NAME}\` first with a markdown checklist based on the approved plan, then retry this tool.`,
7778
].join(" ");
7879
}
79-
if (state.requiresRefresh) {
80-
return [
81-
"The todo list is stale after progress was made on the approved plan.",
82-
`Call \`${TODO_WRITE_TOOL_NAME}\` first to update the markdown checklist and mark completed items, then retry this tool.`,
83-
].join(" ");
84-
}
8580
return undefined;
8681
}
8782

@@ -93,15 +88,15 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
9388
markPlanApproved(plan: string) {
9489
state.approvedPlan = plan.trim() || undefined;
9590
state.requiresInitialization = Boolean(state.approvedPlan);
96-
state.requiresRefresh = false;
91+
state.toolCallsSinceLastTodoWrite = 0;
9792
state.lastMarkdown = undefined;
9893
state.todos = [];
9994
},
10095
recordTodoWrite(markdown: string, todos: PilotDeckTodoItem[]) {
10196
state.lastMarkdown = markdown;
10297
state.todos = todos;
10398
state.requiresInitialization = false;
104-
state.requiresRefresh = false;
99+
state.toolCallsSinceLastTodoWrite = 0;
105100
},
106101
markToolProgressChanged(toolName: string) {
107102
if (!state.approvedPlan || toolName === TODO_WRITE_TOOL_NAME) {
@@ -110,7 +105,7 @@ export function createPlanTodoStateManager(): PlanTodoStateManager {
110105
if (state.requiresInitialization) {
111106
return;
112107
}
113-
state.requiresRefresh = true;
108+
state.toolCallsSinceLastTodoWrite += 1;
114109
},
115110
buildPromptAddendum: () => buildPromptAddendum(state),
116111
blockingMessageFor: (toolName, isReadOnly) =>

src/tool/protocol/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export type PilotDeckTodoItem = {
161161
export type PilotDeckPlanTodoStateSnapshot = {
162162
approvedPlan?: string;
163163
requiresInitialization: boolean;
164-
requiresRefresh: boolean;
164+
toolCallsSinceLastTodoWrite: number;
165165
lastMarkdown?: string;
166166
todos: PilotDeckTodoItem[];
167167
};

0 commit comments

Comments
 (0)