@firstpick/pi-extension-todo-progress 0.1.5 → 0.1.6
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/README.md +7 -5
- package/index.ts +69 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ Auto todo/progress tracking for multi-goal prompts.
|
|
|
6
6
|
|
|
7
7
|
- Instructs the agent to create concise, agent-authored todos for multi-step work.
|
|
8
8
|
- Tracks checklist markers from assistant messages instead of copying raw user prompt lines.
|
|
9
|
-
-
|
|
9
|
+
- Instructs the agent to emit markdown checklist lines exactly like `- [ ] item`, `- [-] item`, or `- [x] item`.
|
|
10
|
+
- Also accepts bare markers like `[ ] item` as a fallback for robustness.
|
|
11
|
+
- Strips matched checklist lines from assistant messages after mirroring them into the widget, keeping the widget as the canonical todo view.
|
|
10
12
|
- Clears the widget automatically when all items are complete.
|
|
11
13
|
- Shows up to 5 rows.
|
|
12
14
|
- Supports hiding the current list manually.
|
|
@@ -37,10 +39,10 @@ None.
|
|
|
37
39
|
## Example view
|
|
38
40
|
|
|
39
41
|
```text
|
|
40
|
-
Todo
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
Todo 1/3 done, 1 partial
|
|
43
|
+
[x] Inspect package structure
|
|
44
|
+
[-] Update README examples
|
|
45
|
+
[ ] Run readiness checks
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
For multi-step requests, Pi keeps a compact progress widget visible and updates it as work moves from planned to in-progress to done.
|
package/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ type TodoState = { visible: boolean; items: TodoItem[]; offset: number };
|
|
|
6
6
|
|
|
7
7
|
const KEY = "todo-progress";
|
|
8
8
|
const MAX_ROWS = 5;
|
|
9
|
+
const MAX_ITEMS = 12;
|
|
10
|
+
const TODO_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.+)$/;
|
|
9
11
|
|
|
10
12
|
function statusLabel(status: TodoStatus): string {
|
|
11
13
|
if (status === "done") return "[x]";
|
|
@@ -41,6 +43,56 @@ function render(ctx: ExtensionContext, s: TodoState) {
|
|
|
41
43
|
ctx.ui.setWidget(KEY, lines);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
function parseTodoLine(line: string): TodoItem | undefined {
|
|
47
|
+
const match = TODO_LINE_REGEX.exec(line);
|
|
48
|
+
if (!match) return undefined;
|
|
49
|
+
|
|
50
|
+
const mark = (match[1] || " ").toLowerCase();
|
|
51
|
+
const label = (match[2] || "").trim().replace(/\s+/g, " ");
|
|
52
|
+
if (!label) return undefined;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
status: mark === "x" ? "done" : mark === "-" ? "partial" : "todo",
|
|
56
|
+
text: label,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractChecklist(text: string): TodoItem[] {
|
|
61
|
+
const checklist: TodoItem[] = [];
|
|
62
|
+
let inFence = false;
|
|
63
|
+
|
|
64
|
+
for (const line of text.split(/\r?\n/)) {
|
|
65
|
+
if (/^\s*```/.test(line)) {
|
|
66
|
+
inFence = !inFence;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (inFence) continue;
|
|
70
|
+
|
|
71
|
+
const item = parseTodoLine(line);
|
|
72
|
+
if (item) checklist.push(item);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return checklist;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stripChecklistLines(text: string): string {
|
|
79
|
+
let inFence = false;
|
|
80
|
+
const kept: string[] = [];
|
|
81
|
+
|
|
82
|
+
for (const line of text.split(/\r?\n/)) {
|
|
83
|
+
if (/^\s*```/.test(line)) {
|
|
84
|
+
inFence = !inFence;
|
|
85
|
+
kept.push(line);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!inFence && parseTodoLine(line)) continue;
|
|
90
|
+
kept.push(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
44
96
|
export default function todoProgress(pi: ExtensionAPI) {
|
|
45
97
|
const state: TodoState = { visible: false, items: [], offset: 0 };
|
|
46
98
|
|
|
@@ -48,7 +100,7 @@ export default function todoProgress(pi: ExtensionAPI) {
|
|
|
48
100
|
return {
|
|
49
101
|
systemPrompt:
|
|
50
102
|
event.systemPrompt +
|
|
51
|
-
"\n\n[TODO PROGRESS POLICY] For multi-step work, create a concise agent-authored checklist with 2-6 short items. Do not copy raw user-prompt lines as todos; rewrite them into clear action items.
|
|
103
|
+
"\n\n[TODO PROGRESS POLICY] For multi-step work, create a concise agent-authored checklist with 2-6 short items. Do not copy raw user-prompt lines as todos; rewrite them into clear action items. Emit todo updates as markdown checklist lines exactly like `- [ ] item`, `- [-] item`, or `- [x] item`; do not use raw user prompt lines as todos. Update checklist markers as work changes. Todo checklists are live-turn progress only: still emit `[x]` updates when possible, but the extension will close the widget deterministically when the agent turn ends.",
|
|
52
104
|
};
|
|
53
105
|
});
|
|
54
106
|
|
|
@@ -60,23 +112,28 @@ export default function todoProgress(pi: ExtensionAPI) {
|
|
|
60
112
|
pi.on("message_end", async (event, ctx) => {
|
|
61
113
|
if (event.message.role !== "assistant") return;
|
|
62
114
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
const checklistRegex = /^\s*(?:[-*]|\d+[.)])\s*\[( |x|X|-)\]\s+(.+)$/gm;
|
|
66
|
-
const checklist: Array<TodoItem> = [];
|
|
67
|
-
for (const match of text.matchAll(checklistRegex)) {
|
|
68
|
-
const mark = (match[1] || " ").toLowerCase();
|
|
69
|
-
const label = (match[2] || "").trim().replace(/\s+/g, " ");
|
|
70
|
-
const status: TodoStatus = mark === "x" ? "done" : mark === "-" ? "partial" : "todo";
|
|
71
|
-
if (label) checklist.push({ status, text: label });
|
|
72
|
-
}
|
|
115
|
+
const textParts = event.message.content.filter((c: any) => c.type === "text");
|
|
116
|
+
const checklist = textParts.flatMap((c: any) => extractChecklist(c.text));
|
|
73
117
|
|
|
74
118
|
if (checklist.length === 0) return;
|
|
75
119
|
|
|
76
|
-
state.items = checklist.slice(0,
|
|
120
|
+
state.items = checklist.slice(0, MAX_ITEMS);
|
|
77
121
|
state.offset = Math.min(state.offset, Math.max(0, state.items.length - MAX_ROWS));
|
|
78
122
|
state.visible = true;
|
|
79
123
|
render(ctx, state);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
message: {
|
|
127
|
+
...event.message,
|
|
128
|
+
content: event.message.content.map((c: any) => (c.type === "text" ? { ...c, text: stripChecklistLines(c.text) } : c)),
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
134
|
+
// The widget represents live progress for the active turn, not persistent task state.
|
|
135
|
+
// Do not leave stale partial/todo items visible if the model forgets a final update.
|
|
136
|
+
clear(ctx, state);
|
|
80
137
|
});
|
|
81
138
|
|
|
82
139
|
pi.registerShortcut("ctrl+alt+x", {
|