import type {
PilotDeckPlanTodoStateHandle,
PilotDeckPlanTodoStateSnapshot,
PilotDeckTodoItem,
} from "../../tool/protocol/types.js";
type SessionPlanTodoState = {
approvedPlan?: string;
requiresInitialization: boolean;
requiresRefresh: boolean;
lastMarkdown?: string;
todos: PilotDeckTodoItem[];
};
export type PlanTodoStateManager = {
forSession(sessionId: string): PilotDeckPlanTodoStateHandle;
};
const TODO_WRITE_TOOL_NAME = "todo_write";
export function createPlanTodoStateManager(): PlanTodoStateManager {
const states = new Map<string, SessionPlanTodoState>();
function ensureState(sessionId: string): SessionPlanTodoState {
let state = states.get(sessionId);
if (!state) {
state = {
requiresInitialization: false,
requiresRefresh: false,
todos: [],
};
states.set(sessionId, state);
}
return state;
}
function snapshot(state: SessionPlanTodoState): PilotDeckPlanTodoStateSnapshot {
return {
approvedPlan: state.approvedPlan,
requiresInitialization: state.requiresInitialization,
requiresRefresh: state.requiresRefresh,
lastMarkdown: state.lastMarkdown,
todos: state.todos,
};
}
function buildPromptAddendum(state: SessionPlanTodoState): string | undefined {
if (!state.approvedPlan) return undefined;
if (state.requiresInitialization) {
return [
"You are executing an approved plan.",
`Before using any non-read-only tool, you MUST call \`${TODO_WRITE_TOOL_NAME}\` with a markdown checklist derived from the approved plan.`,
"Represent completed items as `- [x]` and remaining items as `- [ ]`.",
].join("\n");
}
if (state.requiresRefresh) {
return [
"Your todo checklist is stale.",
`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.`,
].join("\n");
}
return undefined;
}
function blockingMessageFor(
state: SessionPlanTodoState,
toolName: string,
isReadOnly: boolean,
): string | undefined {
if (toolName === TODO_WRITE_TOOL_NAME || isReadOnly) {
return undefined;
}
if (state.requiresInitialization) {
return [
"An approved plan is active, but the todo list has not been initialized yet.",
`Call \`${TODO_WRITE_TOOL_NAME}\` first with a markdown checklist based on the approved plan, then retry this tool.`,
].join(" ");
}
if (state.requiresRefresh) {
return [
"The todo list is stale after progress was made on the approved plan.",
`Call \`${TODO_WRITE_TOOL_NAME}\` first to update the markdown checklist and mark completed items, then retry this tool.`,
].join(" ");
}
return undefined;
}
return {
forSession(sessionId: string): PilotDeckPlanTodoStateHandle {
const state = ensureState(sessionId);
return {
getSnapshot: () => snapshot(state),
markPlanApproved(plan: string) {
state.approvedPlan = plan.trim() || undefined;
state.requiresInitialization = Boolean(state.approvedPlan);
state.requiresRefresh = false;
state.lastMarkdown = undefined;
state.todos = [];
},
recordTodoWrite(markdown: string, todos: PilotDeckTodoItem[]) {
state.lastMarkdown = markdown;
state.todos = todos;
state.requiresInitialization = false;
state.requiresRefresh = false;
},
markToolProgressChanged(toolName: string) {
if (!state.approvedPlan || toolName === TODO_WRITE_TOOL_NAME) {
return;
}
if (state.requiresInitialization) {
return;
}
state.requiresRefresh = true;
},
buildPromptAddendum: () => buildPromptAddendum(state),
blockingMessageFor: (toolName, isReadOnly) =>
blockingMessageFor(state, toolName, isReadOnly),
};
},
};
}