@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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/dist/domain/habit.d.ts +38 -0
  4. package/dist/domain/habit.js +1 -0
  5. package/dist/domain/note.d.ts +21 -0
  6. package/dist/domain/note.js +1 -0
  7. package/dist/domain/recurring.d.ts +29 -0
  8. package/dist/domain/recurring.js +1 -0
  9. package/dist/domain/task-actions.d.ts +24 -0
  10. package/dist/domain/task-actions.js +1 -0
  11. package/dist/domain/task.d.ts +50 -0
  12. package/dist/domain/task.js +1 -0
  13. package/dist/extension/current-task-context.d.ts +26 -0
  14. package/dist/extension/current-task-context.js +140 -0
  15. package/dist/extension/register-commands.d.ts +114 -0
  16. package/dist/extension/register-commands.js +1214 -0
  17. package/dist/extension/register-events.d.ts +3 -0
  18. package/dist/extension/register-events.js +30 -0
  19. package/dist/extension/register-tools.d.ts +17 -0
  20. package/dist/extension/register-tools.js +36 -0
  21. package/dist/extension/register-ui.d.ts +3 -0
  22. package/dist/extension/register-ui.js +7 -0
  23. package/dist/extension/sync-status-context.d.ts +26 -0
  24. package/dist/extension/sync-status-context.js +162 -0
  25. package/dist/extension/task-browse-filter-context.d.ts +16 -0
  26. package/dist/extension/task-browse-filter-context.js +40 -0
  27. package/dist/flows/browse-tasks.d.ts +7 -0
  28. package/dist/flows/browse-tasks.js +2 -0
  29. package/dist/flows/comment-on-task.d.ts +7 -0
  30. package/dist/flows/comment-on-task.js +2 -0
  31. package/dist/flows/create-task.d.ts +7 -0
  32. package/dist/flows/create-task.js +2 -0
  33. package/dist/flows/pick-current-task.d.ts +7 -0
  34. package/dist/flows/pick-current-task.js +4 -0
  35. package/dist/flows/show-task-detail.d.ts +7 -0
  36. package/dist/flows/show-task-detail.js +2 -0
  37. package/dist/flows/update-task.d.ts +7 -0
  38. package/dist/flows/update-task.js +2 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +12 -0
  41. package/dist/services/habit-service.d.ts +38 -0
  42. package/dist/services/habit-service.js +1 -0
  43. package/dist/services/note-service.d.ts +5 -0
  44. package/dist/services/note-service.js +1 -0
  45. package/dist/services/project-integration-service.d.ts +109 -0
  46. package/dist/services/project-integration-service.js +122 -0
  47. package/dist/services/project-service.d.ts +27 -0
  48. package/dist/services/project-service.js +8 -0
  49. package/dist/services/recurring-service.d.ts +37 -0
  50. package/dist/services/recurring-service.js +1 -0
  51. package/dist/services/repo-context.d.ts +55 -0
  52. package/dist/services/repo-context.js +135 -0
  53. package/dist/services/task-browse-filter-store.d.ts +31 -0
  54. package/dist/services/task-browse-filter-store.js +47 -0
  55. package/dist/services/task-service.d.ts +42 -0
  56. package/dist/services/task-service.js +1 -0
  57. package/dist/services/task-session-store.d.ts +30 -0
  58. package/dist/services/task-session-store.js +55 -0
  59. package/dist/services/todu/daemon-client.d.ts +93 -0
  60. package/dist/services/todu/daemon-client.js +660 -0
  61. package/dist/services/todu/daemon-config.d.ts +17 -0
  62. package/dist/services/todu/daemon-config.js +38 -0
  63. package/dist/services/todu/daemon-connection.d.ts +61 -0
  64. package/dist/services/todu/daemon-connection.js +633 -0
  65. package/dist/services/todu/daemon-events.d.ts +11 -0
  66. package/dist/services/todu/daemon-events.js +1 -0
  67. package/dist/services/todu/default-task-service.d.ts +34 -0
  68. package/dist/services/todu/default-task-service.js +109 -0
  69. package/dist/services/todu/todu-habit-service.d.ts +24 -0
  70. package/dist/services/todu/todu-habit-service.js +80 -0
  71. package/dist/services/todu/todu-note-service.d.ts +20 -0
  72. package/dist/services/todu/todu-note-service.js +35 -0
  73. package/dist/services/todu/todu-project-integration-service.d.ts +27 -0
  74. package/dist/services/todu/todu-project-integration-service.js +45 -0
  75. package/dist/services/todu/todu-project-service.d.ts +24 -0
  76. package/dist/services/todu/todu-project-service.js +42 -0
  77. package/dist/services/todu/todu-recurring-service.d.ts +27 -0
  78. package/dist/services/todu/todu-recurring-service.js +72 -0
  79. package/dist/services/todu/todu-task-service.d.ts +23 -0
  80. package/dist/services/todu/todu-task-service.js +80 -0
  81. package/dist/tools/habit-mutation-tools.d.ts +170 -0
  82. package/dist/tools/habit-mutation-tools.js +363 -0
  83. package/dist/tools/habit-read-tools.d.ts +61 -0
  84. package/dist/tools/habit-read-tools.js +152 -0
  85. package/dist/tools/note-read-tools.d.ts +79 -0
  86. package/dist/tools/note-read-tools.js +148 -0
  87. package/dist/tools/project-integration-tools.d.ts +92 -0
  88. package/dist/tools/project-integration-tools.js +344 -0
  89. package/dist/tools/project-mutation-tools.d.ts +100 -0
  90. package/dist/tools/project-mutation-tools.js +205 -0
  91. package/dist/tools/project-read-tools.d.ts +59 -0
  92. package/dist/tools/project-read-tools.js +131 -0
  93. package/dist/tools/recurring-mutation-tools.d.ts +130 -0
  94. package/dist/tools/recurring-mutation-tools.js +317 -0
  95. package/dist/tools/recurring-read-tools.d.ts +31 -0
  96. package/dist/tools/recurring-read-tools.js +57 -0
  97. package/dist/tools/task-mutation-tools.d.ts +159 -0
  98. package/dist/tools/task-mutation-tools.js +340 -0
  99. package/dist/tools/task-read-tools.d.ts +91 -0
  100. package/dist/tools/task-read-tools.js +186 -0
  101. package/dist/ui/components/habit-table.d.ts +5 -0
  102. package/dist/ui/components/habit-table.js +34 -0
  103. package/dist/ui/components/loaders.d.ts +6 -0
  104. package/dist/ui/components/loaders.js +5 -0
  105. package/dist/ui/components/task-detail.d.ts +19 -0
  106. package/dist/ui/components/task-detail.js +74 -0
  107. package/dist/ui/components/task-list.d.ts +8 -0
  108. package/dist/ui/components/task-list.js +7 -0
  109. package/dist/ui/components/task-settings.d.ts +7 -0
  110. package/dist/ui/components/task-settings.js +12 -0
  111. package/dist/ui/renderers/task-tool-renderer.d.ts +4 -0
  112. package/dist/ui/renderers/task-tool-renderer.js +4 -0
  113. package/dist/ui/widgets/current-task-widget.d.ts +7 -0
  114. package/dist/ui/widgets/current-task-widget.js +20 -0
  115. package/dist/ui/widgets/next-actions-widget.d.ts +7 -0
  116. package/dist/ui/widgets/next-actions-widget.js +5 -0
  117. package/dist/utils/key-hints.d.ts +6 -0
  118. package/dist/utils/key-hints.js +2 -0
  119. package/dist/utils/schedule.d.ts +35 -0
  120. package/dist/utils/schedule.js +111 -0
  121. package/dist/utils/task-filters.d.ts +3 -0
  122. package/dist/utils/task-filters.js +7 -0
  123. package/dist/utils/task-format.d.ts +4 -0
  124. package/dist/utils/task-format.js +6 -0
  125. package/dist/utils/timezone.d.ts +2 -0
  126. package/dist/utils/timezone.js +9 -0
  127. 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, };