@nguyenquangthai/pi-todo 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/media/screenshot.png +0 -0
- package/package.json +62 -60
- package/src/index.ts +185 -185
- package/src/prompt-intent.ts +147 -147
- package/src/prompt.ts +77 -77
- package/src/reminder-cadence.ts +116 -116
- package/src/replay.ts +69 -69
- package/src/store.ts +36 -36
- package/src/tools/todoread.ts +8 -3
- package/src/tools/todowrite.ts +6 -1
- package/src/types.ts +29 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 (2026-07-15)
|
|
4
|
+
|
|
5
|
+
- Rename tools to snake_case: `todowrite` → `todo_write`, `todoread` → `todo_read` (pi convention).
|
|
6
|
+
- Improve renderCall/renderResult: accent color for item count, "X open / Y total" instead of "N todo(s)".
|
|
7
|
+
- Update all prompt references to match new tool names.
|
|
8
|
+
|
|
9
|
+
## 0.2.2 (2026-07-15)
|
|
10
|
+
|
|
11
|
+
- Add gallery screenshot to pi manifest for pi.dev/packages listing.
|
|
12
|
+
|
|
3
13
|
## 0.2.1 (2026-07-15)
|
|
4
14
|
|
|
5
15
|
- Overlay heading spacing: add blank line + `maxLines-1` budget for clean visual separation.
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nguyenquangthai/pi-todo",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "OpenCode-like session todo checklist for the pi coding agent — todowrite/todoread with a live TUI overlay",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"author": "QuangThai",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"homepage": "https://github.com/QuangThai/pi-todo#readme",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/QuangThai/pi-todo.git"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/QuangThai/pi-todo/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"pi-package",
|
|
18
|
-
"pi",
|
|
19
|
-
"pi-extension",
|
|
20
|
-
"pi-coding-agent",
|
|
21
|
-
"todo",
|
|
22
|
-
"todowrite",
|
|
23
|
-
"todoread",
|
|
24
|
-
"opencode",
|
|
25
|
-
"tui"
|
|
26
|
-
],
|
|
27
|
-
"files": [
|
|
28
|
-
"src",
|
|
29
|
-
"LICENSE",
|
|
30
|
-
"README.md",
|
|
31
|
-
"CHANGELOG.md"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"test
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"@earendil-works/pi-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"@earendil-works/pi-
|
|
55
|
-
"@
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@nguyenquangthai/pi-todo",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "OpenCode-like session todo checklist for the pi coding agent — todowrite/todoread with a live TUI overlay",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "QuangThai",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/QuangThai/pi-todo#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/QuangThai/pi-todo.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/QuangThai/pi-todo/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi",
|
|
19
|
+
"pi-extension",
|
|
20
|
+
"pi-coding-agent",
|
|
21
|
+
"todo",
|
|
22
|
+
"todowrite",
|
|
23
|
+
"todoread",
|
|
24
|
+
"opencode",
|
|
25
|
+
"tui"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"media/screenshot.png"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"prepublishOnly": "npm test && npm run typecheck"
|
|
42
|
+
},
|
|
43
|
+
"pi": {
|
|
44
|
+
"extensions": ["./src/index.ts"],
|
|
45
|
+
"image": "https://raw.githubusercontent.com/QuangThai/pi-todo/main/media/screenshot.png"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@earendil-works/pi-ai": "*",
|
|
49
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
50
|
+
"@earendil-works/pi-tui": "*",
|
|
51
|
+
"typebox": "*"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@earendil-works/pi-ai": "^0.80.7",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^0.80.7",
|
|
56
|
+
"@earendil-works/pi-tui": "^0.80.7",
|
|
57
|
+
"@types/node": "^22.0.0",
|
|
58
|
+
"typebox": "1.1.38",
|
|
59
|
+
"typescript": "^5.8.0",
|
|
60
|
+
"vitest": "^3.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pi-todo — OpenCode-like session todo checklist for Pi.
|
|
3
|
-
*
|
|
4
|
-
* Tools:
|
|
5
|
-
* Overlay: # Todos with [ ]/[•]/[✓]/[×] above the editor
|
|
6
|
-
* Persistence: toolResult details + custom entry, replayed from branch
|
|
7
|
-
* Reminder: pi-tasks-style cadence → transient <system-reminder> via context
|
|
8
|
-
* Cold start: pi-todotools section + prompt-aware one-shot context nudge
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { formatPlainTodoLine } from "./format.js";
|
|
13
|
-
import { TodoOverlay } from "./overlay.js";
|
|
14
|
-
import {
|
|
15
|
-
buildColdStartReminder,
|
|
16
|
-
buildCompletionUpdateReminder,
|
|
17
|
-
shouldNudgeColdStart,
|
|
18
|
-
shouldNudgeCompletionUpdate,
|
|
19
|
-
} from "./prompt-intent.js";
|
|
20
|
-
import { COLD_START_BOOST, TASK_MANAGEMENT_SECTION } from "./prompt.js";
|
|
21
|
-
import {
|
|
22
|
-
buildSystemReminder,
|
|
23
|
-
createCadenceState,
|
|
24
|
-
drainReminderForContext,
|
|
25
|
-
evaluateToolResult,
|
|
26
|
-
onTurnStart,
|
|
27
|
-
REMINDER_INTERVAL,
|
|
28
|
-
resetCadenceState,
|
|
29
|
-
type CadenceConfig,
|
|
30
|
-
} from "./reminder-cadence.js";
|
|
31
|
-
import { replayFromBranch } from "./replay.js";
|
|
32
|
-
import { clearTodos, getTodos, setTodos } from "./store.js";
|
|
33
|
-
import { registerTodoReadTool } from "./tools/todoread.js";
|
|
34
|
-
import { registerTodoWriteTool } from "./tools/todowrite.js";
|
|
35
|
-
import { TOOL_READ, TOOL_WRITE } from "./types.js";
|
|
36
|
-
import { hasOpenTodos, isOpenTodo } from "./validate.js";
|
|
37
|
-
|
|
38
|
-
const TODO_TOOL_NAMES = new Set([TOOL_WRITE, TOOL_READ]);
|
|
39
|
-
|
|
40
|
-
const cadenceConfig: CadenceConfig = {
|
|
41
|
-
reminderInterval: REMINDER_INTERVAL,
|
|
42
|
-
todoToolNames: TODO_TOOL_NAMES,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function isStaleCtxError(e: unknown): boolean {
|
|
46
|
-
return /stale after session replacement/i.test(String(e));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function syncFromSession(ctx: ExtensionContext, overlay: TodoOverlay | undefined): void {
|
|
50
|
-
try {
|
|
51
|
-
setTodos(replayFromBranch(ctx));
|
|
52
|
-
} catch (e) {
|
|
53
|
-
if (!isStaleCtxError(e)) throw e;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (ctx.hasUI && overlay) {
|
|
57
|
-
overlay.setUICtx(ctx.ui);
|
|
58
|
-
overlay.update();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function injectReminderMessage(
|
|
63
|
-
messages: unknown[],
|
|
64
|
-
text: string,
|
|
65
|
-
): { messages: unknown[] } {
|
|
66
|
-
return {
|
|
67
|
-
messages: [
|
|
68
|
-
...messages,
|
|
69
|
-
{
|
|
70
|
-
role: "user" as const,
|
|
71
|
-
content: [{ type: "text" as const, text }],
|
|
72
|
-
timestamp: Date.now(),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default function (pi: ExtensionAPI): void {
|
|
79
|
-
let overlay: TodoOverlay | undefined;
|
|
80
|
-
const cadence = createCadenceState();
|
|
81
|
-
/** One-shot transient nudge for the next LLM context (cold start or completion). */
|
|
82
|
-
let pendingIntentNudge: string | null = null;
|
|
83
|
-
|
|
84
|
-
const refreshOverlay = (): void => {
|
|
85
|
-
overlay?.update();
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
registerTodoWriteTool(pi, { onCommit: refreshOverlay });
|
|
89
|
-
registerTodoReadTool(pi);
|
|
90
|
-
|
|
91
|
-
// Cold-start + completion nudges. Tool description alone is often ignored;
|
|
92
|
-
// prompt-aware section + one-shot context reminder (pi-tasks style) is stronger.
|
|
93
|
-
pi.on("before_agent_start", async (event) => {
|
|
94
|
-
const prompt = typeof event.prompt === "string" ? event.prompt : "";
|
|
95
|
-
const open = hasOpenTodos(getTodos());
|
|
96
|
-
let systemPrompt = `${event.systemPrompt}\n${TASK_MANAGEMENT_SECTION}`;
|
|
97
|
-
|
|
98
|
-
if (shouldNudgeColdStart(prompt, open)) {
|
|
99
|
-
systemPrompt += `\n${COLD_START_BOOST}`;
|
|
100
|
-
pendingIntentNudge = buildColdStartReminder(prompt);
|
|
101
|
-
} else if (shouldNudgeCompletionUpdate(prompt, open)) {
|
|
102
|
-
const openLines = getTodos().filter(isOpenTodo).map(formatPlainTodoLine);
|
|
103
|
-
pendingIntentNudge = buildCompletionUpdateReminder(openLines);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { systemPrompt };
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
110
|
-
resetCadenceState(cadence);
|
|
111
|
-
pendingIntentNudge = null;
|
|
112
|
-
if (ctx.hasUI && !overlay) {
|
|
113
|
-
overlay = new TodoOverlay();
|
|
114
|
-
}
|
|
115
|
-
syncFromSession(ctx, overlay);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
pi.on("session_tree", async (_event, ctx) => {
|
|
119
|
-
syncFromSession(ctx, overlay);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
pi.on("session_compact", async (_event, ctx) => {
|
|
123
|
-
syncFromSession(ctx, overlay);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
pi.on("session_shutdown", async () => {
|
|
127
|
-
try {
|
|
128
|
-
overlay?.dispose();
|
|
129
|
-
} finally {
|
|
130
|
-
// Clear in-memory store so a stale session_start (early return) cannot
|
|
131
|
-
// briefly expose the previous session's todos to reminders/tools.
|
|
132
|
-
clearTodos();
|
|
133
|
-
overlay = undefined;
|
|
134
|
-
pendingIntentNudge = null;
|
|
135
|
-
resetCadenceState(cadence);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
pi.on("turn_start", async () => {
|
|
140
|
-
onTurnStart(cadence);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Cadence tracking only — never mutate tool result content.
|
|
144
|
-
// Gate on open work (pending/in_progress), not all-terminal lists.
|
|
145
|
-
pi.on("tool_result", async (event) => {
|
|
146
|
-
const isTodoTool = TODO_TOOL_NAMES.has(event.toolName);
|
|
147
|
-
if (
|
|
148
|
-
!isTodoTool &&
|
|
149
|
-
cadence.currentTurn - cadence.lastTodoToolUseTurn < REMINDER_INTERVAL
|
|
150
|
-
) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (!isTodoTool && cadence.reminderInjectedThisCycle) return;
|
|
154
|
-
|
|
155
|
-
const hasOpenWork = isTodoTool ? false : hasOpenTodos(getTodos());
|
|
156
|
-
evaluateToolResult(cadence, event.toolName, hasOpenWork, cadenceConfig);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Transient injection for one LLM call — not persisted in the session store.
|
|
160
|
-
// Intent nudge (cold start / user-said-done) takes priority over idle cadence.
|
|
161
|
-
// Peer typings sometimes omit "context" from the overloaded `on` signature.
|
|
162
|
-
const onEvent = pi.on.bind(pi) as (event: string, handler: (...args: never[]) => unknown) => void;
|
|
163
|
-
onEvent("context", async (event: { messages: unknown[] }) => {
|
|
164
|
-
if (pendingIntentNudge) {
|
|
165
|
-
const text = pendingIntentNudge;
|
|
166
|
-
pendingIntentNudge = null;
|
|
167
|
-
return injectReminderMessage(event.messages, text);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (!drainReminderForContext(cadence)) return;
|
|
171
|
-
|
|
172
|
-
const reminder = buildSystemReminder(getTodos());
|
|
173
|
-
if (!reminder) return;
|
|
174
|
-
|
|
175
|
-
return injectReminderMessage(event.messages, reminder);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Refresh from in-memory store — do NOT replayFromBranch here (branch is stale).
|
|
179
|
-
// Clear intent nudge once
|
|
180
|
-
pi.on("tool_execution_end", async (event) => {
|
|
181
|
-
if (event.toolName !== TOOL_WRITE || event.isError) return;
|
|
182
|
-
pendingIntentNudge = null;
|
|
183
|
-
overlay?.update();
|
|
184
|
-
});
|
|
185
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* pi-todo — OpenCode-like session todo checklist for Pi.
|
|
3
|
+
*
|
|
4
|
+
* Tools: todo_write (full replace), todo_read
|
|
5
|
+
* Overlay: # Todos with [ ]/[•]/[✓]/[×] above the editor
|
|
6
|
+
* Persistence: toolResult details + custom entry, replayed from branch
|
|
7
|
+
* Reminder: pi-tasks-style cadence → transient <system-reminder> via context
|
|
8
|
+
* Cold start: pi-todotools section + prompt-aware one-shot context nudge
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { formatPlainTodoLine } from "./format.js";
|
|
13
|
+
import { TodoOverlay } from "./overlay.js";
|
|
14
|
+
import {
|
|
15
|
+
buildColdStartReminder,
|
|
16
|
+
buildCompletionUpdateReminder,
|
|
17
|
+
shouldNudgeColdStart,
|
|
18
|
+
shouldNudgeCompletionUpdate,
|
|
19
|
+
} from "./prompt-intent.js";
|
|
20
|
+
import { COLD_START_BOOST, TASK_MANAGEMENT_SECTION } from "./prompt.js";
|
|
21
|
+
import {
|
|
22
|
+
buildSystemReminder,
|
|
23
|
+
createCadenceState,
|
|
24
|
+
drainReminderForContext,
|
|
25
|
+
evaluateToolResult,
|
|
26
|
+
onTurnStart,
|
|
27
|
+
REMINDER_INTERVAL,
|
|
28
|
+
resetCadenceState,
|
|
29
|
+
type CadenceConfig,
|
|
30
|
+
} from "./reminder-cadence.js";
|
|
31
|
+
import { replayFromBranch } from "./replay.js";
|
|
32
|
+
import { clearTodos, getTodos, setTodos } from "./store.js";
|
|
33
|
+
import { registerTodoReadTool } from "./tools/todoread.js";
|
|
34
|
+
import { registerTodoWriteTool } from "./tools/todowrite.js";
|
|
35
|
+
import { TOOL_READ, TOOL_WRITE } from "./types.js";
|
|
36
|
+
import { hasOpenTodos, isOpenTodo } from "./validate.js";
|
|
37
|
+
|
|
38
|
+
const TODO_TOOL_NAMES = new Set([TOOL_WRITE, TOOL_READ]);
|
|
39
|
+
|
|
40
|
+
const cadenceConfig: CadenceConfig = {
|
|
41
|
+
reminderInterval: REMINDER_INTERVAL,
|
|
42
|
+
todoToolNames: TODO_TOOL_NAMES,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function isStaleCtxError(e: unknown): boolean {
|
|
46
|
+
return /stale after session replacement/i.test(String(e));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function syncFromSession(ctx: ExtensionContext, overlay: TodoOverlay | undefined): void {
|
|
50
|
+
try {
|
|
51
|
+
setTodos(replayFromBranch(ctx));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (!isStaleCtxError(e)) throw e;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (ctx.hasUI && overlay) {
|
|
57
|
+
overlay.setUICtx(ctx.ui);
|
|
58
|
+
overlay.update();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function injectReminderMessage(
|
|
63
|
+
messages: unknown[],
|
|
64
|
+
text: string,
|
|
65
|
+
): { messages: unknown[] } {
|
|
66
|
+
return {
|
|
67
|
+
messages: [
|
|
68
|
+
...messages,
|
|
69
|
+
{
|
|
70
|
+
role: "user" as const,
|
|
71
|
+
content: [{ type: "text" as const, text }],
|
|
72
|
+
timestamp: Date.now(),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default function (pi: ExtensionAPI): void {
|
|
79
|
+
let overlay: TodoOverlay | undefined;
|
|
80
|
+
const cadence = createCadenceState();
|
|
81
|
+
/** One-shot transient nudge for the next LLM context (cold start or completion). */
|
|
82
|
+
let pendingIntentNudge: string | null = null;
|
|
83
|
+
|
|
84
|
+
const refreshOverlay = (): void => {
|
|
85
|
+
overlay?.update();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
registerTodoWriteTool(pi, { onCommit: refreshOverlay });
|
|
89
|
+
registerTodoReadTool(pi);
|
|
90
|
+
|
|
91
|
+
// Cold-start + completion nudges. Tool description alone is often ignored;
|
|
92
|
+
// prompt-aware section + one-shot context reminder (pi-tasks style) is stronger.
|
|
93
|
+
pi.on("before_agent_start", async (event) => {
|
|
94
|
+
const prompt = typeof event.prompt === "string" ? event.prompt : "";
|
|
95
|
+
const open = hasOpenTodos(getTodos());
|
|
96
|
+
let systemPrompt = `${event.systemPrompt}\n${TASK_MANAGEMENT_SECTION}`;
|
|
97
|
+
|
|
98
|
+
if (shouldNudgeColdStart(prompt, open)) {
|
|
99
|
+
systemPrompt += `\n${COLD_START_BOOST}`;
|
|
100
|
+
pendingIntentNudge = buildColdStartReminder(prompt);
|
|
101
|
+
} else if (shouldNudgeCompletionUpdate(prompt, open)) {
|
|
102
|
+
const openLines = getTodos().filter(isOpenTodo).map(formatPlainTodoLine);
|
|
103
|
+
pendingIntentNudge = buildCompletionUpdateReminder(openLines);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { systemPrompt };
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
110
|
+
resetCadenceState(cadence);
|
|
111
|
+
pendingIntentNudge = null;
|
|
112
|
+
if (ctx.hasUI && !overlay) {
|
|
113
|
+
overlay = new TodoOverlay();
|
|
114
|
+
}
|
|
115
|
+
syncFromSession(ctx, overlay);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
119
|
+
syncFromSession(ctx, overlay);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
123
|
+
syncFromSession(ctx, overlay);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
pi.on("session_shutdown", async () => {
|
|
127
|
+
try {
|
|
128
|
+
overlay?.dispose();
|
|
129
|
+
} finally {
|
|
130
|
+
// Clear in-memory store so a stale session_start (early return) cannot
|
|
131
|
+
// briefly expose the previous session's todos to reminders/tools.
|
|
132
|
+
clearTodos();
|
|
133
|
+
overlay = undefined;
|
|
134
|
+
pendingIntentNudge = null;
|
|
135
|
+
resetCadenceState(cadence);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
pi.on("turn_start", async () => {
|
|
140
|
+
onTurnStart(cadence);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Cadence tracking only — never mutate tool result content.
|
|
144
|
+
// Gate on open work (pending/in_progress), not all-terminal lists.
|
|
145
|
+
pi.on("tool_result", async (event) => {
|
|
146
|
+
const isTodoTool = TODO_TOOL_NAMES.has(event.toolName);
|
|
147
|
+
if (
|
|
148
|
+
!isTodoTool &&
|
|
149
|
+
cadence.currentTurn - cadence.lastTodoToolUseTurn < REMINDER_INTERVAL
|
|
150
|
+
) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!isTodoTool && cadence.reminderInjectedThisCycle) return;
|
|
154
|
+
|
|
155
|
+
const hasOpenWork = isTodoTool ? false : hasOpenTodos(getTodos());
|
|
156
|
+
evaluateToolResult(cadence, event.toolName, hasOpenWork, cadenceConfig);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Transient injection for one LLM call — not persisted in the session store.
|
|
160
|
+
// Intent nudge (cold start / user-said-done) takes priority over idle cadence.
|
|
161
|
+
// Peer typings sometimes omit "context" from the overloaded `on` signature.
|
|
162
|
+
const onEvent = pi.on.bind(pi) as (event: string, handler: (...args: never[]) => unknown) => void;
|
|
163
|
+
onEvent("context", async (event: { messages: unknown[] }) => {
|
|
164
|
+
if (pendingIntentNudge) {
|
|
165
|
+
const text = pendingIntentNudge;
|
|
166
|
+
pendingIntentNudge = null;
|
|
167
|
+
return injectReminderMessage(event.messages, text);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!drainReminderForContext(cadence)) return;
|
|
171
|
+
|
|
172
|
+
const reminder = buildSystemReminder(getTodos());
|
|
173
|
+
if (!reminder) return;
|
|
174
|
+
|
|
175
|
+
return injectReminderMessage(event.messages, reminder);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Refresh from in-memory store — do NOT replayFromBranch here (branch is stale).
|
|
179
|
+
// Clear intent nudge once todo_write succeeds (model complied; avoid double-inject).
|
|
180
|
+
pi.on("tool_execution_end", async (event) => {
|
|
181
|
+
if (event.toolName !== TOOL_WRITE || event.isError) return;
|
|
182
|
+
pendingIntentNudge = null;
|
|
183
|
+
overlay?.update();
|
|
184
|
+
});
|
|
185
|
+
}
|