@agnishc/edb-todo 0.8.1 → 0.10.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 +50 -0
- package/README.md +151 -18
- package/package.json +1 -1
- package/src/auto-clear.ts +87 -0
- package/src/component.ts +314 -69
- package/src/config.ts +25 -0
- package/src/file-store.ts +408 -0
- package/src/index.ts +584 -82
- package/src/process-tracker.ts +146 -0
- package/src/prompt.ts +19 -17
- package/src/schemas.ts +55 -24
- package/src/state.ts +251 -72
- package/src/types.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.3] - 2026-05-15
|
|
4
|
+
|
|
5
|
+
## [0.9.0] - 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `todo_create` tool — create individual tasks with `content`, `description`, `priority`, `activeForm`, and `metadata`
|
|
9
|
+
- `todo_get` tool — retrieve full task details: description, dependencies (`blocks`/`blockedBy`), metadata
|
|
10
|
+
- `todo_update` tool — update individual tasks (status, content, description, priority, owner); add dependency edges (`addBlocks`/`addBlockedBy`); `status: "deleted"` permanently removes the task
|
|
11
|
+
- **File-backed storage** — tasks now persist to disk with file locking and atomic writes
|
|
12
|
+
- `memory`: in-memory only (lost on session end)
|
|
13
|
+
- `session` *(default)*: per-session file at `<cwd>/.pi/tasks/tasks-<sessionId>.json`
|
|
14
|
+
- `project`: shared across all sessions at `<cwd>/.pi/tasks/tasks.json`
|
|
15
|
+
- **Dependency management** — bidirectional `blocks`/`blockedBy` edges with cycle detection and warnings
|
|
16
|
+
- **Auto-clear completed tasks** — configurable via settings: `never` / `on_list_complete` *(default)* / `on_task_complete`; turn-based delay so completions linger briefly before disappearing
|
|
17
|
+
- **Settings panel** — `/todos → ⚙ Settings` opens a native TUI settings panel (taskScope + autoClearCompleted); saved to `<cwd>/.pi/tasks-config.json`
|
|
18
|
+
- **System-reminder injection** — periodic `<system-reminder>` nudges appended to non-task tool results after `REMINDER_INTERVAL` turns of inactivity, encouraging the model to keep tasks up to date
|
|
19
|
+
- **Enhanced widget** — animated star spinner (✳✴✵…) for in-progress tasks, elapsed time display (e.g. `42s`, `2m 5s`), blocked-by hints inline
|
|
20
|
+
- `PI_TODO` environment variable override: `off` (memory only), named list (`~/.pi/tasks/<name>.json`), or absolute/relative path
|
|
21
|
+
- `/todos` command now shows a select-based menu with View / Clear completed / Clear all / Settings
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Widget placement changed from status bar to **above editor** (persistent, always visible)
|
|
25
|
+
- Session state no longer reconstructed from tool-result branch entries — file-backed store is the source of truth
|
|
26
|
+
- `todo_write` now merges `blocks`, `blockedBy`, and `metadata` from existing tasks when a task ID is reused (non-destructive for dependency edges)
|
|
27
|
+
- System prompt injection now includes task IDs and blocked-by info
|
|
28
|
+
- `priorityLabel` now correctly outputs `High`/`Medium`/`Low`
|
|
29
|
+
|
|
30
|
+
## [0.8.2] - 2026-05-11
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- `todo_remove` tool — permanently remove tasks by ID
|
|
34
|
+
- Interactive keyboard navigation in `/todos` viewer (↑↓/jk, g/G, Home/End)
|
|
35
|
+
- Toggle completed task visibility with `c` key in `/todos` viewer
|
|
36
|
+
- Timestamps on tasks: `createdAt`, `startedAt`, `completedAt`
|
|
37
|
+
- Status transition tracking — timestamps update automatically when tasks move between states
|
|
38
|
+
- Percentage display in progress bar
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Replaced module-level mutable globals (`tasks`, `idCounter`) with `TodoStore` class
|
|
42
|
+
- Deduplicated rendering logic — shared `priorityColor()`, `priorityLabel()` helpers used everywhere
|
|
43
|
+
- Priority labels now display as `High`/`Medium`/`Low` instead of `HIG`/`MED`/`LOW`
|
|
44
|
+
- In-progress icon changed from `→` to `●` for visual consistency
|
|
45
|
+
- `/todos` viewer now shows cursor indicator (`❯`) on focused task
|
|
46
|
+
- Section headers show task counts
|
|
47
|
+
- Updated widget status bar to use `●` for active count
|
|
48
|
+
- `todo_write` prompt guidelines now explicitly state completed tasks are never auto-deleted
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- Rendering inconsistency between widget, viewer, and tool results — all now use the same styling
|
|
52
|
+
|
|
3
53
|
## [0.8.1] - 2026-05-11
|
|
4
54
|
|
|
5
55
|
## [0.6.0] - 2026-05-11
|
package/README.md
CHANGED
|
@@ -2,38 +2,171 @@
|
|
|
2
2
|
|
|
3
3
|
A Pi CLI extension that gives the agent a structured task list to prevent **goal drift** — the tendency for agents to lose track of the original plan as context grows and tool calls accumulate.
|
|
4
4
|
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **6 LLM tools** — `TaskCreate`, `TaskList`, `TaskGet`, `TaskUpdate`, `TaskOutput`, `TaskStop` — matching pi-tasks behavior
|
|
8
|
+
- **Persistent widget** — live task list above the editor with animated spinner (✳✽), elapsed time, and blocked-by hints
|
|
9
|
+
- **File-backed storage** — memory / session / project scope with file locking and atomic writes
|
|
10
|
+
- **Dependency management** — bidirectional `blocks` / `blockedBy` edges with cycle detection
|
|
11
|
+
- **Auto-clear completed tasks** — configurable: never / on_list_complete / on_task_complete
|
|
12
|
+
- **System-reminder injection** — periodic nudges when task tools haven't been used recently
|
|
13
|
+
- **Settings panel** — `/todos → ⚙ Settings` (task storage + auto-clear, saved to `tasks-config.json`)
|
|
14
|
+
- **Priority system** — high / medium / low with color coding (Red / Yellow / Dim)
|
|
15
|
+
|
|
5
16
|
## How it works
|
|
6
17
|
|
|
7
|
-
1. The agent
|
|
8
|
-
2. Before every agent turn, active tasks are injected into the system prompt
|
|
9
|
-
3. A live widget above the editor shows
|
|
10
|
-
4.
|
|
18
|
+
1. The agent uses `TaskCreate` to plan multi-step work as a structured task list
|
|
19
|
+
2. Before every agent turn, active tasks are injected into the system prompt
|
|
20
|
+
3. A live widget above the editor shows tasks with status icons and elapsed time for active tasks
|
|
21
|
+
4. Tasks persist to disk per-session (or project-wide) and survive session resume
|
|
22
|
+
5. Completed tasks remain visible until auto-cleared or manually removed
|
|
11
23
|
|
|
12
24
|
## Tools
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|------|-------------|
|
|
16
|
-
| `todo_write` | Replace the entire task list (atomic update) — always pass all tasks |
|
|
17
|
-
| `todo_read` | Read the current task list and statuses |
|
|
26
|
+
### `TaskCreate`
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
Create a structured task. Used proactively for complex multi-step work.
|
|
20
29
|
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
30
|
+
| Parameter | Type | Required | Description |
|
|
31
|
+
|-----------|------|----------|-------------|
|
|
32
|
+
| `content` | string | ✓ | Brief actionable title in imperative form |
|
|
33
|
+
| `description` | string | | Detailed context and acceptance criteria |
|
|
34
|
+
| `priority` | `high` \| `medium` \| `low` | | Default: `medium` |
|
|
35
|
+
| `activeForm` | string | | Spinner text when in_progress (e.g., "Running tests") |
|
|
36
|
+
| `metadata` | object | | Arbitrary key-value pairs |
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
### `TaskList`
|
|
39
|
+
|
|
40
|
+
List all tasks sorted by status (pending first, then in_progress, then completed) and ID.
|
|
41
|
+
|
|
42
|
+
Returns each task's id, content, status, priority, and open blockedBy entries.
|
|
43
|
+
|
|
44
|
+
### `TaskGet`
|
|
45
|
+
|
|
46
|
+
Get full details for a specific task by ID — including description, dependencies, and metadata.
|
|
47
|
+
|
|
48
|
+
| Parameter | Type | Description |
|
|
49
|
+
|-----------|------|-------------|
|
|
50
|
+
| `id` | string | The task ID |
|
|
51
|
+
|
|
52
|
+
### `TaskUpdate`
|
|
53
|
+
|
|
54
|
+
Update task fields, status, and dependencies.
|
|
55
|
+
|
|
56
|
+
| Parameter | Type | Description |
|
|
57
|
+
|-----------|------|-------------|
|
|
58
|
+
| `id` | string | Task ID (required) |
|
|
59
|
+
| `status` | `pending` \| `in_progress` \| `completed` \| `deleted` | New status (`deleted` permanently removes) |
|
|
60
|
+
| `content` | string | New title |
|
|
61
|
+
| `description` | string | New description |
|
|
62
|
+
| `priority` | `high` \| `medium` \| `low` | New priority |
|
|
63
|
+
| `activeForm` | string | Spinner text |
|
|
64
|
+
| `owner` | string | Agent/owner name |
|
|
65
|
+
| `metadata` | object | Shallow merge (set key to `null` to delete) |
|
|
66
|
+
| `addBlocks` | string[] | Task IDs this task blocks |
|
|
67
|
+
| `addBlockedBy` | string[] | Task IDs that block this task |
|
|
68
|
+
|
|
69
|
+
Setting `status: "deleted"` permanently removes the task and cleans up all dependency edges.
|
|
70
|
+
|
|
71
|
+
Dependencies are bidirectional — `addBlocks: ["t2"]` on task `t1` also adds `blockedBy: ["t1"]` to task `t2`.
|
|
72
|
+
|
|
73
|
+
### `TaskOutput`
|
|
74
|
+
|
|
75
|
+
Retrieve output from a running or completed background task process.
|
|
76
|
+
|
|
77
|
+
| Parameter | Type | Default | Description |
|
|
78
|
+
|-----------|------|---------|-------------|
|
|
79
|
+
| `task_id` | string | — | Task ID (required) |
|
|
80
|
+
| `block` | boolean | `true` | Wait for completion |
|
|
81
|
+
| `timeout` | number | `30000` | Max wait time in ms (max 600000) |
|
|
82
|
+
|
|
83
|
+
### `TaskStop`
|
|
84
|
+
|
|
85
|
+
Stop a running background task process. Sends SIGTERM, waits 5 seconds, then SIGKILL. Marks the task as completed.
|
|
86
|
+
|
|
87
|
+
| Parameter | Type | Description |
|
|
88
|
+
|-----------|------|-------------|
|
|
89
|
+
| `task_id` | string | Task ID to stop |
|
|
90
|
+
|
|
91
|
+
## Task lifecycle
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
pending → in_progress → completed
|
|
95
|
+
→ deleted (permanently removed)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Dependency management
|
|
28
99
|
|
|
29
100
|
```bash
|
|
30
|
-
|
|
101
|
+
# Task t2 cannot start until t1 is completed
|
|
102
|
+
TaskUpdate { id: "t2", addBlockedBy: ["t1"] }
|
|
31
103
|
```
|
|
32
104
|
|
|
33
|
-
|
|
105
|
+
Edges are bidirectional. The widget and `TaskList` show open blockers inline (`› blocked by #t1`). Cycles and self-dependencies produce warnings but are stored.
|
|
106
|
+
|
|
107
|
+
## Task storage
|
|
108
|
+
|
|
109
|
+
Configured via `/todos → ⚙ Settings` or the `PI_TODO` environment variable:
|
|
110
|
+
|
|
111
|
+
| Mode | File | Behaviour |
|
|
112
|
+
|------|------|-----------|
|
|
113
|
+
| `memory` | *(none)* | In-memory only — tasks lost when session ends |
|
|
114
|
+
| `session` *(default)* | `<cwd>/.pi/tasks/tasks-<sessionId>.json` | Per-session, survives resume |
|
|
115
|
+
| `project` | `<cwd>/.pi/tasks/tasks.json` | Shared across all sessions in the project |
|
|
116
|
+
|
|
117
|
+
Settings are saved to `<cwd>/.pi/tasks-config.json`.
|
|
118
|
+
|
|
119
|
+
### Environment variable override
|
|
34
120
|
|
|
121
|
+
| Variable | Value | Behaviour |
|
|
122
|
+
|----------|-------|-----------|
|
|
123
|
+
| `PI_TODO` | `off` | In-memory only (CI/automation) |
|
|
124
|
+
| `PI_TODO` | `sprint-1` | Named shared list at `~/.pi/tasks/sprint-1.json` |
|
|
125
|
+
| `PI_TODO` | `/abs/path.json` | Explicit absolute file path |
|
|
126
|
+
|
|
127
|
+
## Auto-clear completed tasks
|
|
128
|
+
|
|
129
|
+
| Mode | Behaviour |
|
|
130
|
+
|------|-----------|
|
|
131
|
+
| `never` | Completed tasks stay visible until manually cleared |
|
|
132
|
+
| `on_list_complete` *(default)* | Cleared after all tasks complete and a few idle turns pass |
|
|
133
|
+
| `on_task_complete` | Each task cleared individually a few turns after completion |
|
|
134
|
+
|
|
135
|
+
## Widget
|
|
136
|
+
|
|
137
|
+
Persistent task list rendered above the editor:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
● 4 tasks (1 done, 1 in progress, 2 open)
|
|
141
|
+
✔ Design the API
|
|
142
|
+
✳ Implementing auth… (42s)
|
|
143
|
+
◻ Write tests › blocked by #t2
|
|
144
|
+
◻ Update docs
|
|
35
145
|
```
|
|
36
|
-
|
|
146
|
+
|
|
147
|
+
| Icon | Meaning |
|
|
148
|
+
|------|---------|
|
|
149
|
+
| `✔` | Completed (strikethrough + dim) |
|
|
150
|
+
| `◼` | In-progress |
|
|
151
|
+
| `✳`/`✽` | Animated spinner — actively executing (shows elapsed time) |
|
|
152
|
+
| `◻` | Pending |
|
|
153
|
+
|
|
154
|
+
## `/todos` command
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
/todos — open the interactive task manager
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Menu options:
|
|
161
|
+
- **View all tasks** — select a task to start / complete / delete it
|
|
162
|
+
- **Clear completed** — remove all completed tasks
|
|
163
|
+
- **Clear all** — remove all tasks
|
|
164
|
+
- **⚙ Settings** — configure task storage and auto-clear
|
|
165
|
+
|
|
166
|
+
## Install
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pi install npm:@agnishc/edb-todo
|
|
37
170
|
```
|
|
38
171
|
|
|
39
172
|
## License
|
package/package.json
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-clear.ts — Turn-based auto-clearing of completed tasks.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - "on_task_complete": each completed task gets its own countdown, deleted individually
|
|
6
|
+
* - "on_list_complete": countdown starts when ALL tasks are completed, cleared as a batch
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FileTaskStore } from "./file-store.js";
|
|
10
|
+
|
|
11
|
+
export type AutoClearMode = "never" | "on_list_complete" | "on_task_complete";
|
|
12
|
+
|
|
13
|
+
export class AutoClearManager {
|
|
14
|
+
/** Per-task: turn when task was marked completed ("on_task_complete" mode). */
|
|
15
|
+
private completedAtTurn = new Map<string, number>();
|
|
16
|
+
/** Turn when ALL tasks became completed ("on_list_complete" mode). */
|
|
17
|
+
private allCompletedAtTurn: number | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
public getStore: () => FileTaskStore,
|
|
21
|
+
private getMode: () => AutoClearMode,
|
|
22
|
+
/** How many turns completed tasks linger before auto-clearing. */
|
|
23
|
+
private clearDelayTurns = 4,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
/** Record a task completion. Call after updating status. */
|
|
27
|
+
trackCompletion(taskId: string, currentTurn: number): void {
|
|
28
|
+
const mode = this.getMode();
|
|
29
|
+
if (mode === "never") return;
|
|
30
|
+
|
|
31
|
+
if (mode === "on_task_complete") {
|
|
32
|
+
this.completedAtTurn.set(taskId, currentTurn);
|
|
33
|
+
} else if (mode === "on_list_complete") {
|
|
34
|
+
this.checkAllCompleted(currentTurn);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private checkAllCompleted(currentTurn: number): void {
|
|
39
|
+
const tasks = this.getStore().list();
|
|
40
|
+
if (tasks.length > 0 && tasks.every((t) => t.status === "completed")) {
|
|
41
|
+
if (this.allCompletedAtTurn === null) this.allCompletedAtTurn = currentTurn;
|
|
42
|
+
} else {
|
|
43
|
+
this.allCompletedAtTurn = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Reset batch countdown (e.g., when a new task is created). */
|
|
48
|
+
resetBatchCountdown(): void {
|
|
49
|
+
this.allCompletedAtTurn = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Reset all tracking state (e.g., on new session). */
|
|
53
|
+
reset(): void {
|
|
54
|
+
this.completedAtTurn.clear();
|
|
55
|
+
this.allCompletedAtTurn = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Called on each turn start. Deletes tasks whose linger period has expired.
|
|
60
|
+
* Returns true if any tasks were cleared.
|
|
61
|
+
*/
|
|
62
|
+
onTurnStart(currentTurn: number): boolean {
|
|
63
|
+
const mode = this.getMode();
|
|
64
|
+
let cleared = false;
|
|
65
|
+
|
|
66
|
+
if (mode === "on_task_complete") {
|
|
67
|
+
for (const [taskId, turn] of this.completedAtTurn) {
|
|
68
|
+
const task = this.getStore().get(taskId);
|
|
69
|
+
if (!task || task.status !== "completed") {
|
|
70
|
+
this.completedAtTurn.delete(taskId);
|
|
71
|
+
} else if (currentTurn - turn >= this.clearDelayTurns) {
|
|
72
|
+
this.getStore().delete(taskId);
|
|
73
|
+
this.completedAtTurn.delete(taskId);
|
|
74
|
+
cleared = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else if (mode === "on_list_complete" && this.allCompletedAtTurn !== null) {
|
|
78
|
+
if (currentTurn - this.allCompletedAtTurn >= this.clearDelayTurns) {
|
|
79
|
+
this.getStore().clearCompleted();
|
|
80
|
+
this.allCompletedAtTurn = null;
|
|
81
|
+
cleared = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return cleared;
|
|
86
|
+
}
|
|
87
|
+
}
|