Skip to content

Commit 2ccfad4

Browse files
committed
feat: implement todo status tracking and nudge reminders
- Added functionality to track todo statuses and set a nudge reminder when a todo is marked as completed. - Introduced `todoStatusById` and `todoCompletionNudgePending` properties in the session state. - Updated the `insertPruneToolContext` function to conditionally insert nudge messages based on the new todo completion state. - Refactored prompt loading to use the correct directory path.
1 parent 024928f commit 2ccfad4

File tree

5 files changed

+73
-5
lines changed

5 files changed

+73
-5
lines changed

lib/messages/inject.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,18 @@ export const insertPruneToolContext = (
117117
logger.debug("prunable-tools: \n" + prunableToolsList)
118118

119119
let nudgeString = ""
120-
if (
120+
const shouldNudge =
121121
config.tools.settings.nudgeEnabled &&
122-
state.nudgeCounter >= config.tools.settings.nudgeFrequency
123-
) {
122+
(state.nudgeCounter >= config.tools.settings.nudgeFrequency ||
123+
state.todoCompletionNudgePending)
124+
125+
if (shouldNudge) {
124126
logger.info("Inserting prune nudge message")
125127
nudgeString = "\n" + getNudgeString(config)
128+
129+
if (state.todoCompletionNudgePending) {
130+
state.todoCompletionNudgePending = false
131+
}
126132
}
127133

128134
prunableToolsContent = prunableToolsList + nudgeString

lib/prompts/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { readFileSync } from "fs"
2-
import { join } from "path"
2+
import { dirname, join } from "path"
3+
import { fileURLToPath } from "url"
4+
5+
const PROMPTS_DIR = dirname(fileURLToPath(import.meta.url))
36

47
export function loadPrompt(name: string, vars?: Record<string, string>): string {
5-
const filePath = join(__dirname, `${name}.txt`)
8+
const filePath = join(PROMPTS_DIR, `${name}.txt`)
69
let content = readFileSync(filePath, "utf8").trim()
710
if (vars) {
811
for (const [key, value] of Object.entries(vars)) {

lib/state/state.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export const checkSession = async (
3131
state.lastCompaction = lastCompactionTimestamp
3232
state.toolParameters.clear()
3333
state.prune.toolIds = []
34+
state.todoStatusById.clear()
35+
state.todoCompletionNudgePending = false
3436
logger.info("Detected compaction from messages - cleared tool cache", {
3537
timestamp: lastCompactionTimestamp,
3638
})
@@ -56,6 +58,9 @@ export function createSessionState(): SessionState {
5658
lastCompaction: 0,
5759
currentTurn: 0,
5860
variant: undefined,
61+
62+
todoStatusById: new Map<string, string>(),
63+
todoCompletionNudgePending: false,
5964
}
6065
}
6166

@@ -75,6 +80,9 @@ export function resetSessionState(state: SessionState): void {
7580
state.lastCompaction = 0
7681
state.currentTurn = 0
7782
state.variant = undefined
83+
84+
state.todoStatusById.clear()
85+
state.todoCompletionNudgePending = false
7886
}
7987

8088
export async function ensureSessionInitialized(
@@ -101,6 +109,8 @@ export async function ensureSessionInitialized(
101109
state.lastCompaction = findLastCompactionTimestamp(messages)
102110
state.currentTurn = countTurns(state, messages)
103111

112+
seedTodoStatusFromMessages(state, messages)
113+
104114
const persisted = await loadSessionState(sessionId, logger)
105115
if (persisted === null) {
106116
return
@@ -115,6 +125,28 @@ export async function ensureSessionInitialized(
115125
}
116126
}
117127

128+
function seedTodoStatusFromMessages(state: SessionState, messages: WithParts[]): void {
129+
for (const msg of messages) {
130+
if (isMessageCompacted(state, msg)) {
131+
continue
132+
}
133+
for (const part of msg.parts) {
134+
if (part.type !== "tool" || part.tool !== "todowrite") {
135+
continue
136+
}
137+
const todos = (part.state?.input as any)?.todos
138+
if (!Array.isArray(todos)) {
139+
continue
140+
}
141+
for (const todo of todos) {
142+
if (todo && typeof todo.id === "string" && typeof todo.status === "string") {
143+
state.todoStatusById.set(todo.id, todo.status)
144+
}
145+
}
146+
}
147+
}
148+
}
149+
118150
function findLastCompactionTimestamp(messages: WithParts[]): number {
119151
for (let i = messages.length - 1; i >= 0; i--) {
120152
const msg = messages[i]

lib/state/tool-cache.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,30 @@ export async function syncToolCache(
6060
continue
6161
}
6262

63+
if (part.tool === "todowrite") {
64+
const todos = (part.state?.input as any)?.todos
65+
if (Array.isArray(todos)) {
66+
for (const todo of todos) {
67+
if (
68+
!todo ||
69+
typeof todo.id !== "string" ||
70+
typeof todo.status !== "string"
71+
) {
72+
continue
73+
}
74+
const previousStatus = state.todoStatusById.get(todo.id)
75+
if (
76+
previousStatus !== undefined &&
77+
previousStatus !== "completed" &&
78+
todo.status === "completed"
79+
) {
80+
state.todoCompletionNudgePending = true
81+
}
82+
state.todoStatusById.set(todo.id, todo.status)
83+
}
84+
}
85+
}
86+
6387
state.toolParameters.set(part.callID, {
6488
tool: part.tool,
6589
parameters: part.state?.input ?? {},

lib/state/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,7 @@ export interface SessionState {
3535
lastCompaction: number
3636
currentTurn: number
3737
variant: string | undefined
38+
39+
todoStatusById: Map<string, string>
40+
todoCompletionNudgePending: boolean
3841
}

0 commit comments

Comments
 (0)