@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
@@ -0,0 +1,57 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ const RecurringListParams = Type.Object({});
3
+ const createRecurringListToolDefinition = ({ getRecurringService, }) => ({
4
+ name: "recurring_list",
5
+ label: "Recurring List",
6
+ description: "List recurring task templates.",
7
+ promptSnippet: "List recurring task templates through the native recurring service.",
8
+ promptGuidelines: [
9
+ "Use this tool for backend recurring template lookups in normal chat.",
10
+ "Keep recurring_list unfiltered in the first wave unless the task explicitly widens scope.",
11
+ ],
12
+ parameters: RecurringListParams,
13
+ async execute(_toolCallId, _params) {
14
+ try {
15
+ const recurringService = await getRecurringService();
16
+ const templates = await recurringService.listRecurring();
17
+ const details = {
18
+ kind: "recurring_list",
19
+ templates,
20
+ total: templates.length,
21
+ empty: templates.length === 0,
22
+ };
23
+ return {
24
+ content: [{ type: "text", text: formatRecurringListContent(details) }],
25
+ details,
26
+ };
27
+ }
28
+ catch (error) {
29
+ throw new Error(formatToolError(error, "recurring_list failed"), { cause: error });
30
+ }
31
+ },
32
+ });
33
+ const registerRecurringReadTools = (pi, dependencies) => {
34
+ pi.registerTool(createRecurringListToolDefinition(dependencies));
35
+ };
36
+ const formatRecurringListContent = (details) => {
37
+ if (details.empty) {
38
+ return "No recurring templates found.";
39
+ }
40
+ const lines = [`Recurring templates (${details.total}):`];
41
+ for (const template of details.templates) {
42
+ lines.push(`- ${formatRecurringSummaryLine(template)}`);
43
+ }
44
+ return lines.join("\n");
45
+ };
46
+ const formatRecurringSummaryLine = (template) => {
47
+ const projectLabel = template.projectName ?? template.projectId;
48
+ const status = template.paused ? "paused" : "active";
49
+ return `${template.id} • ${template.title} • ${status} • ${template.priority} • ${projectLabel}`;
50
+ };
51
+ const formatToolError = (error, prefix) => {
52
+ if (error instanceof Error && error.message.trim().length > 0) {
53
+ return `${prefix}: ${error.message}`;
54
+ }
55
+ return prefix;
56
+ };
57
+ export { createRecurringListToolDefinition, formatRecurringListContent, registerRecurringReadTools, };
@@ -0,0 +1,159 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ProjectSummary, TaskComment, TaskDetail, TaskId, TaskPriority, TaskStatus } from "../domain/task";
3
+ import type { AddTaskCommentInput, CreateTaskInput, DeleteTaskResult, MoveTaskResult, TaskService, UpdateTaskInput } from "../services/task-service";
4
+ interface TaskCreateToolParams {
5
+ title: string;
6
+ projectId: string;
7
+ description?: string;
8
+ }
9
+ interface TaskUpdateToolParams {
10
+ taskId: TaskId;
11
+ title?: string;
12
+ status?: TaskStatus;
13
+ priority?: TaskPriority;
14
+ description?: string;
15
+ }
16
+ interface TaskCommentCreateToolParams {
17
+ taskId: TaskId;
18
+ content: string;
19
+ }
20
+ interface TaskCreateToolDetails {
21
+ kind: "task_create";
22
+ input: CreateTaskInput;
23
+ task: TaskDetail;
24
+ }
25
+ interface TaskUpdateToolDetails {
26
+ kind: "task_update";
27
+ input: UpdateTaskInput;
28
+ task: TaskDetail;
29
+ }
30
+ interface TaskCommentCreateToolDetails {
31
+ kind: "task_comment_create";
32
+ taskId: TaskId;
33
+ comment: TaskComment;
34
+ }
35
+ interface TaskDeleteToolParams {
36
+ taskId: string;
37
+ }
38
+ interface TaskDeleteToolDetails {
39
+ kind: "task_delete";
40
+ taskId: TaskId;
41
+ found: boolean;
42
+ deleted: boolean;
43
+ result?: DeleteTaskResult;
44
+ }
45
+ interface TaskMoveToolParams {
46
+ taskId: string;
47
+ projectId: string;
48
+ }
49
+ interface TaskMoveToolDetails {
50
+ kind: "task_move";
51
+ sourceTaskId: TaskId;
52
+ found: boolean;
53
+ moved: boolean;
54
+ result?: MoveTaskResult;
55
+ }
56
+ interface TaskMutationToolDependencies {
57
+ getTaskService: () => Promise<TaskService>;
58
+ }
59
+ declare const createTaskCreateToolDefinition: ({ getTaskService }: TaskMutationToolDependencies) => {
60
+ name: string;
61
+ label: string;
62
+ description: string;
63
+ promptSnippet: string;
64
+ promptGuidelines: string[];
65
+ parameters: import("@sinclair/typebox").TObject<{
66
+ title: import("@sinclair/typebox").TString;
67
+ projectId: import("@sinclair/typebox").TString;
68
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
69
+ }>;
70
+ execute(_toolCallId: string, params: TaskCreateToolParams): Promise<{
71
+ content: {
72
+ type: "text";
73
+ text: string;
74
+ }[];
75
+ details: TaskCreateToolDetails;
76
+ }>;
77
+ };
78
+ declare const createTaskUpdateToolDefinition: ({ getTaskService }: TaskMutationToolDependencies) => {
79
+ name: string;
80
+ label: string;
81
+ description: string;
82
+ promptSnippet: string;
83
+ promptGuidelines: string[];
84
+ parameters: import("@sinclair/typebox").TObject<{
85
+ taskId: import("@sinclair/typebox").TString;
86
+ title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
87
+ status: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"active" | "inprogress" | "waiting" | "done" | "cancelled">>;
88
+ priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>;
89
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
90
+ }>;
91
+ execute(_toolCallId: string, params: TaskUpdateToolParams): Promise<{
92
+ content: {
93
+ type: "text";
94
+ text: string;
95
+ }[];
96
+ details: TaskUpdateToolDetails;
97
+ }>;
98
+ };
99
+ declare const createTaskCommentCreateToolDefinition: ({ getTaskService, }: TaskMutationToolDependencies) => {
100
+ name: string;
101
+ label: string;
102
+ description: string;
103
+ promptSnippet: string;
104
+ promptGuidelines: string[];
105
+ parameters: import("@sinclair/typebox").TObject<{
106
+ taskId: import("@sinclair/typebox").TString;
107
+ content: import("@sinclair/typebox").TString;
108
+ }>;
109
+ execute(_toolCallId: string, params: TaskCommentCreateToolParams): Promise<{
110
+ content: {
111
+ type: "text";
112
+ text: string;
113
+ }[];
114
+ details: TaskCommentCreateToolDetails;
115
+ }>;
116
+ };
117
+ declare const createTaskDeleteToolDefinition: ({ getTaskService }: TaskMutationToolDependencies) => {
118
+ name: string;
119
+ label: string;
120
+ description: string;
121
+ promptSnippet: string;
122
+ promptGuidelines: string[];
123
+ parameters: import("@sinclair/typebox").TObject<{
124
+ taskId: import("@sinclair/typebox").TString;
125
+ }>;
126
+ execute(_toolCallId: string, params: TaskDeleteToolParams): Promise<{
127
+ content: {
128
+ type: "text";
129
+ text: string;
130
+ }[];
131
+ details: TaskDeleteToolDetails;
132
+ }>;
133
+ };
134
+ declare const createTaskMoveToolDefinition: ({ getTaskService }: TaskMutationToolDependencies) => {
135
+ name: string;
136
+ label: string;
137
+ description: string;
138
+ promptSnippet: string;
139
+ promptGuidelines: string[];
140
+ parameters: import("@sinclair/typebox").TObject<{
141
+ taskId: import("@sinclair/typebox").TString;
142
+ projectId: import("@sinclair/typebox").TString;
143
+ }>;
144
+ execute(_toolCallId: string, params: TaskMoveToolParams): Promise<{
145
+ content: {
146
+ type: "text";
147
+ text: string;
148
+ }[];
149
+ details: TaskMoveToolDetails;
150
+ }>;
151
+ };
152
+ declare const registerTaskMutationTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: TaskMutationToolDependencies) => void;
153
+ declare const normalizeCreateTaskInput: (params: TaskCreateToolParams) => CreateTaskInput;
154
+ declare const resolveCreateTaskInput: (taskService: TaskService, params: TaskCreateToolParams) => Promise<CreateTaskInput>;
155
+ declare const resolveProjectForTaskCreate: (taskService: TaskService, projectRef: string) => Promise<ProjectSummary>;
156
+ declare const normalizeUpdateTaskInput: (params: TaskUpdateToolParams) => UpdateTaskInput;
157
+ declare const normalizeTaskCommentInput: (params: TaskCommentCreateToolParams) => AddTaskCommentInput;
158
+ export type { TaskCommentCreateToolDetails, TaskCreateToolDetails, TaskDeleteToolDetails, TaskMoveToolDetails, TaskMutationToolDependencies, TaskUpdateToolDetails, };
159
+ export { createTaskCommentCreateToolDefinition, createTaskCreateToolDefinition, createTaskDeleteToolDefinition, createTaskMoveToolDefinition, createTaskUpdateToolDefinition, normalizeCreateTaskInput, normalizeTaskCommentInput, normalizeUpdateTaskInput, registerTaskMutationTools, resolveCreateTaskInput, resolveProjectForTaskCreate, };
@@ -0,0 +1,340 @@
1
+ import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { commentOnTask } from "../flows/comment-on-task.js";
4
+ import { createTask } from "../flows/create-task.js";
5
+ import { updateTask } from "../flows/update-task.js";
6
+ import { ToduTaskServiceError } from "../services/todu/todu-task-service.js";
7
+ const TASK_STATUS_VALUES = ["active", "inprogress", "waiting", "done", "cancelled"];
8
+ const TASK_PRIORITY_VALUES = ["low", "medium", "high"];
9
+ const TaskCreateParams = Type.Object({
10
+ title: Type.String({ description: "Task title" }),
11
+ projectId: Type.String({ description: "Project ID or unique project name for the new task" }),
12
+ description: Type.Optional(Type.String({ description: "Optional task description" })),
13
+ });
14
+ const TaskUpdateParams = Type.Object({
15
+ taskId: Type.String({ description: "Task ID" }),
16
+ title: Type.Optional(Type.String({ description: "Optional replacement task title" })),
17
+ status: Type.Optional(StringEnum(TASK_STATUS_VALUES, { description: "Optional next task status" })),
18
+ priority: Type.Optional(StringEnum(TASK_PRIORITY_VALUES, { description: "Optional next task priority" })),
19
+ description: Type.Optional(Type.String({
20
+ description: "Optional replacement description. Use an empty string to clear it.",
21
+ })),
22
+ });
23
+ const TaskCommentCreateParams = Type.Object({
24
+ taskId: Type.String({ description: "Task ID" }),
25
+ content: Type.String({ description: "Comment content" }),
26
+ });
27
+ const TaskDeleteParams = Type.Object({
28
+ taskId: Type.String({ description: "Task ID" }),
29
+ });
30
+ const TaskMoveParams = Type.Object({
31
+ taskId: Type.String({ description: "Task ID" }),
32
+ projectId: Type.String({ description: "Target project ID or unique project name" }),
33
+ });
34
+ const createTaskCreateToolDefinition = ({ getTaskService }) => ({
35
+ name: "task_create",
36
+ label: "Task Create",
37
+ description: "Create a task with a title, project reference, and optional description.",
38
+ promptSnippet: "Create a task when the title and project reference are known.",
39
+ promptGuidelines: [
40
+ "Use this tool for backend task creation in normal chat instead of interactive slash-command flows.",
41
+ "Provide an explicit project ID when known.",
42
+ "If only a project name is available, pass the exact unique project name so the tool can resolve it.",
43
+ "Do not guess project identity.",
44
+ ],
45
+ parameters: TaskCreateParams,
46
+ async execute(_toolCallId, params) {
47
+ try {
48
+ const taskService = await getTaskService();
49
+ const input = await resolveCreateTaskInput(taskService, params);
50
+ const task = await createTask({ taskService }, input);
51
+ const details = {
52
+ kind: "task_create",
53
+ input,
54
+ task,
55
+ };
56
+ return {
57
+ content: [{ type: "text", text: formatTaskCreateContent(task) }],
58
+ details,
59
+ };
60
+ }
61
+ catch (error) {
62
+ throw new Error(formatToolError(error, "task_create failed"), { cause: error });
63
+ }
64
+ },
65
+ });
66
+ const createTaskUpdateToolDefinition = ({ getTaskService }) => ({
67
+ name: "task_update",
68
+ label: "Task Update",
69
+ description: "Update a task's title, status, priority, or description.",
70
+ promptSnippet: "Update a task's supported metadata fields by task ID.",
71
+ promptGuidelines: [
72
+ "Use this tool for backend task updates in normal chat.",
73
+ "Supported fields are title, status, priority, and description.",
74
+ ],
75
+ parameters: TaskUpdateParams,
76
+ async execute(_toolCallId, params) {
77
+ try {
78
+ const input = normalizeUpdateTaskInput(params);
79
+ const taskService = await getTaskService();
80
+ const task = await updateTask({ taskService }, input);
81
+ const details = {
82
+ kind: "task_update",
83
+ input,
84
+ task,
85
+ };
86
+ return {
87
+ content: [{ type: "text", text: formatTaskUpdateContent(task, input) }],
88
+ details,
89
+ };
90
+ }
91
+ catch (error) {
92
+ throw new Error(formatToolError(error, "task_update failed"), { cause: error });
93
+ }
94
+ },
95
+ });
96
+ const createTaskCommentCreateToolDefinition = ({ getTaskService, }) => ({
97
+ name: "task_comment_create",
98
+ label: "Task Comment Create",
99
+ description: "Add a comment to a task.",
100
+ promptSnippet: "Add a comment to a task when the task ID and comment content are known.",
101
+ promptGuidelines: [
102
+ "Use this tool to attach task comments in normal chat.",
103
+ "Provide explicit task IDs instead of guessing which task to comment on.",
104
+ ],
105
+ parameters: TaskCommentCreateParams,
106
+ async execute(_toolCallId, params) {
107
+ try {
108
+ const input = normalizeTaskCommentInput(params);
109
+ const taskService = await getTaskService();
110
+ const comment = await commentOnTask({ taskService }, input);
111
+ const details = {
112
+ kind: "task_comment_create",
113
+ taskId: input.taskId,
114
+ comment,
115
+ };
116
+ return {
117
+ content: [{ type: "text", text: formatTaskCommentCreateContent(comment) }],
118
+ details,
119
+ };
120
+ }
121
+ catch (error) {
122
+ throw new Error(formatToolError(error, "task_comment_create failed"), { cause: error });
123
+ }
124
+ },
125
+ });
126
+ const createTaskDeleteToolDefinition = ({ getTaskService }) => ({
127
+ name: "task_delete",
128
+ label: "Task Delete",
129
+ description: "Delete a task by explicit ID.",
130
+ promptSnippet: "Delete a task by explicit task ID.",
131
+ promptGuidelines: [
132
+ "Use this tool for backend task deletion in normal chat.",
133
+ "Provide the task ID explicitly.",
134
+ "Do not guess which task to delete.",
135
+ ],
136
+ parameters: TaskDeleteParams,
137
+ async execute(_toolCallId, params) {
138
+ const taskId = normalizeRequiredText(params.taskId, "taskId");
139
+ try {
140
+ const taskService = await getTaskService();
141
+ const result = await taskService.deleteTask(taskId);
142
+ const details = {
143
+ kind: "task_delete",
144
+ taskId,
145
+ found: true,
146
+ deleted: true,
147
+ result,
148
+ };
149
+ return {
150
+ content: [{ type: "text", text: formatTaskDeleteContent(details) }],
151
+ details,
152
+ };
153
+ }
154
+ catch (error) {
155
+ if (isTaskNotFoundError(error)) {
156
+ const details = {
157
+ kind: "task_delete",
158
+ taskId,
159
+ found: false,
160
+ deleted: false,
161
+ };
162
+ return {
163
+ content: [{ type: "text", text: formatTaskDeleteContent(details) }],
164
+ details,
165
+ };
166
+ }
167
+ throw new Error(formatToolError(error, "task_delete failed"), { cause: error });
168
+ }
169
+ },
170
+ });
171
+ const createTaskMoveToolDefinition = ({ getTaskService }) => ({
172
+ name: "task_move",
173
+ label: "Task Move",
174
+ description: "Move a task to a different project.",
175
+ promptSnippet: "Move a task to a different project by task ID and target project reference.",
176
+ promptGuidelines: [
177
+ "Use this tool to move a task between projects in normal chat.",
178
+ "Provide the task ID and target project ID or unique project name explicitly.",
179
+ "The original task is cancelled and a new task is created in the target project.",
180
+ ],
181
+ parameters: TaskMoveParams,
182
+ async execute(_toolCallId, params) {
183
+ const taskId = normalizeRequiredText(params.taskId, "taskId");
184
+ const projectRef = normalizeRequiredText(params.projectId, "projectId");
185
+ try {
186
+ const taskService = await getTaskService();
187
+ const project = await resolveProjectForTaskCreate(taskService, projectRef);
188
+ const result = await taskService.moveTask({
189
+ taskId,
190
+ targetProjectId: project.id,
191
+ });
192
+ const details = {
193
+ kind: "task_move",
194
+ sourceTaskId: taskId,
195
+ found: true,
196
+ moved: true,
197
+ result,
198
+ };
199
+ return {
200
+ content: [{ type: "text", text: formatTaskMoveContent(details) }],
201
+ details,
202
+ };
203
+ }
204
+ catch (error) {
205
+ if (isTaskNotFoundError(error)) {
206
+ const details = {
207
+ kind: "task_move",
208
+ sourceTaskId: taskId,
209
+ found: false,
210
+ moved: false,
211
+ };
212
+ return {
213
+ content: [{ type: "text", text: formatTaskMoveContent(details) }],
214
+ details,
215
+ };
216
+ }
217
+ throw new Error(formatToolError(error, "task_move failed"), { cause: error });
218
+ }
219
+ },
220
+ });
221
+ const registerTaskMutationTools = (pi, dependencies) => {
222
+ pi.registerTool(createTaskCreateToolDefinition(dependencies));
223
+ pi.registerTool(createTaskUpdateToolDefinition(dependencies));
224
+ pi.registerTool(createTaskCommentCreateToolDefinition(dependencies));
225
+ pi.registerTool(createTaskDeleteToolDefinition(dependencies));
226
+ pi.registerTool(createTaskMoveToolDefinition(dependencies));
227
+ };
228
+ const normalizeCreateTaskInput = (params) => ({
229
+ title: normalizeRequiredText(params.title, "title"),
230
+ projectId: normalizeRequiredText(params.projectId, "projectId"),
231
+ description: normalizeOptionalDescription(params, "description"),
232
+ });
233
+ const resolveCreateTaskInput = async (taskService, params) => {
234
+ const input = normalizeCreateTaskInput(params);
235
+ const project = await resolveProjectForTaskCreate(taskService, normalizeRequiredText(input.projectId ?? "", "projectId"));
236
+ return {
237
+ ...input,
238
+ projectId: project.id,
239
+ };
240
+ };
241
+ const resolveProjectForTaskCreate = async (taskService, projectRef) => {
242
+ const project = await taskService.getProject(projectRef);
243
+ if (project) {
244
+ return project;
245
+ }
246
+ const projects = await taskService.listProjects();
247
+ const nameMatches = projects.filter((candidate) => candidate.name === projectRef);
248
+ if (nameMatches.length === 0) {
249
+ throw new Error(`project not found: ${projectRef}`);
250
+ }
251
+ if (nameMatches.length > 1) {
252
+ throw new Error(`multiple projects matched: ${projectRef}`);
253
+ }
254
+ const matchedProject = nameMatches[0];
255
+ if (!matchedProject) {
256
+ throw new Error(`project not found: ${projectRef}`);
257
+ }
258
+ return matchedProject;
259
+ };
260
+ const normalizeUpdateTaskInput = (params) => {
261
+ const input = {
262
+ taskId: normalizeRequiredText(params.taskId, "taskId"),
263
+ status: params.status,
264
+ priority: params.priority,
265
+ };
266
+ if (hasOwn(params, "title")) {
267
+ input.title = normalizeRequiredText(params.title ?? "", "title");
268
+ }
269
+ if (hasOwn(params, "description")) {
270
+ input.description = normalizeNullableText(params.description);
271
+ }
272
+ if (input.title === undefined &&
273
+ input.status === undefined &&
274
+ input.priority === undefined &&
275
+ !hasOwn(input, "description")) {
276
+ throw new Error("task_update requires at least one supported field: title, status, priority, or description");
277
+ }
278
+ return input;
279
+ };
280
+ const normalizeTaskCommentInput = (params) => ({
281
+ taskId: normalizeRequiredText(params.taskId, "taskId"),
282
+ content: normalizeRequiredText(params.content, "content"),
283
+ });
284
+ const normalizeRequiredText = (value, fieldName) => {
285
+ const trimmedValue = value.trim();
286
+ if (trimmedValue.length === 0) {
287
+ throw new Error(`${fieldName} is required`);
288
+ }
289
+ return trimmedValue;
290
+ };
291
+ const normalizeOptionalDescription = (params, fieldName) => {
292
+ if (!hasOwn(params, fieldName)) {
293
+ return undefined;
294
+ }
295
+ return normalizeNullableText(params[fieldName]);
296
+ };
297
+ const normalizeNullableText = (value) => {
298
+ const trimmedValue = value?.trim() ?? "";
299
+ return trimmedValue.length > 0 ? trimmedValue : null;
300
+ };
301
+ const hasOwn = (value, property) => Object.prototype.hasOwnProperty.call(value, property);
302
+ const formatTaskCreateContent = (task) => [
303
+ `Created task ${task.id}: ${task.title}`,
304
+ `Status: ${task.status}`,
305
+ `Priority: ${task.priority}`,
306
+ `Project: ${task.projectName ?? task.projectId ?? "No project"}`,
307
+ ].join("\n");
308
+ const formatTaskUpdateContent = (task, input) => {
309
+ const changedFields = [
310
+ input.title !== undefined ? `title=${JSON.stringify(input.title)}` : null,
311
+ input.status !== undefined ? `status=${input.status}` : null,
312
+ input.priority !== undefined ? `priority=${input.priority}` : null,
313
+ hasOwn(input, "description")
314
+ ? `description=${input.description === null ? "cleared" : "updated"}`
315
+ : null,
316
+ ].filter((value) => value !== null);
317
+ return [`Updated task ${task.id}: ${task.title}`, `Changes: ${changedFields.join(", ")}`].join("\n");
318
+ };
319
+ const formatTaskCommentCreateContent = (comment) => `Added comment ${comment.id} to task ${comment.taskId}.`;
320
+ const formatTaskDeleteContent = (details) => details.found ? `Deleted task ${details.taskId}.` : `Task not found: ${details.taskId}`;
321
+ const isTaskNotFoundError = (error) => error instanceof ToduTaskServiceError && error.causeCode === "not-found";
322
+ const formatTaskMoveContent = (details) => {
323
+ if (!details.found) {
324
+ return `Task not found: ${details.sourceTaskId}`;
325
+ }
326
+ const target = details.result?.targetTask;
327
+ const projectLabel = target?.projectName ?? target?.projectId ?? "unknown";
328
+ return [
329
+ `Moved task ${details.sourceTaskId} → ${target?.id ?? "unknown"}`,
330
+ `Target project: ${projectLabel}`,
331
+ `New task: ${target?.id}: ${target?.title}`,
332
+ ].join("\n");
333
+ };
334
+ const formatToolError = (error, prefix) => {
335
+ if (error instanceof Error && error.message.trim().length > 0) {
336
+ return `${prefix}: ${error.message}`;
337
+ }
338
+ return prefix;
339
+ };
340
+ export { createTaskCommentCreateToolDefinition, createTaskCreateToolDefinition, createTaskDeleteToolDefinition, createTaskMoveToolDefinition, createTaskUpdateToolDefinition, normalizeCreateTaskInput, normalizeTaskCommentInput, normalizeUpdateTaskInput, registerTaskMutationTools, resolveCreateTaskInput, resolveProjectForTaskCreate, };
@@ -0,0 +1,91 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { TaskDetail, TaskFilter, TaskId, TaskPriority, TaskSortDirection, TaskSortField, TaskStatus, TaskSummary } from "../domain/task";
3
+ import type { TaskService } from "../services/task-service";
4
+ interface TaskListToolParams {
5
+ statuses?: TaskStatus[];
6
+ priorities?: TaskPriority[];
7
+ projectId?: string;
8
+ query?: string;
9
+ from?: string;
10
+ to?: string;
11
+ updatedFrom?: string;
12
+ updatedTo?: string;
13
+ label?: string;
14
+ overdue?: boolean;
15
+ today?: boolean;
16
+ sort?: TaskSortField;
17
+ sortDirection?: TaskSortDirection;
18
+ timezone?: string;
19
+ }
20
+ interface TaskShowToolParams {
21
+ taskId: TaskId;
22
+ }
23
+ interface TaskListToolDetails {
24
+ kind: "task_list";
25
+ filter: TaskFilter;
26
+ tasks: TaskSummary[];
27
+ total: number;
28
+ empty: boolean;
29
+ }
30
+ interface TaskShowToolDetails {
31
+ kind: "task_show";
32
+ taskId: TaskId;
33
+ found: boolean;
34
+ task?: TaskDetail;
35
+ }
36
+ interface TaskReadToolDependencies {
37
+ getTaskService: () => Promise<TaskService>;
38
+ }
39
+ declare const createTaskListToolDefinition: ({ getTaskService }: TaskReadToolDependencies) => {
40
+ name: string;
41
+ label: string;
42
+ description: string;
43
+ promptSnippet: string;
44
+ promptGuidelines: string[];
45
+ parameters: import("@sinclair/typebox").TObject<{
46
+ statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnsafe<"active" | "inprogress" | "waiting" | "done" | "cancelled">>>;
47
+ priorities: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>>;
48
+ projectId: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
49
+ query: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
50
+ from: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
51
+ to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
52
+ updatedFrom: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
53
+ updatedTo: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
54
+ label: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
55
+ overdue: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
56
+ today: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
57
+ sort: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"priority" | "dueDate" | "createdAt" | "updatedAt" | "title">>;
58
+ sortDirection: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"asc" | "desc">>;
59
+ timezone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
60
+ }>;
61
+ execute(_toolCallId: string, params: TaskListToolParams): Promise<{
62
+ content: {
63
+ type: "text";
64
+ text: string;
65
+ }[];
66
+ details: TaskListToolDetails;
67
+ }>;
68
+ };
69
+ declare const createTaskShowToolDefinition: ({ getTaskService }: TaskReadToolDependencies) => {
70
+ name: string;
71
+ label: string;
72
+ description: string;
73
+ promptSnippet: string;
74
+ promptGuidelines: string[];
75
+ parameters: import("@sinclair/typebox").TObject<{
76
+ taskId: import("@sinclair/typebox").TString;
77
+ }>;
78
+ execute(_toolCallId: string, params: TaskShowToolParams): Promise<{
79
+ content: {
80
+ type: "text";
81
+ text: string;
82
+ }[];
83
+ details: TaskShowToolDetails;
84
+ }>;
85
+ };
86
+ declare const registerTaskReadTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: TaskReadToolDependencies) => void;
87
+ declare const normalizeTaskListFilter: (params: TaskListToolParams) => TaskFilter;
88
+ declare const formatTaskListContent: (details: TaskListToolDetails) => string;
89
+ declare const formatTaskShowContent: (task: TaskDetail) => string;
90
+ export type { TaskListToolDetails, TaskShowToolDetails, TaskReadToolDependencies };
91
+ export { createTaskListToolDefinition, createTaskShowToolDefinition, formatTaskListContent, formatTaskShowContent, normalizeTaskListFilter, registerTaskReadTools, };