@todu/pi-extensions 0.1.0
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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/domain/habit.d.ts +38 -0
- package/dist/domain/habit.js +1 -0
- package/dist/domain/note.d.ts +21 -0
- package/dist/domain/note.js +1 -0
- package/dist/domain/recurring.d.ts +29 -0
- package/dist/domain/recurring.js +1 -0
- package/dist/domain/task-actions.d.ts +24 -0
- package/dist/domain/task-actions.js +1 -0
- package/dist/domain/task.d.ts +50 -0
- package/dist/domain/task.js +1 -0
- package/dist/extension/current-task-context.d.ts +26 -0
- package/dist/extension/current-task-context.js +140 -0
- package/dist/extension/register-commands.d.ts +114 -0
- package/dist/extension/register-commands.js +1214 -0
- package/dist/extension/register-events.d.ts +3 -0
- package/dist/extension/register-events.js +30 -0
- package/dist/extension/register-tools.d.ts +17 -0
- package/dist/extension/register-tools.js +36 -0
- package/dist/extension/register-ui.d.ts +3 -0
- package/dist/extension/register-ui.js +7 -0
- package/dist/extension/sync-status-context.d.ts +26 -0
- package/dist/extension/sync-status-context.js +162 -0
- package/dist/extension/task-browse-filter-context.d.ts +16 -0
- package/dist/extension/task-browse-filter-context.js +40 -0
- package/dist/flows/browse-tasks.d.ts +7 -0
- package/dist/flows/browse-tasks.js +2 -0
- package/dist/flows/comment-on-task.d.ts +7 -0
- package/dist/flows/comment-on-task.js +2 -0
- package/dist/flows/create-task.d.ts +7 -0
- package/dist/flows/create-task.js +2 -0
- package/dist/flows/pick-current-task.d.ts +7 -0
- package/dist/flows/pick-current-task.js +4 -0
- package/dist/flows/show-task-detail.d.ts +7 -0
- package/dist/flows/show-task-detail.js +2 -0
- package/dist/flows/update-task.d.ts +7 -0
- package/dist/flows/update-task.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/services/habit-service.d.ts +38 -0
- package/dist/services/habit-service.js +1 -0
- package/dist/services/note-service.d.ts +5 -0
- package/dist/services/note-service.js +1 -0
- package/dist/services/project-integration-service.d.ts +109 -0
- package/dist/services/project-integration-service.js +122 -0
- package/dist/services/project-service.d.ts +27 -0
- package/dist/services/project-service.js +8 -0
- package/dist/services/recurring-service.d.ts +37 -0
- package/dist/services/recurring-service.js +1 -0
- package/dist/services/repo-context.d.ts +55 -0
- package/dist/services/repo-context.js +135 -0
- package/dist/services/task-browse-filter-store.d.ts +31 -0
- package/dist/services/task-browse-filter-store.js +47 -0
- package/dist/services/task-service.d.ts +42 -0
- package/dist/services/task-service.js +1 -0
- package/dist/services/task-session-store.d.ts +30 -0
- package/dist/services/task-session-store.js +55 -0
- package/dist/services/todu/daemon-client.d.ts +93 -0
- package/dist/services/todu/daemon-client.js +660 -0
- package/dist/services/todu/daemon-config.d.ts +17 -0
- package/dist/services/todu/daemon-config.js +38 -0
- package/dist/services/todu/daemon-connection.d.ts +61 -0
- package/dist/services/todu/daemon-connection.js +633 -0
- package/dist/services/todu/daemon-events.d.ts +11 -0
- package/dist/services/todu/daemon-events.js +1 -0
- package/dist/services/todu/default-task-service.d.ts +34 -0
- package/dist/services/todu/default-task-service.js +109 -0
- package/dist/services/todu/todu-habit-service.d.ts +24 -0
- package/dist/services/todu/todu-habit-service.js +80 -0
- package/dist/services/todu/todu-note-service.d.ts +20 -0
- package/dist/services/todu/todu-note-service.js +35 -0
- package/dist/services/todu/todu-project-integration-service.d.ts +27 -0
- package/dist/services/todu/todu-project-integration-service.js +45 -0
- package/dist/services/todu/todu-project-service.d.ts +24 -0
- package/dist/services/todu/todu-project-service.js +42 -0
- package/dist/services/todu/todu-recurring-service.d.ts +27 -0
- package/dist/services/todu/todu-recurring-service.js +72 -0
- package/dist/services/todu/todu-task-service.d.ts +23 -0
- package/dist/services/todu/todu-task-service.js +80 -0
- package/dist/tools/habit-mutation-tools.d.ts +170 -0
- package/dist/tools/habit-mutation-tools.js +363 -0
- package/dist/tools/habit-read-tools.d.ts +61 -0
- package/dist/tools/habit-read-tools.js +152 -0
- package/dist/tools/note-read-tools.d.ts +79 -0
- package/dist/tools/note-read-tools.js +148 -0
- package/dist/tools/project-integration-tools.d.ts +92 -0
- package/dist/tools/project-integration-tools.js +344 -0
- package/dist/tools/project-mutation-tools.d.ts +100 -0
- package/dist/tools/project-mutation-tools.js +205 -0
- package/dist/tools/project-read-tools.d.ts +59 -0
- package/dist/tools/project-read-tools.js +131 -0
- package/dist/tools/recurring-mutation-tools.d.ts +130 -0
- package/dist/tools/recurring-mutation-tools.js +317 -0
- package/dist/tools/recurring-read-tools.d.ts +31 -0
- package/dist/tools/recurring-read-tools.js +57 -0
- package/dist/tools/task-mutation-tools.d.ts +159 -0
- package/dist/tools/task-mutation-tools.js +340 -0
- package/dist/tools/task-read-tools.d.ts +91 -0
- package/dist/tools/task-read-tools.js +186 -0
- package/dist/ui/components/habit-table.d.ts +5 -0
- package/dist/ui/components/habit-table.js +34 -0
- package/dist/ui/components/loaders.d.ts +6 -0
- package/dist/ui/components/loaders.js +5 -0
- package/dist/ui/components/task-detail.d.ts +19 -0
- package/dist/ui/components/task-detail.js +74 -0
- package/dist/ui/components/task-list.d.ts +8 -0
- package/dist/ui/components/task-list.js +7 -0
- package/dist/ui/components/task-settings.d.ts +7 -0
- package/dist/ui/components/task-settings.js +12 -0
- package/dist/ui/renderers/task-tool-renderer.d.ts +4 -0
- package/dist/ui/renderers/task-tool-renderer.js +4 -0
- package/dist/ui/widgets/current-task-widget.d.ts +7 -0
- package/dist/ui/widgets/current-task-widget.js +20 -0
- package/dist/ui/widgets/next-actions-widget.d.ts +7 -0
- package/dist/ui/widgets/next-actions-widget.js +5 -0
- package/dist/utils/key-hints.d.ts +6 -0
- package/dist/utils/key-hints.js +2 -0
- package/dist/utils/schedule.d.ts +35 -0
- package/dist/utils/schedule.js +111 -0
- package/dist/utils/task-filters.d.ts +3 -0
- package/dist/utils/task-filters.js +7 -0
- package/dist/utils/task-format.d.ts +4 -0
- package/dist/utils/task-format.js +6 -0
- package/dist/utils/timezone.d.ts +2 -0
- package/dist/utils/timezone.js +9 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Erik Craddock
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# todu-pi-extensions
|
|
2
|
+
|
|
3
|
+
`todu-pi-extensions` is a pi package that adds todu-focused commands, tools, and TUI workflows to [pi](https://pi.dev). It is the pi-side integration layer for working with tasks from [todu](https://github.com/evcraddock/todu) without leaving the agent.
|
|
4
|
+
|
|
5
|
+
Today the package is focused on task browsing, task detail, task creation, and task updates inside pi.
|
|
6
|
+
|
|
7
|
+
## How it fits together
|
|
8
|
+
|
|
9
|
+
- [pi](https://pi.dev) is the host coding agent and extension runtime.
|
|
10
|
+
- [todu](https://github.com/evcraddock/todu) is the task backend and CLI.
|
|
11
|
+
- `todu-pi-extensions` connects pi to todu.
|
|
12
|
+
- [todu-workflow](https://github.com/evcraddock/todu-workflow) is an optional companion project with higher-level workflow skills and policies.
|
|
13
|
+
|
|
14
|
+
`todu-workflow` is not a hard dependency. You can use this package by itself, use it alongside `todu-workflow`, or build your own workflow on top of pi and todu.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
Install these first:
|
|
19
|
+
|
|
20
|
+
- [pi via pi.dev](https://pi.dev)
|
|
21
|
+
- [todu via github.com/evcraddock/todu](https://github.com/evcraddock/todu)
|
|
22
|
+
- Node.js 20+
|
|
23
|
+
- npm
|
|
24
|
+
- [overmind](https://github.com/DarthSim/overmind) for the local dev environment
|
|
25
|
+
|
|
26
|
+
## Install the extension
|
|
27
|
+
|
|
28
|
+
Install the packaged npm release:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pi install npm:@todu/pi-extensions
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Install project-local instead of globally:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pi install -l npm:@todu/pi-extensions
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Install from git when testing an unpublished revision:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pi install git:github.com/evcraddock/todu-pi-extensions
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Install from a local checkout while developing:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
pi install /path/to/todu-pi-extensions
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Local path installs load `dist/index.js`, not `src/index.ts`. Rebuild after source changes so pi picks up the packaged output instead of live source files.
|
|
55
|
+
|
|
56
|
+
After installation, reload pi if it is already running.
|
|
57
|
+
|
|
58
|
+
## Basic usage
|
|
59
|
+
|
|
60
|
+
Use the extension inside pi with commands such as:
|
|
61
|
+
|
|
62
|
+
- `/tasks` to browse and filter tasks
|
|
63
|
+
- `/task` to inspect the current task or a task by ID
|
|
64
|
+
- `/task-new` to create a task
|
|
65
|
+
- `/task-clear` to clear the current task context
|
|
66
|
+
|
|
67
|
+
The package also exposes agent tools for structured task operations such as listing tasks, showing task details, and creating or updating tasks.
|
|
68
|
+
|
|
69
|
+
## Work on this project
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install
|
|
73
|
+
make dev
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`make dev` starts the local dev environment, including the isolated todu daemon used by this repository.
|
|
77
|
+
|
|
78
|
+
Useful commands:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
make check
|
|
82
|
+
make pre-pr
|
|
83
|
+
make dev-cli CMD="task list"
|
|
84
|
+
make help
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Release
|
|
88
|
+
|
|
89
|
+
Tag a version that matches `package.json` and let GitHub Actions publish `@todu/pi-extensions` to npm and attach the package tarball to the GitHub release. The workflow expects `NPM_TOKEN` to be configured in the repository secrets.
|
|
90
|
+
|
|
91
|
+
The isolated dev daemon keeps its state under `.dev/` so local development does not touch your normal todu data.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type HabitId = string;
|
|
2
|
+
export interface HabitSummary {
|
|
3
|
+
id: HabitId;
|
|
4
|
+
title: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
projectName: string | null;
|
|
7
|
+
schedule: string;
|
|
8
|
+
timezone: string;
|
|
9
|
+
startDate: string;
|
|
10
|
+
endDate: string | null;
|
|
11
|
+
nextDue: string;
|
|
12
|
+
paused: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface HabitDetail extends HabitSummary {
|
|
15
|
+
description: string | null;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface HabitStreak {
|
|
20
|
+
current: number;
|
|
21
|
+
longest: number;
|
|
22
|
+
completedToday: boolean;
|
|
23
|
+
totalCheckins: number;
|
|
24
|
+
}
|
|
25
|
+
export interface HabitCheckResult {
|
|
26
|
+
habitId: HabitId;
|
|
27
|
+
date: string;
|
|
28
|
+
completed: boolean;
|
|
29
|
+
streak?: HabitStreak;
|
|
30
|
+
}
|
|
31
|
+
export interface HabitSummaryWithStreak extends HabitSummary {
|
|
32
|
+
streak: HabitStreak | null;
|
|
33
|
+
}
|
|
34
|
+
export interface HabitFilter {
|
|
35
|
+
paused?: boolean;
|
|
36
|
+
projectId?: string;
|
|
37
|
+
query?: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type NoteId = string;
|
|
2
|
+
export type NoteEntityType = "task" | "project" | "habit";
|
|
3
|
+
export interface NoteSummary {
|
|
4
|
+
id: NoteId;
|
|
5
|
+
content: string;
|
|
6
|
+
author: string;
|
|
7
|
+
entityType: NoteEntityType | null;
|
|
8
|
+
entityId: string | null;
|
|
9
|
+
tags: string[];
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NoteFilter {
|
|
13
|
+
entityType?: NoteEntityType;
|
|
14
|
+
entityId?: string;
|
|
15
|
+
tag?: string;
|
|
16
|
+
author?: string;
|
|
17
|
+
from?: string;
|
|
18
|
+
to?: string;
|
|
19
|
+
journal?: boolean;
|
|
20
|
+
timezone?: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TaskPriority } from "./task";
|
|
2
|
+
export type RecurringId = string;
|
|
3
|
+
export type RecurringMissPolicy = "accumulate" | "rollForward";
|
|
4
|
+
export interface RecurringTemplateSummary {
|
|
5
|
+
id: RecurringId;
|
|
6
|
+
title: string;
|
|
7
|
+
projectId: string;
|
|
8
|
+
projectName: string | null;
|
|
9
|
+
priority: TaskPriority;
|
|
10
|
+
schedule: string;
|
|
11
|
+
timezone: string;
|
|
12
|
+
startDate: string;
|
|
13
|
+
endDate: string | null;
|
|
14
|
+
nextDue: string;
|
|
15
|
+
missPolicy: RecurringMissPolicy;
|
|
16
|
+
paused: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface RecurringTemplateDetail extends RecurringTemplateSummary {
|
|
19
|
+
description: string | null;
|
|
20
|
+
labels: string[];
|
|
21
|
+
skippedDates: string[];
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RecurringFilter {
|
|
26
|
+
paused?: boolean;
|
|
27
|
+
projectId?: string;
|
|
28
|
+
query?: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TaskId, TaskPriority, TaskStatus } from "./task";
|
|
2
|
+
export type TaskFlowId = "browse-tasks" | "show-task-detail" | "create-task" | "update-task" | "comment-on-task" | "pick-current-task";
|
|
3
|
+
export interface SetCurrentTaskAction {
|
|
4
|
+
kind: "set-current";
|
|
5
|
+
taskId: TaskId;
|
|
6
|
+
}
|
|
7
|
+
export interface UpdateTaskStatusAction {
|
|
8
|
+
kind: "update-status";
|
|
9
|
+
taskId: TaskId;
|
|
10
|
+
status: TaskStatus;
|
|
11
|
+
}
|
|
12
|
+
export interface UpdateTaskPriorityAction {
|
|
13
|
+
kind: "update-priority";
|
|
14
|
+
taskId: TaskId;
|
|
15
|
+
priority: TaskPriority;
|
|
16
|
+
}
|
|
17
|
+
export interface CommentOnTaskAction {
|
|
18
|
+
kind: "comment";
|
|
19
|
+
taskId: TaskId;
|
|
20
|
+
}
|
|
21
|
+
export interface CreateTaskAction {
|
|
22
|
+
kind: "create";
|
|
23
|
+
}
|
|
24
|
+
export type TaskAction = SetCurrentTaskAction | UpdateTaskStatusAction | UpdateTaskPriorityAction | CommentOnTaskAction | CreateTaskAction;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type TaskId = string;
|
|
2
|
+
export type ProjectId = string;
|
|
3
|
+
export type TaskCommentId = string;
|
|
4
|
+
export type TaskStatus = "active" | "inprogress" | "waiting" | "done" | "cancelled";
|
|
5
|
+
export type TaskPriority = "low" | "medium" | "high";
|
|
6
|
+
export interface ProjectSummary {
|
|
7
|
+
id: ProjectId;
|
|
8
|
+
name: string;
|
|
9
|
+
status: "active" | "done" | "cancelled";
|
|
10
|
+
priority: TaskPriority;
|
|
11
|
+
description: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface TaskComment {
|
|
14
|
+
id: TaskCommentId;
|
|
15
|
+
taskId: TaskId;
|
|
16
|
+
content: string;
|
|
17
|
+
author: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TaskSummary {
|
|
21
|
+
id: TaskId;
|
|
22
|
+
title: string;
|
|
23
|
+
status: TaskStatus;
|
|
24
|
+
priority: TaskPriority;
|
|
25
|
+
projectId: ProjectId | null;
|
|
26
|
+
projectName: string | null;
|
|
27
|
+
labels: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface TaskDetail extends TaskSummary {
|
|
30
|
+
description: string | null;
|
|
31
|
+
comments: TaskComment[];
|
|
32
|
+
}
|
|
33
|
+
export type TaskSortField = "priority" | "dueDate" | "createdAt" | "updatedAt" | "title";
|
|
34
|
+
export type TaskSortDirection = "asc" | "desc";
|
|
35
|
+
export interface TaskFilter {
|
|
36
|
+
projectId?: ProjectId | null;
|
|
37
|
+
statuses?: TaskStatus[];
|
|
38
|
+
priorities?: TaskPriority[];
|
|
39
|
+
query?: string;
|
|
40
|
+
from?: string;
|
|
41
|
+
to?: string;
|
|
42
|
+
updatedFrom?: string;
|
|
43
|
+
updatedTo?: string;
|
|
44
|
+
label?: string;
|
|
45
|
+
overdue?: boolean;
|
|
46
|
+
today?: boolean;
|
|
47
|
+
sort?: TaskSortField;
|
|
48
|
+
sortDirection?: TaskSortDirection;
|
|
49
|
+
timezone?: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { TaskDetail, TaskId } from "../domain/task";
|
|
3
|
+
import { type TaskSessionStore } from "../services/task-session-store";
|
|
4
|
+
import { type ToduTaskServiceRuntime } from "../services/todu/default-task-service";
|
|
5
|
+
declare const CURRENT_TASK_STATUS_KEY = "todu-current-task";
|
|
6
|
+
declare const CURRENT_TASK_WIDGET_KEY = "todu-current-task";
|
|
7
|
+
export interface CurrentTaskContextState {
|
|
8
|
+
currentTaskId: TaskId | null;
|
|
9
|
+
currentTask: TaskDetail | null;
|
|
10
|
+
}
|
|
11
|
+
export interface CurrentTaskContextController {
|
|
12
|
+
getState(): CurrentTaskContextState;
|
|
13
|
+
restoreFromBranch(ctx: ExtensionContext): Promise<void>;
|
|
14
|
+
setCurrentTask(ctx: ExtensionContext, task: TaskDetail | null): Promise<void>;
|
|
15
|
+
clearCurrentTask(ctx: ExtensionContext): Promise<void>;
|
|
16
|
+
handleDataChanged(): Promise<void>;
|
|
17
|
+
dispose(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateCurrentTaskContextControllerDependencies {
|
|
20
|
+
runtime?: Pick<ToduTaskServiceRuntime, "client" | "ensureConnected">;
|
|
21
|
+
taskSessionStore?: TaskSessionStore;
|
|
22
|
+
}
|
|
23
|
+
declare const createCurrentTaskContextController: (pi: Pick<ExtensionAPI, "appendEntry">, dependencies?: CreateCurrentTaskContextControllerDependencies) => CurrentTaskContextController;
|
|
24
|
+
declare const getDefaultCurrentTaskContextController: (pi?: Pick<ExtensionAPI, "appendEntry">) => CurrentTaskContextController;
|
|
25
|
+
declare const resetDefaultCurrentTaskContextController: () => Promise<void>;
|
|
26
|
+
export { createCurrentTaskContextController, CURRENT_TASK_STATUS_KEY, CURRENT_TASK_WIDGET_KEY, getDefaultCurrentTaskContextController, resetDefaultCurrentTaskContextController, };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createInMemoryTaskSessionStore, persistTaskSessionState, restoreTaskSessionState, } from "../services/task-session-store.js";
|
|
2
|
+
import { getDefaultToduTaskServiceRuntime, } from "../services/todu/default-task-service.js";
|
|
3
|
+
import { createCurrentTaskWidgetViewModel } from "../ui/widgets/current-task-widget.js";
|
|
4
|
+
const CURRENT_TASK_STATUS_KEY = "todu-current-task";
|
|
5
|
+
const CURRENT_TASK_WIDGET_KEY = "todu-current-task";
|
|
6
|
+
const createCurrentTaskContextController = (pi, dependencies = {}) => {
|
|
7
|
+
const runtime = dependencies.runtime ?? getDefaultToduTaskServiceRuntime();
|
|
8
|
+
const taskSessionStore = dependencies.taskSessionStore ?? createInMemoryTaskSessionStore();
|
|
9
|
+
let currentTask = null;
|
|
10
|
+
let activeContext = null;
|
|
11
|
+
let dataChangedSubscription = null;
|
|
12
|
+
let subscribePromise = null;
|
|
13
|
+
const updateAmbientUi = (ctx) => {
|
|
14
|
+
if (!ctx.hasUI) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { currentTaskId } = taskSessionStore.getState();
|
|
18
|
+
if (!currentTaskId && !currentTask) {
|
|
19
|
+
ctx.ui.setStatus(CURRENT_TASK_STATUS_KEY, undefined);
|
|
20
|
+
ctx.ui.setWidget(CURRENT_TASK_WIDGET_KEY, undefined);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const viewModel = createCurrentTaskWidgetViewModel(currentTask, currentTaskId);
|
|
24
|
+
ctx.ui.setWidget(CURRENT_TASK_WIDGET_KEY, [viewModel.title, viewModel.subtitle]);
|
|
25
|
+
if (currentTask) {
|
|
26
|
+
ctx.ui.setStatus(CURRENT_TASK_STATUS_KEY, `${currentTask.id} • ${currentTask.title}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
ctx.ui.setStatus(CURRENT_TASK_STATUS_KEY, currentTaskId ?? undefined);
|
|
30
|
+
};
|
|
31
|
+
const ensureDataChangedSubscription = async () => {
|
|
32
|
+
if (dataChangedSubscription) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (subscribePromise) {
|
|
36
|
+
await subscribePromise;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
subscribePromise = runtime.client
|
|
40
|
+
.on("data.changed", () => {
|
|
41
|
+
void controller.handleDataChanged();
|
|
42
|
+
})
|
|
43
|
+
.then((subscription) => {
|
|
44
|
+
dataChangedSubscription = subscription;
|
|
45
|
+
return subscription;
|
|
46
|
+
})
|
|
47
|
+
.finally(() => {
|
|
48
|
+
subscribePromise = null;
|
|
49
|
+
});
|
|
50
|
+
await subscribePromise;
|
|
51
|
+
};
|
|
52
|
+
const refreshCurrentTask = async (ctx) => {
|
|
53
|
+
const { currentTaskId } = taskSessionStore.getState();
|
|
54
|
+
if (!currentTaskId) {
|
|
55
|
+
currentTask = null;
|
|
56
|
+
updateAmbientUi(ctx);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const taskService = await runtime.ensureConnected();
|
|
61
|
+
const task = await taskService.getTask(currentTaskId);
|
|
62
|
+
if (!task || isTerminalCurrentTaskStatus(task.status)) {
|
|
63
|
+
currentTask = null;
|
|
64
|
+
taskSessionStore.clearCurrentTask();
|
|
65
|
+
persistTaskSessionState(pi.appendEntry, taskSessionStore.getState());
|
|
66
|
+
updateAmbientUi(ctx);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
currentTask = task;
|
|
70
|
+
updateAmbientUi(ctx);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
currentTask = null;
|
|
74
|
+
updateAmbientUi(ctx);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const controller = {
|
|
78
|
+
getState: () => ({
|
|
79
|
+
currentTaskId: taskSessionStore.getState().currentTaskId,
|
|
80
|
+
currentTask,
|
|
81
|
+
}),
|
|
82
|
+
async restoreFromBranch(ctx) {
|
|
83
|
+
activeContext = ctx;
|
|
84
|
+
taskSessionStore.replaceState(restoreTaskSessionState(ctx.sessionManager.getBranch()));
|
|
85
|
+
if (taskSessionStore.getState().currentTaskId) {
|
|
86
|
+
await ensureDataChangedSubscription();
|
|
87
|
+
}
|
|
88
|
+
await refreshCurrentTask(ctx);
|
|
89
|
+
},
|
|
90
|
+
async setCurrentTask(ctx, task) {
|
|
91
|
+
activeContext = ctx;
|
|
92
|
+
currentTask = task;
|
|
93
|
+
if (task) {
|
|
94
|
+
taskSessionStore.setCurrentTask(task.id);
|
|
95
|
+
await ensureDataChangedSubscription();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
taskSessionStore.clearCurrentTask();
|
|
99
|
+
}
|
|
100
|
+
persistTaskSessionState(pi.appendEntry, taskSessionStore.getState());
|
|
101
|
+
updateAmbientUi(ctx);
|
|
102
|
+
},
|
|
103
|
+
async clearCurrentTask(ctx) {
|
|
104
|
+
await controller.setCurrentTask(ctx, null);
|
|
105
|
+
},
|
|
106
|
+
async handleDataChanged() {
|
|
107
|
+
if (!activeContext) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
await refreshCurrentTask(activeContext);
|
|
111
|
+
},
|
|
112
|
+
async dispose() {
|
|
113
|
+
dataChangedSubscription?.unsubscribe();
|
|
114
|
+
dataChangedSubscription = null;
|
|
115
|
+
subscribePromise = null;
|
|
116
|
+
activeContext = null;
|
|
117
|
+
currentTask = null;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
return controller;
|
|
121
|
+
};
|
|
122
|
+
let defaultCurrentTaskContextController = null;
|
|
123
|
+
const getDefaultCurrentTaskContextController = (pi) => {
|
|
124
|
+
if (!defaultCurrentTaskContextController) {
|
|
125
|
+
if (!pi) {
|
|
126
|
+
throw new Error("Current task context controller has not been initialized");
|
|
127
|
+
}
|
|
128
|
+
defaultCurrentTaskContextController = createCurrentTaskContextController(pi);
|
|
129
|
+
}
|
|
130
|
+
return defaultCurrentTaskContextController;
|
|
131
|
+
};
|
|
132
|
+
const isTerminalCurrentTaskStatus = (status) => status === "done" || status === "cancelled";
|
|
133
|
+
const resetDefaultCurrentTaskContextController = async () => {
|
|
134
|
+
if (!defaultCurrentTaskContextController) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await defaultCurrentTaskContextController.dispose();
|
|
138
|
+
defaultCurrentTaskContextController = null;
|
|
139
|
+
};
|
|
140
|
+
export { createCurrentTaskContextController, CURRENT_TASK_STATUS_KEY, CURRENT_TASK_WIDGET_KEY, getDefaultCurrentTaskContextController, resetDefaultCurrentTaskContextController, };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { ProjectSummary, TaskDetail, TaskFilter, TaskId, TaskPriority, TaskStatus, TaskSummary } from "../domain/task";
|
|
3
|
+
import type { HabitService } from "../services/habit-service";
|
|
4
|
+
import type { TaskService } from "../services/task-service";
|
|
5
|
+
import type { TaskBrowseFilterState } from "../services/task-browse-filter-store";
|
|
6
|
+
import { type TaskDetailActionKind } from "../ui/components/task-detail";
|
|
7
|
+
import { type TaskBrowseFilterContextController } from "./task-browse-filter-context";
|
|
8
|
+
declare const DEFAULT_TASK_BROWSE_FILTER_STATE: TaskBrowseFilterState;
|
|
9
|
+
interface LoadedTasksResult {
|
|
10
|
+
status: "loaded";
|
|
11
|
+
tasks: TaskSummary[];
|
|
12
|
+
}
|
|
13
|
+
interface CancelledTasksResult {
|
|
14
|
+
status: "cancelled";
|
|
15
|
+
}
|
|
16
|
+
interface ErrorTasksResult {
|
|
17
|
+
status: "error";
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
type TaskBrowseLoadResult = LoadedTasksResult | CancelledTasksResult | ErrorTasksResult;
|
|
21
|
+
type SelectTaskProjectResult = {
|
|
22
|
+
status: "selected";
|
|
23
|
+
project: ProjectSummary;
|
|
24
|
+
} | {
|
|
25
|
+
status: "cancelled";
|
|
26
|
+
} | {
|
|
27
|
+
status: "unavailable";
|
|
28
|
+
};
|
|
29
|
+
interface TaskAuthoringDraft {
|
|
30
|
+
title: string;
|
|
31
|
+
description: string | null;
|
|
32
|
+
projectName: string;
|
|
33
|
+
}
|
|
34
|
+
interface TaskAuthoringResult {
|
|
35
|
+
title: string;
|
|
36
|
+
description: string | null;
|
|
37
|
+
}
|
|
38
|
+
type EditTaskBrowseFiltersResult = {
|
|
39
|
+
status: "saved";
|
|
40
|
+
filterState: TaskBrowseFilterState;
|
|
41
|
+
} | {
|
|
42
|
+
status: "cancelled";
|
|
43
|
+
};
|
|
44
|
+
type TaskBrowseViewResult = {
|
|
45
|
+
status: "selected";
|
|
46
|
+
taskId: TaskId;
|
|
47
|
+
} | {
|
|
48
|
+
status: "change-filters";
|
|
49
|
+
} | {
|
|
50
|
+
status: "clear-filters";
|
|
51
|
+
} | {
|
|
52
|
+
status: "closed";
|
|
53
|
+
};
|
|
54
|
+
type EmptyTaskBrowseAction = "change-filters" | "clear-filters" | "close";
|
|
55
|
+
export interface ResolveDefaultProjectResult {
|
|
56
|
+
projectId: string;
|
|
57
|
+
projectName: string;
|
|
58
|
+
}
|
|
59
|
+
export interface RegisterCommandDependencies {
|
|
60
|
+
getTaskService?: () => Promise<TaskService>;
|
|
61
|
+
getHabitService?: () => Promise<HabitService>;
|
|
62
|
+
resolveDefaultProject?: (taskService: TaskService) => Promise<ResolveDefaultProjectResult | null>;
|
|
63
|
+
getCurrentTaskId?: () => TaskId | null;
|
|
64
|
+
clearCurrentTask?: (ctx: ExtensionCommandContext) => Promise<void>;
|
|
65
|
+
promptTaskTitle?: (ctx: ExtensionCommandContext) => Promise<string | null>;
|
|
66
|
+
selectTaskProject?: (ctx: ExtensionCommandContext, taskService: TaskService) => Promise<SelectTaskProjectResult>;
|
|
67
|
+
editTaskExplanation?: (ctx: ExtensionCommandContext, taskTitle: string) => Promise<string | undefined>;
|
|
68
|
+
confirmTaskAuthoring?: (ctx: ExtensionCommandContext, draft: TaskAuthoringDraft) => Promise<boolean>;
|
|
69
|
+
requestTaskAuthoringAssistance?: (ctx: ExtensionCommandContext, draft: TaskAuthoringDraft) => Promise<TaskAuthoringResult | null>;
|
|
70
|
+
taskBrowseFilterController?: TaskBrowseFilterContextController;
|
|
71
|
+
editTaskBrowseFilters?: (ctx: ExtensionCommandContext, taskService: TaskService, currentState: TaskBrowseFilterState) => Promise<EditTaskBrowseFiltersResult>;
|
|
72
|
+
loadTasks?: (ctx: ExtensionCommandContext, taskService: TaskService, filter: TaskFilter, filterSummary: string) => Promise<TaskBrowseLoadResult>;
|
|
73
|
+
showTaskBrowseView?: (ctx: ExtensionCommandContext, tasks: TaskSummary[], filterState: TaskBrowseFilterState) => Promise<TaskBrowseViewResult>;
|
|
74
|
+
showEmptyState?: (ctx: ExtensionCommandContext, filterState: TaskBrowseFilterState) => Promise<EmptyTaskBrowseAction>;
|
|
75
|
+
setCurrentTask?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<void>;
|
|
76
|
+
showTaskDetailView?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskDetailActionKind | null>;
|
|
77
|
+
selectTaskStatus?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskStatus | null>;
|
|
78
|
+
selectTaskPriority?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskPriority | null>;
|
|
79
|
+
editTaskComment?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<string | null>;
|
|
80
|
+
openTaskDetail?: (ctx: ExtensionCommandContext, taskService: TaskService, taskId: TaskId) => Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
declare const createTasksCommandHandler: (dependencies?: RegisterCommandDependencies) => ((args: string, ctx: ExtensionCommandContext) => Promise<void>);
|
|
83
|
+
declare const createTaskCommandHandler: (dependencies?: RegisterCommandDependencies) => ((args: string, ctx: ExtensionCommandContext) => Promise<void>);
|
|
84
|
+
declare const createTaskClearCommandHandler: (dependencies?: RegisterCommandDependencies) => ((args: string, ctx: ExtensionCommandContext) => Promise<void>);
|
|
85
|
+
declare const createTaskNewCommandHandler: (dependencies?: RegisterCommandDependencies) => ((args: string, ctx: ExtensionCommandContext) => Promise<void>);
|
|
86
|
+
declare const createHabitsCommandHandler: (dependencies?: RegisterCommandDependencies) => ((args: string, ctx: ExtensionCommandContext) => Promise<void>);
|
|
87
|
+
declare const registerCommands: (pi: ExtensionAPI, dependencies?: RegisterCommandDependencies) => void;
|
|
88
|
+
declare const loadTasksWithLoader: (ctx: ExtensionCommandContext, taskService: TaskService, filter: TaskFilter, filterSummary: string) => Promise<TaskBrowseLoadResult>;
|
|
89
|
+
declare const showTaskBrowseFilterMode: (ctx: ExtensionCommandContext, taskService: TaskService, currentState: TaskBrowseFilterState) => Promise<EditTaskBrowseFiltersResult>;
|
|
90
|
+
declare const selectTaskBrowseViewAction: (ctx: ExtensionCommandContext, tasks: TaskSummary[], filterState: TaskBrowseFilterState) => Promise<TaskBrowseViewResult>;
|
|
91
|
+
declare const showEmptyTasksState: (ctx: ExtensionCommandContext, filterState: TaskBrowseFilterState) => Promise<EmptyTaskBrowseAction>;
|
|
92
|
+
declare const createSavedTaskBrowseFilterState: (overrides?: Partial<TaskBrowseFilterState>) => TaskBrowseFilterState;
|
|
93
|
+
declare const createTaskFilterFromBrowseState: (filterState: TaskBrowseFilterState) => TaskFilter;
|
|
94
|
+
declare const formatTaskBrowseFilterSummary: (filterState: TaskBrowseFilterState) => string;
|
|
95
|
+
interface OpenTaskDetailDependencies {
|
|
96
|
+
setCurrentTask: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<void>;
|
|
97
|
+
showTaskDetailView?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskDetailActionKind | null>;
|
|
98
|
+
selectTaskStatus?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskStatus | null>;
|
|
99
|
+
selectTaskPriority?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskPriority | null>;
|
|
100
|
+
editTaskComment?: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<string | null>;
|
|
101
|
+
}
|
|
102
|
+
declare const openSelectedTaskDetail: (ctx: ExtensionCommandContext, taskService: TaskService, taskId: TaskId, dependencies: OpenTaskDetailDependencies) => Promise<void>;
|
|
103
|
+
declare const openTaskDetailHub: (ctx: ExtensionCommandContext, taskService: TaskService, taskId: TaskId, dependencies: OpenTaskDetailDependencies) => Promise<void>;
|
|
104
|
+
declare const selectTaskDetailAction: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskDetailActionKind | null>;
|
|
105
|
+
declare const selectTaskStatusFromList: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskStatus | null>;
|
|
106
|
+
declare const selectTaskPriorityFromList: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<TaskPriority | null>;
|
|
107
|
+
declare const editTaskComment: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<string | null>;
|
|
108
|
+
declare const syncCurrentTaskIfFocused: (ctx: ExtensionCommandContext, task: TaskDetail, setCurrentTask: (ctx: ExtensionCommandContext, task: TaskDetail) => Promise<void>) => Promise<void>;
|
|
109
|
+
declare const resolveRequestedTaskId: (args: string, currentTaskId: TaskId | null) => TaskId | null;
|
|
110
|
+
declare const resolveDefaultTaskBrowseFilterState: (taskService: TaskService, resolveDefaultProject: (taskService: TaskService) => Promise<ResolveDefaultProjectResult | null>) => Promise<TaskBrowseFilterState>;
|
|
111
|
+
declare const resolveDefaultProjectFromRepo: (taskService: TaskService, repoContextService?: import("../services/repo-context").RepoContextService) => Promise<ResolveDefaultProjectResult | null>;
|
|
112
|
+
declare const matchProjectByName: (projects: ProjectSummary[], candidateName: string) => ProjectSummary | null;
|
|
113
|
+
declare const formatTasksCommandError: (error: unknown, prefix: string) => string;
|
|
114
|
+
export { createHabitsCommandHandler, createTaskClearCommandHandler, createTaskCommandHandler, createTaskNewCommandHandler, createTasksCommandHandler, createSavedTaskBrowseFilterState, createTaskFilterFromBrowseState, DEFAULT_TASK_BROWSE_FILTER_STATE, editTaskComment, formatTaskBrowseFilterSummary, formatTasksCommandError, loadTasksWithLoader, matchProjectByName, openSelectedTaskDetail, openTaskDetailHub, registerCommands, resolveDefaultProjectFromRepo, resolveDefaultTaskBrowseFilterState, resolveRequestedTaskId, selectTaskBrowseViewAction, selectTaskDetailAction, selectTaskPriorityFromList, selectTaskStatusFromList, showEmptyTasksState, showTaskBrowseFilterMode, syncCurrentTaskIfFocused, };
|