@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
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { ToduProjectIntegrationServiceError } from "../services/todu/todu-project-integration-service.js";
|
|
4
|
+
import { ToduProjectServiceError } from "../services/todu/todu-project-service.js";
|
|
5
|
+
const PROJECT_PRIORITY_VALUES = ["low", "medium", "high"];
|
|
6
|
+
const ProjectCheckParams = Type.Object({
|
|
7
|
+
repositoryPath: Type.Optional(Type.String({ description: "Optional repository path override" })),
|
|
8
|
+
provider: Type.Optional(Type.String({ description: "Optional explicit repository provider" })),
|
|
9
|
+
targetRef: Type.Optional(Type.String({ description: "Optional explicit repository target reference, like owner/repo" })),
|
|
10
|
+
});
|
|
11
|
+
const ProjectRegisterParams = Type.Object({
|
|
12
|
+
projectName: Type.Optional(Type.String({ description: "Optional project name override" })),
|
|
13
|
+
repositoryPath: Type.Optional(Type.String({ description: "Optional repository path override" })),
|
|
14
|
+
provider: Type.Optional(Type.String({ description: "Optional explicit repository provider" })),
|
|
15
|
+
targetRef: Type.Optional(Type.String({ description: "Optional explicit repository target reference, like owner/repo" })),
|
|
16
|
+
description: Type.Optional(Type.String({ description: "Optional project description" })),
|
|
17
|
+
priority: Type.Optional(StringEnum(PROJECT_PRIORITY_VALUES, { description: "Optional project priority" })),
|
|
18
|
+
});
|
|
19
|
+
const createProjectCheckToolDefinition = ({ getProjectIntegrationService, }) => ({
|
|
20
|
+
name: "project_check",
|
|
21
|
+
label: "Project Check",
|
|
22
|
+
description: "Check whether a repository is registered to a project.",
|
|
23
|
+
promptSnippet: "Check whether a repository is registered through the integration-aware project service.",
|
|
24
|
+
promptGuidelines: [
|
|
25
|
+
"Use this tool for repository-aware registration checks in normal chat.",
|
|
26
|
+
"Keep project_check read-only and return explicit registered, not-registered, or ambiguous states.",
|
|
27
|
+
"Explicit repositoryPath, provider, and targetRef inputs override ambient repo detection where applicable.",
|
|
28
|
+
],
|
|
29
|
+
parameters: ProjectCheckParams,
|
|
30
|
+
async execute(_toolCallId, params) {
|
|
31
|
+
try {
|
|
32
|
+
const input = normalizeProjectCheckInput(params);
|
|
33
|
+
const projectIntegrationService = await getProjectIntegrationService();
|
|
34
|
+
const result = await projectIntegrationService.checkRepositoryBinding(input);
|
|
35
|
+
const details = mapProjectCheckDetails(result, input.repositoryPath);
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: formatProjectCheckContent(details) }],
|
|
38
|
+
details,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(formatToolError(error, "project_check failed"), { cause: error });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const createProjectRegisterToolDefinition = ({ getProjectIntegrationService, getProjectService, }) => ({
|
|
47
|
+
name: "project_register",
|
|
48
|
+
label: "Project Register",
|
|
49
|
+
description: "Register a repository-backed project through integration-aware services.",
|
|
50
|
+
promptSnippet: "Register a repository-backed project without collapsing repo registration into plain project_create.",
|
|
51
|
+
promptGuidelines: [
|
|
52
|
+
"Use this tool only for repository-aware project registration in normal chat.",
|
|
53
|
+
"Do not use project_register as a synonym for plain local project_create.",
|
|
54
|
+
"Report name conflicts, duplicate bindings, ambiguous remotes, and missing repository context explicitly.",
|
|
55
|
+
"Explicit repositoryPath, provider, and targetRef inputs override ambient repo detection where applicable.",
|
|
56
|
+
],
|
|
57
|
+
parameters: ProjectRegisterParams,
|
|
58
|
+
async execute(_toolCallId, params) {
|
|
59
|
+
try {
|
|
60
|
+
const input = normalizeProjectRegisterInput(params);
|
|
61
|
+
const projectIntegrationService = await getProjectIntegrationService();
|
|
62
|
+
const initialCheck = await projectIntegrationService.checkRepositoryBinding(input);
|
|
63
|
+
const nameConflict = initialCheck.kind === "not-registered"
|
|
64
|
+
? await findProjectNameConflict(getProjectService, input, initialCheck)
|
|
65
|
+
: null;
|
|
66
|
+
if (nameConflict) {
|
|
67
|
+
const details = {
|
|
68
|
+
kind: "project_register",
|
|
69
|
+
state: "name-conflict",
|
|
70
|
+
repositoryPath: input.repositoryPath,
|
|
71
|
+
repository: initialCheck.kind === "not-registered" ? initialCheck.repository : undefined,
|
|
72
|
+
conflictingProjects: nameConflict,
|
|
73
|
+
reason: "project-name-conflict",
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: formatProjectRegisterContent(details) }],
|
|
77
|
+
details,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const result = await projectIntegrationService.registerRepositoryProject(input);
|
|
81
|
+
const details = mapProjectRegisterDetails(result, input.repositoryPath);
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: formatProjectRegisterContent(details) }],
|
|
84
|
+
details,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (isProjectConflictError(error)) {
|
|
89
|
+
const details = {
|
|
90
|
+
kind: "project_register",
|
|
91
|
+
state: "name-conflict",
|
|
92
|
+
repositoryPath: params.repositoryPath?.trim() || undefined,
|
|
93
|
+
conflictingProjects: [],
|
|
94
|
+
reason: error.message,
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text: formatProjectRegisterContent(details) }],
|
|
98
|
+
details,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
throw new Error(formatToolError(error, "project_register failed"), { cause: error });
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
const registerProjectIntegrationTools = (pi, dependencies) => {
|
|
106
|
+
pi.registerTool(createProjectCheckToolDefinition(dependencies));
|
|
107
|
+
pi.registerTool(createProjectRegisterToolDefinition(dependencies));
|
|
108
|
+
};
|
|
109
|
+
const normalizeProjectCheckInput = (params) => ({
|
|
110
|
+
repositoryPath: normalizeOptionalText(params.repositoryPath),
|
|
111
|
+
provider: normalizeOptionalText(params.provider),
|
|
112
|
+
targetRef: normalizeOptionalText(params.targetRef),
|
|
113
|
+
});
|
|
114
|
+
const normalizeProjectRegisterInput = (params) => ({
|
|
115
|
+
projectName: normalizeOptionalText(params.projectName),
|
|
116
|
+
repositoryPath: normalizeOptionalText(params.repositoryPath),
|
|
117
|
+
provider: normalizeOptionalText(params.provider),
|
|
118
|
+
targetRef: normalizeOptionalText(params.targetRef),
|
|
119
|
+
description: hasOwn(params, "description")
|
|
120
|
+
? normalizeNullableText(params.description)
|
|
121
|
+
: undefined,
|
|
122
|
+
priority: params.priority,
|
|
123
|
+
});
|
|
124
|
+
const mapProjectCheckDetails = (result, repositoryPath) => {
|
|
125
|
+
switch (result.kind) {
|
|
126
|
+
case "registered":
|
|
127
|
+
return {
|
|
128
|
+
kind: "project_check",
|
|
129
|
+
state: "registered",
|
|
130
|
+
repositoryPath,
|
|
131
|
+
repository: result.repository,
|
|
132
|
+
project: result.project,
|
|
133
|
+
binding: result.binding,
|
|
134
|
+
};
|
|
135
|
+
case "not-registered":
|
|
136
|
+
return {
|
|
137
|
+
kind: "project_check",
|
|
138
|
+
state: "not-registered",
|
|
139
|
+
repositoryPath,
|
|
140
|
+
repository: result.repository,
|
|
141
|
+
};
|
|
142
|
+
case "ambiguous":
|
|
143
|
+
return {
|
|
144
|
+
kind: "project_check",
|
|
145
|
+
state: "ambiguous",
|
|
146
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
147
|
+
repository: result.repository,
|
|
148
|
+
bindings: result.bindings,
|
|
149
|
+
remotes: result.remotes,
|
|
150
|
+
reason: result.reason,
|
|
151
|
+
};
|
|
152
|
+
case "missing-context":
|
|
153
|
+
return {
|
|
154
|
+
kind: "project_check",
|
|
155
|
+
state: "missing-context",
|
|
156
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
157
|
+
reason: result.reason,
|
|
158
|
+
};
|
|
159
|
+
case "unsupported":
|
|
160
|
+
return {
|
|
161
|
+
kind: "project_check",
|
|
162
|
+
state: "unsupported",
|
|
163
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
164
|
+
reason: result.reason,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const mapProjectRegisterDetails = (result, repositoryPath) => {
|
|
169
|
+
if (result.kind === "registered" && "createdProject" in result) {
|
|
170
|
+
return {
|
|
171
|
+
kind: "project_register",
|
|
172
|
+
state: "registered",
|
|
173
|
+
repositoryPath,
|
|
174
|
+
repository: result.repository,
|
|
175
|
+
project: result.project,
|
|
176
|
+
binding: result.binding,
|
|
177
|
+
createdProject: result.createdProject,
|
|
178
|
+
createdBinding: result.createdBinding,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
switch (result.kind) {
|
|
182
|
+
case "already-registered":
|
|
183
|
+
return {
|
|
184
|
+
kind: "project_register",
|
|
185
|
+
state: "already-registered",
|
|
186
|
+
repositoryPath,
|
|
187
|
+
repository: result.repository,
|
|
188
|
+
project: result.project,
|
|
189
|
+
binding: result.binding,
|
|
190
|
+
};
|
|
191
|
+
case "ambiguous":
|
|
192
|
+
return {
|
|
193
|
+
kind: "project_register",
|
|
194
|
+
state: "ambiguous",
|
|
195
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
196
|
+
repository: result.repository,
|
|
197
|
+
bindings: result.bindings,
|
|
198
|
+
remotes: result.remotes,
|
|
199
|
+
reason: result.reason,
|
|
200
|
+
};
|
|
201
|
+
case "missing-context":
|
|
202
|
+
return {
|
|
203
|
+
kind: "project_register",
|
|
204
|
+
state: "missing-context",
|
|
205
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
206
|
+
reason: result.reason,
|
|
207
|
+
};
|
|
208
|
+
case "unsupported":
|
|
209
|
+
return {
|
|
210
|
+
kind: "project_register",
|
|
211
|
+
state: "unsupported",
|
|
212
|
+
repositoryPath: result.repositoryPath ?? repositoryPath,
|
|
213
|
+
reason: result.reason,
|
|
214
|
+
};
|
|
215
|
+
case "not-registered":
|
|
216
|
+
return {
|
|
217
|
+
kind: "project_register",
|
|
218
|
+
state: "missing-context",
|
|
219
|
+
repositoryPath,
|
|
220
|
+
repository: result.repository,
|
|
221
|
+
reason: "not-registered",
|
|
222
|
+
};
|
|
223
|
+
case "registered":
|
|
224
|
+
return {
|
|
225
|
+
kind: "project_register",
|
|
226
|
+
state: "already-registered",
|
|
227
|
+
repositoryPath,
|
|
228
|
+
repository: result.repository,
|
|
229
|
+
project: result.project,
|
|
230
|
+
binding: result.binding,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const findProjectNameConflict = async (getProjectService, input, result) => {
|
|
235
|
+
const requestedName = normalizeProjectRegisterName(input.projectName, result.repository.targetRef);
|
|
236
|
+
const projectService = await getProjectService();
|
|
237
|
+
const projects = await projectService.listProjects();
|
|
238
|
+
const matches = projects.filter((project) => project.name === requestedName);
|
|
239
|
+
return matches.length > 0 ? matches : null;
|
|
240
|
+
};
|
|
241
|
+
const normalizeProjectRegisterName = (projectName, targetRef) => {
|
|
242
|
+
const trimmedName = projectName?.trim();
|
|
243
|
+
if (trimmedName && trimmedName.length > 0) {
|
|
244
|
+
return trimmedName;
|
|
245
|
+
}
|
|
246
|
+
const segments = targetRef.split("/");
|
|
247
|
+
return segments[segments.length - 1] ?? targetRef;
|
|
248
|
+
};
|
|
249
|
+
const formatProjectCheckContent = (details) => {
|
|
250
|
+
switch (details.state) {
|
|
251
|
+
case "registered":
|
|
252
|
+
return [
|
|
253
|
+
"Registration: Registered",
|
|
254
|
+
`Project: ${details.project?.name ?? details.binding?.projectId ?? "(missing project)"}`,
|
|
255
|
+
`Provider: ${details.repository?.provider ?? "-"}`,
|
|
256
|
+
`Repository: ${details.repository?.targetRef ?? "-"}`,
|
|
257
|
+
`Integration ID: ${details.binding?.id ?? "-"}`,
|
|
258
|
+
].join("\n");
|
|
259
|
+
case "not-registered":
|
|
260
|
+
return [
|
|
261
|
+
"Registration: Not Registered",
|
|
262
|
+
`Provider: ${details.repository?.provider ?? "-"}`,
|
|
263
|
+
`Repository: ${details.repository?.targetRef ?? "-"}`,
|
|
264
|
+
"Integration ID: -",
|
|
265
|
+
].join("\n");
|
|
266
|
+
case "ambiguous":
|
|
267
|
+
return [
|
|
268
|
+
"Registration: Ambiguous",
|
|
269
|
+
`Reason: ${details.reason ?? "ambiguous"}`,
|
|
270
|
+
details.remotes ? `Remotes: ${details.remotes.join(", ")}` : null,
|
|
271
|
+
details.bindings
|
|
272
|
+
? `Matching Bindings: ${details.bindings.map((binding) => binding.id).join(", ")}`
|
|
273
|
+
: null,
|
|
274
|
+
]
|
|
275
|
+
.filter((value) => value !== null)
|
|
276
|
+
.join("\n");
|
|
277
|
+
case "missing-context":
|
|
278
|
+
return `Registration: Missing Repository Context\nReason: ${details.reason ?? "missing-context"}`;
|
|
279
|
+
case "unsupported":
|
|
280
|
+
return `Registration: Unsupported Repository Remote\nReason: ${details.reason ?? "unsupported-remote-format"}`;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const formatProjectRegisterContent = (details) => {
|
|
284
|
+
switch (details.state) {
|
|
285
|
+
case "registered":
|
|
286
|
+
return [
|
|
287
|
+
`Registered project ${details.project?.id ?? "-"}: ${details.project?.name ?? "-"}`,
|
|
288
|
+
`Provider: ${details.repository?.provider ?? "-"}`,
|
|
289
|
+
`Repository: ${details.repository?.targetRef ?? "-"}`,
|
|
290
|
+
`Integration ID: ${details.binding?.id ?? "-"}`,
|
|
291
|
+
].join("\n");
|
|
292
|
+
case "already-registered":
|
|
293
|
+
return [
|
|
294
|
+
"Repository is already registered.",
|
|
295
|
+
`Project: ${details.project?.name ?? details.binding?.projectId ?? "(missing project)"}`,
|
|
296
|
+
`Provider: ${details.repository?.provider ?? "-"}`,
|
|
297
|
+
`Repository: ${details.repository?.targetRef ?? "-"}`,
|
|
298
|
+
].join("\n");
|
|
299
|
+
case "name-conflict":
|
|
300
|
+
return [
|
|
301
|
+
"Repository registration blocked by project name conflict.",
|
|
302
|
+
details.conflictingProjects && details.conflictingProjects.length > 0
|
|
303
|
+
? `Conflicting Projects: ${details.conflictingProjects.map((project) => `${project.id} (${project.name})`).join(", ")}`
|
|
304
|
+
: null,
|
|
305
|
+
details.reason ? `Reason: ${details.reason}` : null,
|
|
306
|
+
]
|
|
307
|
+
.filter((value) => value !== null)
|
|
308
|
+
.join("\n");
|
|
309
|
+
case "ambiguous":
|
|
310
|
+
return [
|
|
311
|
+
"Repository registration is ambiguous.",
|
|
312
|
+
`Reason: ${details.reason ?? "ambiguous"}`,
|
|
313
|
+
details.remotes ? `Remotes: ${details.remotes.join(", ")}` : null,
|
|
314
|
+
details.bindings
|
|
315
|
+
? `Matching Bindings: ${details.bindings.map((binding) => binding.id).join(", ")}`
|
|
316
|
+
: null,
|
|
317
|
+
]
|
|
318
|
+
.filter((value) => value !== null)
|
|
319
|
+
.join("\n");
|
|
320
|
+
case "missing-context":
|
|
321
|
+
return `Repository registration requires repository context.\nReason: ${details.reason ?? "missing-context"}`;
|
|
322
|
+
case "unsupported":
|
|
323
|
+
return `Repository registration failed for an unsupported remote.\nReason: ${details.reason ?? "unsupported-remote-format"}`;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const normalizeOptionalText = (value) => {
|
|
327
|
+
const trimmedValue = value?.trim();
|
|
328
|
+
return trimmedValue && trimmedValue.length > 0 ? trimmedValue : undefined;
|
|
329
|
+
};
|
|
330
|
+
const normalizeNullableText = (value) => {
|
|
331
|
+
const trimmedValue = value?.trim() ?? "";
|
|
332
|
+
return trimmedValue.length > 0 ? trimmedValue : null;
|
|
333
|
+
};
|
|
334
|
+
const hasOwn = (value, property) => Object.prototype.hasOwnProperty.call(value, property);
|
|
335
|
+
const isProjectConflictError = (error) => (error instanceof ToduProjectServiceError ||
|
|
336
|
+
error instanceof ToduProjectIntegrationServiceError) &&
|
|
337
|
+
error.causeCode === "conflict";
|
|
338
|
+
const formatToolError = (error, prefix) => {
|
|
339
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
340
|
+
return `${prefix}: ${error.message}`;
|
|
341
|
+
}
|
|
342
|
+
return prefix;
|
|
343
|
+
};
|
|
344
|
+
export { createProjectCheckToolDefinition, createProjectRegisterToolDefinition, formatProjectCheckContent, formatProjectRegisterContent, normalizeProjectCheckInput, normalizeProjectRegisterInput, registerProjectIntegrationTools, };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { ProjectSummary, TaskPriority } from "../domain/task";
|
|
3
|
+
import type { CreateProjectInput, DeleteProjectResult, ProjectService, UpdateProjectInput } from "../services/project-service";
|
|
4
|
+
interface ProjectCreateToolParams {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
priority?: TaskPriority;
|
|
8
|
+
}
|
|
9
|
+
interface ProjectUpdateToolParams {
|
|
10
|
+
projectId: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
status?: ProjectSummary["status"];
|
|
14
|
+
priority?: TaskPriority;
|
|
15
|
+
}
|
|
16
|
+
interface ProjectDeleteToolParams {
|
|
17
|
+
projectId: string;
|
|
18
|
+
}
|
|
19
|
+
interface ProjectCreateToolDetails {
|
|
20
|
+
kind: "project_create";
|
|
21
|
+
input: CreateProjectInput;
|
|
22
|
+
project: ProjectSummary;
|
|
23
|
+
}
|
|
24
|
+
interface ProjectUpdateToolDetails {
|
|
25
|
+
kind: "project_update";
|
|
26
|
+
input: UpdateProjectInput;
|
|
27
|
+
project: ProjectSummary;
|
|
28
|
+
}
|
|
29
|
+
interface ProjectDeleteToolDetails {
|
|
30
|
+
kind: "project_delete";
|
|
31
|
+
projectId: string;
|
|
32
|
+
found: boolean;
|
|
33
|
+
deleted: boolean;
|
|
34
|
+
project?: DeleteProjectResult;
|
|
35
|
+
}
|
|
36
|
+
interface ProjectMutationToolDependencies {
|
|
37
|
+
getProjectService: () => Promise<ProjectService>;
|
|
38
|
+
}
|
|
39
|
+
declare const createProjectCreateToolDefinition: ({ getProjectService, }: ProjectMutationToolDependencies) => {
|
|
40
|
+
name: string;
|
|
41
|
+
label: string;
|
|
42
|
+
description: string;
|
|
43
|
+
promptSnippet: string;
|
|
44
|
+
promptGuidelines: string[];
|
|
45
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
46
|
+
name: import("@sinclair/typebox").TString;
|
|
47
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
48
|
+
priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>;
|
|
49
|
+
}>;
|
|
50
|
+
execute(_toolCallId: string, params: ProjectCreateToolParams): Promise<{
|
|
51
|
+
content: {
|
|
52
|
+
type: "text";
|
|
53
|
+
text: string;
|
|
54
|
+
}[];
|
|
55
|
+
details: ProjectCreateToolDetails;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
declare const createProjectUpdateToolDefinition: ({ getProjectService, }: ProjectMutationToolDependencies) => {
|
|
59
|
+
name: string;
|
|
60
|
+
label: string;
|
|
61
|
+
description: string;
|
|
62
|
+
promptSnippet: string;
|
|
63
|
+
promptGuidelines: string[];
|
|
64
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
65
|
+
projectId: import("@sinclair/typebox").TString;
|
|
66
|
+
name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
67
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
68
|
+
status: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"active" | "done" | "cancelled">>;
|
|
69
|
+
priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>;
|
|
70
|
+
}>;
|
|
71
|
+
execute(_toolCallId: string, params: ProjectUpdateToolParams): Promise<{
|
|
72
|
+
content: {
|
|
73
|
+
type: "text";
|
|
74
|
+
text: string;
|
|
75
|
+
}[];
|
|
76
|
+
details: ProjectUpdateToolDetails;
|
|
77
|
+
}>;
|
|
78
|
+
};
|
|
79
|
+
declare const createProjectDeleteToolDefinition: ({ getProjectService, }: ProjectMutationToolDependencies) => {
|
|
80
|
+
name: string;
|
|
81
|
+
label: string;
|
|
82
|
+
description: string;
|
|
83
|
+
promptSnippet: string;
|
|
84
|
+
promptGuidelines: string[];
|
|
85
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
86
|
+
projectId: import("@sinclair/typebox").TString;
|
|
87
|
+
}>;
|
|
88
|
+
execute(_toolCallId: string, params: ProjectDeleteToolParams): Promise<{
|
|
89
|
+
content: {
|
|
90
|
+
type: "text";
|
|
91
|
+
text: string;
|
|
92
|
+
}[];
|
|
93
|
+
details: ProjectDeleteToolDetails;
|
|
94
|
+
}>;
|
|
95
|
+
};
|
|
96
|
+
declare const registerProjectMutationTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: ProjectMutationToolDependencies) => void;
|
|
97
|
+
declare const normalizeCreateProjectInput: (params: ProjectCreateToolParams) => CreateProjectInput;
|
|
98
|
+
declare const normalizeUpdateProjectInput: (params: ProjectUpdateToolParams) => UpdateProjectInput;
|
|
99
|
+
export type { ProjectCreateToolDetails, ProjectMutationToolDependencies, ProjectUpdateToolDetails, ProjectDeleteToolDetails, };
|
|
100
|
+
export { createProjectCreateToolDefinition, createProjectDeleteToolDefinition, createProjectUpdateToolDefinition, normalizeCreateProjectInput, normalizeUpdateProjectInput, registerProjectMutationTools, };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { ToduProjectServiceError } from "../services/todu/todu-project-service.js";
|
|
4
|
+
const PROJECT_STATUS_VALUES = ["active", "done", "cancelled"];
|
|
5
|
+
const PROJECT_PRIORITY_VALUES = ["low", "medium", "high"];
|
|
6
|
+
const ProjectCreateParams = Type.Object({
|
|
7
|
+
name: Type.String({ description: "Project name" }),
|
|
8
|
+
description: Type.Optional(Type.String({ description: "Optional project description" })),
|
|
9
|
+
priority: Type.Optional(StringEnum(PROJECT_PRIORITY_VALUES, { description: "Optional project priority" })),
|
|
10
|
+
});
|
|
11
|
+
const ProjectUpdateParams = Type.Object({
|
|
12
|
+
projectId: Type.String({ description: "Project ID" }),
|
|
13
|
+
name: Type.Optional(Type.String({ description: "Optional replacement project name" })),
|
|
14
|
+
description: Type.Optional(Type.String({
|
|
15
|
+
description: "Optional replacement description. Use an empty string to clear it.",
|
|
16
|
+
})),
|
|
17
|
+
status: Type.Optional(StringEnum(PROJECT_STATUS_VALUES, { description: "Optional next project status" })),
|
|
18
|
+
priority: Type.Optional(StringEnum(PROJECT_PRIORITY_VALUES, { description: "Optional next project priority" })),
|
|
19
|
+
});
|
|
20
|
+
const ProjectDeleteParams = Type.Object({
|
|
21
|
+
projectId: Type.String({ description: "Project ID" }),
|
|
22
|
+
});
|
|
23
|
+
const createProjectCreateToolDefinition = ({ getProjectService, }) => ({
|
|
24
|
+
name: "project_create",
|
|
25
|
+
label: "Project Create",
|
|
26
|
+
description: "Create a plain project record.",
|
|
27
|
+
promptSnippet: "Create a plain project record through the native project service.",
|
|
28
|
+
promptGuidelines: [
|
|
29
|
+
"Use this tool for plain project record creation in normal chat.",
|
|
30
|
+
"Do not use it for repository registration or integration-binding behavior.",
|
|
31
|
+
],
|
|
32
|
+
parameters: ProjectCreateParams,
|
|
33
|
+
async execute(_toolCallId, params) {
|
|
34
|
+
try {
|
|
35
|
+
const input = normalizeCreateProjectInput(params);
|
|
36
|
+
const projectService = await getProjectService();
|
|
37
|
+
const project = await projectService.createProject(input);
|
|
38
|
+
const details = {
|
|
39
|
+
kind: "project_create",
|
|
40
|
+
input,
|
|
41
|
+
project,
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: formatProjectCreateContent(project) }],
|
|
45
|
+
details,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(formatToolError(error, "project_create failed"), { cause: error });
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const createProjectUpdateToolDefinition = ({ getProjectService, }) => ({
|
|
54
|
+
name: "project_update",
|
|
55
|
+
label: "Project Update",
|
|
56
|
+
description: "Update a plain project's name, description, status, or priority.",
|
|
57
|
+
promptSnippet: "Update a plain project record by explicit project ID.",
|
|
58
|
+
promptGuidelines: [
|
|
59
|
+
"Use this tool for plain project-record updates in normal chat.",
|
|
60
|
+
"Supported fields are name, description, status, and priority.",
|
|
61
|
+
"Do not use it for repo inspection or integration-binding changes.",
|
|
62
|
+
],
|
|
63
|
+
parameters: ProjectUpdateParams,
|
|
64
|
+
async execute(_toolCallId, params) {
|
|
65
|
+
try {
|
|
66
|
+
const input = normalizeUpdateProjectInput(params);
|
|
67
|
+
const projectService = await getProjectService();
|
|
68
|
+
const project = await projectService.updateProject(input);
|
|
69
|
+
const details = {
|
|
70
|
+
kind: "project_update",
|
|
71
|
+
input,
|
|
72
|
+
project,
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: formatProjectUpdateContent(project, input) }],
|
|
76
|
+
details,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw new Error(formatToolError(error, "project_update failed"), { cause: error });
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const createProjectDeleteToolDefinition = ({ getProjectService, }) => ({
|
|
85
|
+
name: "project_delete",
|
|
86
|
+
label: "Project Delete",
|
|
87
|
+
description: "Delete a plain project record by explicit project ID.",
|
|
88
|
+
promptSnippet: "Delete a plain project record by explicit project ID.",
|
|
89
|
+
promptGuidelines: [
|
|
90
|
+
"Use this tool for plain project deletion in normal chat.",
|
|
91
|
+
"Do not use it for repository unregister or integration-binding changes.",
|
|
92
|
+
],
|
|
93
|
+
parameters: ProjectDeleteParams,
|
|
94
|
+
async execute(_toolCallId, params) {
|
|
95
|
+
const projectId = normalizeRequiredText(params.projectId, "projectId");
|
|
96
|
+
try {
|
|
97
|
+
const projectService = await getProjectService();
|
|
98
|
+
const project = await projectService.deleteProject(projectId);
|
|
99
|
+
const details = {
|
|
100
|
+
kind: "project_delete",
|
|
101
|
+
projectId,
|
|
102
|
+
found: true,
|
|
103
|
+
deleted: true,
|
|
104
|
+
project,
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: formatProjectDeleteContent(details) }],
|
|
108
|
+
details,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (isProjectNotFoundError(error)) {
|
|
113
|
+
const details = {
|
|
114
|
+
kind: "project_delete",
|
|
115
|
+
projectId,
|
|
116
|
+
found: false,
|
|
117
|
+
deleted: false,
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text", text: formatProjectDeleteContent(details) }],
|
|
121
|
+
details,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
throw new Error(formatToolError(error, "project_delete failed"), { cause: error });
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const registerProjectMutationTools = (pi, dependencies) => {
|
|
129
|
+
pi.registerTool(createProjectCreateToolDefinition(dependencies));
|
|
130
|
+
pi.registerTool(createProjectUpdateToolDefinition(dependencies));
|
|
131
|
+
pi.registerTool(createProjectDeleteToolDefinition(dependencies));
|
|
132
|
+
};
|
|
133
|
+
const normalizeCreateProjectInput = (params) => ({
|
|
134
|
+
name: normalizeRequiredText(params.name, "name"),
|
|
135
|
+
description: normalizeOptionalDescription(params, "description"),
|
|
136
|
+
priority: params.priority,
|
|
137
|
+
});
|
|
138
|
+
const normalizeUpdateProjectInput = (params) => {
|
|
139
|
+
const input = {
|
|
140
|
+
projectId: normalizeRequiredText(params.projectId, "projectId"),
|
|
141
|
+
status: params.status,
|
|
142
|
+
priority: params.priority,
|
|
143
|
+
};
|
|
144
|
+
if (hasOwn(params, "name")) {
|
|
145
|
+
input.name = normalizeRequiredText(params.name ?? "", "name");
|
|
146
|
+
}
|
|
147
|
+
if (hasOwn(params, "description")) {
|
|
148
|
+
input.description = normalizeNullableText(params.description);
|
|
149
|
+
}
|
|
150
|
+
if (input.name === undefined &&
|
|
151
|
+
input.status === undefined &&
|
|
152
|
+
input.priority === undefined &&
|
|
153
|
+
!hasOwn(input, "description")) {
|
|
154
|
+
throw new Error("project_update requires at least one supported field: name, description, status, or priority");
|
|
155
|
+
}
|
|
156
|
+
return input;
|
|
157
|
+
};
|
|
158
|
+
const normalizeOptionalDescription = (params, fieldName) => {
|
|
159
|
+
if (!hasOwn(params, fieldName)) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return normalizeNullableText(params[fieldName]);
|
|
163
|
+
};
|
|
164
|
+
const normalizeRequiredText = (value, fieldName) => {
|
|
165
|
+
const trimmedValue = value.trim();
|
|
166
|
+
if (trimmedValue.length === 0) {
|
|
167
|
+
throw new Error(`${fieldName} is required`);
|
|
168
|
+
}
|
|
169
|
+
return trimmedValue;
|
|
170
|
+
};
|
|
171
|
+
const normalizeNullableText = (value) => {
|
|
172
|
+
const trimmedValue = value?.trim() ?? "";
|
|
173
|
+
return trimmedValue.length > 0 ? trimmedValue : null;
|
|
174
|
+
};
|
|
175
|
+
const hasOwn = (value, property) => Object.prototype.hasOwnProperty.call(value, property);
|
|
176
|
+
const isProjectNotFoundError = (error) => error instanceof ToduProjectServiceError && error.causeCode === "not-found";
|
|
177
|
+
const formatProjectCreateContent = (project) => [
|
|
178
|
+
`Created project ${project.id}: ${project.name}`,
|
|
179
|
+
`Status: ${project.status}`,
|
|
180
|
+
`Priority: ${project.priority}`,
|
|
181
|
+
].join("\n");
|
|
182
|
+
const formatProjectUpdateContent = (project, input) => {
|
|
183
|
+
const changedFields = [
|
|
184
|
+
input.name !== undefined ? `name=${JSON.stringify(input.name)}` : null,
|
|
185
|
+
input.status !== undefined ? `status=${input.status}` : null,
|
|
186
|
+
input.priority !== undefined ? `priority=${input.priority}` : null,
|
|
187
|
+
hasOwn(input, "description")
|
|
188
|
+
? `description=${input.description === null ? "cleared" : "updated"}`
|
|
189
|
+
: null,
|
|
190
|
+
].filter((value) => value !== null);
|
|
191
|
+
return [
|
|
192
|
+
`Updated project ${project.id}: ${project.name}`,
|
|
193
|
+
`Changes: ${changedFields.join(", ")}`,
|
|
194
|
+
].join("\n");
|
|
195
|
+
};
|
|
196
|
+
const formatProjectDeleteContent = (details) => details.found
|
|
197
|
+
? `Deleted project ${details.projectId}.`
|
|
198
|
+
: `Project not found: ${details.projectId}`;
|
|
199
|
+
const formatToolError = (error, prefix) => {
|
|
200
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
201
|
+
return `${prefix}: ${error.message}`;
|
|
202
|
+
}
|
|
203
|
+
return prefix;
|
|
204
|
+
};
|
|
205
|
+
export { createProjectCreateToolDefinition, createProjectDeleteToolDefinition, createProjectUpdateToolDefinition, normalizeCreateProjectInput, normalizeUpdateProjectInput, registerProjectMutationTools, };
|