added missed out files

This commit is contained in:
Arjun 2025-11-18 16:05:34 +05:30
parent fda8c4c024
commit baf962352c
2 changed files with 180 additions and 0 deletions

View file

@ -0,0 +1,104 @@
import * as fs from "fs/promises";
import * as path from "path";
import { CopilotDataDir } from "../../config/config.js";
import { buildTodoReminder, readTodoState } from "../../lib/todo-store.js";
const REMINDERS_FILE = path.join(CopilotDataDir, "reminders.json");
type ReminderState = {
lastSent: Record<string, string>;
lastTodoToolUse?: string;
};
const defaultState: ReminderState = {
lastSent: {},
};
const TODO_TOOL_NAMES = new Set(["todoList", "todoWrite", "todoUpdate"]);
const ReminderCooldownMs: Record<string, number> = {
"todo-empty": 15 * 60 * 1000,
"todo-review": 10 * 60 * 1000,
};
const TodoEngagementThresholdMs = 10 * 60 * 1000; // remind if todo tools unused for 10+ minutes while todos exist
async function loadState(): Promise<ReminderState> {
try {
const raw = await fs.readFile(REMINDERS_FILE, "utf-8");
const parsed = JSON.parse(raw);
return {
lastSent: typeof parsed.lastSent === "object" && parsed.lastSent !== null ? parsed.lastSent : {},
lastTodoToolUse: typeof parsed.lastTodoToolUse === "string" ? parsed.lastTodoToolUse : undefined,
};
} catch {
return { ...defaultState };
}
}
async function saveState(state: ReminderState) {
await fs.mkdir(path.dirname(REMINDERS_FILE), { recursive: true });
await fs.writeFile(REMINDERS_FILE, JSON.stringify(state, null, 2), "utf-8");
}
function shouldSend(state: ReminderState, key: string): boolean {
const cooldown = ReminderCooldownMs[key] ?? 5 * 60 * 1000;
const lastSent = state.lastSent[key];
if (!lastSent) return true;
const elapsed = Date.now() - new Date(lastSent).getTime();
return elapsed >= cooldown;
}
export type ReminderContext = {
source: "tool-result" | "message";
toolName?: string;
};
export async function collectSystemReminders(context: ReminderContext): Promise<string[]> {
const state = await loadState();
let hasStateChanges = false;
const reminders: string[] = [];
const now = new Date().toISOString();
if (context.toolName && TODO_TOOL_NAMES.has(context.toolName)) {
if (state.lastTodoToolUse !== now) {
state.lastTodoToolUse = now;
hasStateChanges = true;
}
} else {
reminders.push(...await maybeAddTodoReminders(state, context));
if (reminders.length > 0) {
// maybeAddTodoReminders updates state timestamps internally
hasStateChanges = true;
}
}
if (hasStateChanges) {
await saveState(state);
}
return reminders;
}
async function maybeAddTodoReminders(state: ReminderState, context: ReminderContext): Promise<string[]> {
const reminders: string[] = [];
const todoState = await readTodoState();
const nowIso = new Date().toISOString();
if (todoState.todos.length === 0) {
if (shouldSend(state, "todo-empty")) {
reminders.push(buildTodoReminder(todoState.todos, "This is a private reminder: your todo list is empty. If the current work benefits from tracking progress, use the TodoWrite tool to outline the plan."));
state.lastSent["todo-empty"] = nowIso;
}
return reminders;
}
const lastUseTs = state.lastTodoToolUse ? new Date(state.lastTodoToolUse).getTime() : 0;
const todoStale = Date.now() - lastUseTs >= TodoEngagementThresholdMs;
if (todoStale && shouldSend(state, "todo-review")) {
reminders.push(buildTodoReminder(todoState.todos, "Private reminder: review and update your todo list as you make progress. Keep this reminder internal and reflect updates via the todo tools."));
state.lastSent["todo-review"] = nowIso;
}
return reminders;
}

View file

@ -0,0 +1,76 @@
import { z } from "zod";
export const TodoStatusSchema = z.enum(["pending", "in_progress", "done", "blocked"]);
export type TodoStatus = z.infer<typeof TodoStatusSchema>;
export type TodoItem = {
id: string;
content: string;
status: TodoStatus;
};
export type TodoState = {
todos: TodoItem[];
updatedAt: string;
};
const defaultTodoState: TodoState = {
todos: [],
updatedAt: new Date(0).toISOString(),
};
const todoStateStack: TodoState[] = [{ ...defaultTodoState }];
const currentState = (): TodoState => todoStateStack[todoStateStack.length - 1];
export async function readTodoState(): Promise<TodoState> {
return currentState();
}
export async function writeTodoState(todos: TodoItem[]): Promise<TodoState> {
const updated = {
todos: sanitiseTodos(todos),
updatedAt: new Date().toISOString(),
};
todoStateStack[todoStateStack.length - 1] = updated;
return updated;
}
export function resetTodoState(): void {
todoStateStack[todoStateStack.length - 1] = { ...defaultTodoState };
}
export function pushTodoState(initialState?: TodoState): void {
todoStateStack.push(initialState ? { ...initialState } : { ...defaultTodoState });
}
export function popTodoState(): void {
if (todoStateStack.length > 1) {
todoStateStack.pop();
} else {
todoStateStack[0] = { ...defaultTodoState };
}
}
export function sanitiseTodos(todos: TodoItem[]): TodoItem[] {
const seen = new Set<string>();
const sanitized: TodoItem[] = [];
for (const todo of todos) {
if (!todo) continue;
const id = typeof todo.id === "string" ? todo.id.trim() : "";
const content = typeof todo.content === "string" ? todo.content : "";
const statusResult = TodoStatusSchema.safeParse(todo.status);
const status = statusResult.success ? statusResult.data : "pending";
if (!id || !content || seen.has(id)) {
continue;
}
seen.add(id);
sanitized.push({ id, content, status });
}
return sanitized;
}
export function buildTodoReminder(todos: TodoItem[], preface: string) {
return `<system-reminder>\n${preface}\n\n${JSON.stringify(todos)}\n</system-reminder>`;
}