@critical-path/mcp 0.18.1 → 0.19.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/CHANGELOG.md +17 -0
- package/README.md +70 -25
- package/dist/bin/args.d.ts +8 -0
- package/dist/bin/args.d.ts.map +1 -1
- package/dist/bin/args.js +20 -0
- package/dist/bin/args.js.map +1 -1
- package/dist/bin/cli.js +37 -3
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/skills.d.ts +15 -0
- package/dist/bin/skills.d.ts.map +1 -0
- package/dist/bin/skills.js +28 -0
- package/dist/bin/skills.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +27 -13
- package/dist/server/index.js.map +1 -1
- package/dist/skills/catalog.generated.d.ts +3 -0
- package/dist/skills/catalog.generated.d.ts.map +1 -0
- package/dist/skills/catalog.generated.js +33 -0
- package/dist/skills/catalog.generated.js.map +1 -0
- package/dist/skills/index.d.ts +13 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +7 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/tools/definitions.d.ts +82 -8
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +296 -52
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/instructions.d.ts +10 -0
- package/dist/tools/instructions.d.ts.map +1 -0
- package/dist/tools/instructions.js +48 -0
- package/dist/tools/instructions.js.map +1 -0
- package/dist/tools/outputs.d.ts +2311 -0
- package/dist/tools/outputs.d.ts.map +1 -0
- package/dist/tools/outputs.js +184 -0
- package/dist/tools/outputs.js.map +1 -0
- package/dist/web/index.d.ts +16 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +13 -12
- package/dist/web/index.js.map +1 -1
- package/package.json +11 -6
- package/skills/analyze-schedule/SKILL.md +42 -0
- package/skills/investigate-task/SKILL.md +41 -0
- package/skills/plan-capacity/SKILL.md +32 -0
- package/skills/plan-project/SKILL.md +45 -0
- package/skills/report-status/SKILL.md +46 -0
- package/skills/triage-tasks/SKILL.md +41 -0
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
3
|
+
import { ProjectOutput, TaskOutput, TaskWithDependenciesOutput, DependencyOutput, DeliverableOutput, CommentOutput, CriticalPathOutput, PortfolioCriticalPathOutput, TimelineLadderOutput, TaskLadderOutput, TaskMetricsOutput, TaskProgressHistoryOutput, WorkloadDistributionOutput, SkillOutput } from './outputs.js';
|
|
4
|
+
import { SKILLS, getSkill } from '../skills/index.js';
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Groups of related tools. Hosts expose only the groups an agent needs (`toolsets` option), which
|
|
7
|
+
* keeps the tool list short and tool choice accurate.
|
|
8
|
+
*/
|
|
9
|
+
export const TOOLSETS = {
|
|
10
|
+
core: 'Projects, tasks and task dependencies: find, read, create, update and delete',
|
|
11
|
+
collaboration: 'Deliverables (milestones) and task comments',
|
|
12
|
+
scheduling: 'Critical path, portfolio scheduling and workload/capacity',
|
|
13
|
+
analytics: 'Project status ladder, task metrics, earned value and progress history',
|
|
14
|
+
guidance: 'Skills: step-by-step playbooks for multi-step jobs'
|
|
15
|
+
};
|
|
16
|
+
function toJsonSchema(schema) {
|
|
17
|
+
const { $schema: _schema, ...json } = zodToJsonSchema(schema, { $refStrategy: 'none' });
|
|
18
|
+
return json;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Declares a tool, generating its JSON Schema `inputSchema` and `outputSchema` from the zod schemas
|
|
22
|
+
* so the schemas the model sees and the ones values are checked against cannot drift apart.
|
|
6
23
|
*/
|
|
7
24
|
export function defineTool(spec) {
|
|
8
|
-
const
|
|
9
|
-
return {
|
|
25
|
+
const readOnlyHint = spec.annotations?.readOnlyHint ?? false;
|
|
26
|
+
return {
|
|
27
|
+
...spec,
|
|
28
|
+
inputSchema: toJsonSchema(spec.zodSchema),
|
|
29
|
+
outputSchema: toJsonSchema(spec.outputZodSchema),
|
|
30
|
+
annotations: {
|
|
31
|
+
readOnlyHint,
|
|
32
|
+
destructiveHint: spec.annotations?.destructiveHint ?? false,
|
|
33
|
+
idempotentHint: spec.annotations?.idempotentHint ?? readOnlyHint,
|
|
34
|
+
openWorldHint: spec.annotations?.openWorldHint ?? false
|
|
35
|
+
}
|
|
36
|
+
};
|
|
10
37
|
}
|
|
11
38
|
export class ToolArgumentError extends Error {
|
|
12
39
|
constructor(toolName, details) {
|
|
@@ -37,17 +64,21 @@ export async function resolveBackend(target) {
|
|
|
37
64
|
export const listProjectsTool = defineTool({
|
|
38
65
|
name: 'list_projects',
|
|
39
66
|
title: 'List Projects',
|
|
40
|
-
description: '
|
|
67
|
+
description: 'List every project you can read, with its id, name and key. Start here to find a projectId; never guess ids. Returns { projects }.',
|
|
68
|
+
toolset: 'core',
|
|
69
|
+
outputZodSchema: z.object({ projects: z.array(ProjectOutput) }),
|
|
41
70
|
zodSchema: z.object({}),
|
|
42
71
|
annotations: { readOnlyHint: true },
|
|
43
72
|
execute: async (_args, target) => {
|
|
44
|
-
return target.getProjects();
|
|
73
|
+
return { projects: await target.getProjects() };
|
|
45
74
|
}
|
|
46
75
|
});
|
|
47
76
|
export const getProjectTool = defineTool({
|
|
48
77
|
name: 'get_project',
|
|
49
78
|
title: 'Get Project',
|
|
50
|
-
description: 'Get
|
|
79
|
+
description: 'Get one project by id: name, key, workflow statuses, custom field definitions and calendar. Use it before creating or moving tasks to learn the valid status keys and custom fields. Returns the project. For its tasks use `list_tasks`.',
|
|
80
|
+
toolset: 'core',
|
|
81
|
+
outputZodSchema: ProjectOutput,
|
|
51
82
|
zodSchema: z.object({
|
|
52
83
|
id: z.string().describe('The project ID')
|
|
53
84
|
}),
|
|
@@ -59,7 +90,9 @@ export const getProjectTool = defineTool({
|
|
|
59
90
|
export const createProjectTool = defineTool({
|
|
60
91
|
name: 'create_project',
|
|
61
92
|
title: 'Create Project',
|
|
62
|
-
description: 'Create a new project
|
|
93
|
+
description: 'Create a new, empty project. Use only when the user wants a new project; to add work to an existing one use `create_task`. Returns the created project with its id.',
|
|
94
|
+
toolset: 'core',
|
|
95
|
+
outputZodSchema: ProjectOutput,
|
|
63
96
|
zodSchema: z.object({
|
|
64
97
|
name: z.string().describe('Name of the project'),
|
|
65
98
|
description: z.string().optional().describe('Description of the project'),
|
|
@@ -73,7 +106,9 @@ export const createProjectTool = defineTool({
|
|
|
73
106
|
export const listTasksTool = defineTool({
|
|
74
107
|
name: 'list_tasks',
|
|
75
108
|
title: 'List Tasks',
|
|
76
|
-
description: '
|
|
109
|
+
description: 'Find tasks, oldest first, filtered by project, status, priority, assignee, iteration, deliverable, container or parent task (\'none\' for top-level tasks only). Use it for questions like "what is blocked / assigned to X / in progress" and to look up task ids. Returns { tasks, nextCursor }; pass nextCursor back as cursor for the next page. For one task\'s full record use `get_task`.',
|
|
110
|
+
toolset: 'core',
|
|
111
|
+
outputZodSchema: z.object({ tasks: z.array(TaskOutput), nextCursor: z.string().nullable().optional() }),
|
|
77
112
|
zodSchema: z.object({
|
|
78
113
|
projectId: z.string().optional().describe('Filter by project ID'),
|
|
79
114
|
status: z.string().optional().describe('Filter by task status key'),
|
|
@@ -106,19 +141,60 @@ export const listTasksTool = defineTool({
|
|
|
106
141
|
export const getTaskTool = defineTool({
|
|
107
142
|
name: 'get_task',
|
|
108
143
|
title: 'Get Task',
|
|
109
|
-
description: 'Get task
|
|
144
|
+
description: 'Get one task by id: status, priority, assignee, estimate, logged hours, dates, blocked state and custom fields. Set includeDependencies to also see which tasks it waits for and which wait for it. Returns the task, with { dependencies: { dependsOn, dependents } } when asked. For its discussion use `list_comments`; for progress and variance use `get_task_metrics`.',
|
|
145
|
+
toolset: 'core',
|
|
146
|
+
outputZodSchema: TaskWithDependenciesOutput,
|
|
110
147
|
zodSchema: z.object({
|
|
111
|
-
id: z.string().describe('The task ID')
|
|
148
|
+
id: z.string().describe('The task ID'),
|
|
149
|
+
includeDependencies: z
|
|
150
|
+
.boolean()
|
|
151
|
+
.optional()
|
|
152
|
+
.describe('Also return the tasks this one waits for (dependsOn) and the tasks waiting for it (dependents)')
|
|
112
153
|
}),
|
|
113
154
|
annotations: { readOnlyHint: true },
|
|
114
155
|
execute: async (args, target) => {
|
|
115
|
-
|
|
156
|
+
const task = await target.getTask(args.id);
|
|
157
|
+
if (!task || !args.includeDependencies)
|
|
158
|
+
return task;
|
|
159
|
+
const graph = await getDependencyGraph(target, args.id);
|
|
160
|
+
const related = new Map([...graph.upstreamTasks, ...graph.downstreamTasks].map((t) => [t.id, t]));
|
|
161
|
+
const dependsOn = [];
|
|
162
|
+
const dependents = [];
|
|
163
|
+
for (const dependency of graph.dependencies) {
|
|
164
|
+
const { waiting, waitedFor } = dependencyDirection(dependency);
|
|
165
|
+
const otherId = waiting === args.id ? waitedFor : waiting;
|
|
166
|
+
const other = related.get(otherId);
|
|
167
|
+
// Tasks the caller cannot read are left out.
|
|
168
|
+
if (!other || otherId === args.id)
|
|
169
|
+
continue;
|
|
170
|
+
const link = { dependencyId: dependency.id, taskId: other.id, title: other.title, status: other.status, type: linkType(dependency) };
|
|
171
|
+
(waiting === args.id ? dependsOn : dependents).push(link);
|
|
172
|
+
}
|
|
173
|
+
return { ...task, dependencies: { dependsOn, dependents } };
|
|
116
174
|
}
|
|
117
175
|
});
|
|
176
|
+
/**
|
|
177
|
+
* Which task waits for which. A `blocked_by` record is stored the other way round: its
|
|
178
|
+
* `dependsOnTaskId` waits for its `taskId` (see the scheduler in `@critical-path/core`).
|
|
179
|
+
*/
|
|
180
|
+
function dependencyDirection(dependency) {
|
|
181
|
+
return dependency.type === 'blocked_by'
|
|
182
|
+
? { waiting: dependency.dependsOnTaskId, waitedFor: dependency.taskId }
|
|
183
|
+
: { waiting: dependency.taskId, waitedFor: dependency.dependsOnTaskId };
|
|
184
|
+
}
|
|
185
|
+
const linkType = (dependency) => (dependency.type === 'relates_to' ? 'relates_to' : 'blocking');
|
|
186
|
+
async function getDependencyGraph(target, taskId) {
|
|
187
|
+
const { isEngine } = await resolveBackend(target);
|
|
188
|
+
return isEngine
|
|
189
|
+
? target.getTaskDependencyGraph(taskId)
|
|
190
|
+
: target.getTaskDependencies(taskId);
|
|
191
|
+
}
|
|
118
192
|
export const createTaskTool = defineTool({
|
|
119
193
|
name: 'create_task',
|
|
120
194
|
title: 'Create Task',
|
|
121
|
-
description: 'Create a
|
|
195
|
+
description: 'Create a task in a project. Check with `list_tasks` first so you do not add a duplicate. Without a status the task starts in the project\'s default workflow status. Returns the created task with its id.',
|
|
196
|
+
toolset: 'core',
|
|
197
|
+
outputZodSchema: TaskOutput,
|
|
122
198
|
zodSchema: z.object({
|
|
123
199
|
projectId: z.string().optional().describe('Project ID (inferred from active view in browser if omitted)'),
|
|
124
200
|
title: z.string().describe('Title of the task'),
|
|
@@ -142,7 +218,7 @@ export const createTaskTool = defineTool({
|
|
|
142
218
|
execute: async (args, target, ambientContext) => {
|
|
143
219
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
144
220
|
if (!projectId) {
|
|
145
|
-
throw new Error('projectId is required to create a task, but none was provided or active in context.');
|
|
221
|
+
throw new Error('projectId is required to create a task, but none was provided or active in context. Find one with `list_projects`.');
|
|
146
222
|
}
|
|
147
223
|
const input = {
|
|
148
224
|
projectId,
|
|
@@ -166,28 +242,31 @@ export const createTaskTool = defineTool({
|
|
|
166
242
|
export const updateTaskTool = defineTool({
|
|
167
243
|
name: 'update_task',
|
|
168
244
|
title: 'Update Task',
|
|
169
|
-
description: '
|
|
245
|
+
description: 'Change an existing task: title, description, status, priority, assignee, due date, estimate, logged hours, allocation, blocked state, tags or custom fields. Only the fields you pass change. Use it to move a task through its workflow or re-plan it. Returns the updated task.',
|
|
246
|
+
toolset: 'core',
|
|
247
|
+
outputZodSchema: TaskOutput,
|
|
170
248
|
zodSchema: z.object({
|
|
171
249
|
id: z.string().describe('The task ID'),
|
|
172
|
-
title: z.string().optional(),
|
|
173
|
-
description: z.string().optional(),
|
|
174
|
-
status: z.string().optional(),
|
|
175
|
-
priority: z.enum(['urgent', 'high', 'medium', 'low', 'none']).optional(),
|
|
176
|
-
assigneeId: z.string().optional(),
|
|
177
|
-
dueDate: z.string().optional(),
|
|
178
|
-
tags: z.array(z.string()).optional(),
|
|
179
|
-
estimatedHours: z.number().optional(),
|
|
250
|
+
title: z.string().optional().describe('New title'),
|
|
251
|
+
description: z.string().optional().describe('New description (markdown)'),
|
|
252
|
+
status: z.string().optional().describe("Status key from the project's workflow (see `get_project`)"),
|
|
253
|
+
priority: z.enum(['urgent', 'high', 'medium', 'low', 'none']).optional().describe('New priority'),
|
|
254
|
+
assigneeId: z.string().optional().describe('User ID to assign the task to'),
|
|
255
|
+
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
256
|
+
tags: z.array(z.string()).optional().describe('Replaces all tags'),
|
|
257
|
+
estimatedHours: z.number().optional().describe('Estimated effort in hours'),
|
|
180
258
|
allocation: z
|
|
181
259
|
.number()
|
|
182
260
|
.gt(0)
|
|
183
261
|
.max(1)
|
|
184
262
|
.optional()
|
|
185
263
|
.describe("Share of the assignee's time the task takes, in (0, 1]; e.g. 0.5 for half-time. Estimates are effort, so a 0.5 task takes twice as long"),
|
|
186
|
-
loggedHours: z.number().optional(),
|
|
264
|
+
loggedHours: z.number().optional().describe('Total hours worked so far'),
|
|
187
265
|
isBlocked: z.boolean().optional().describe('Whether the task is blocked'),
|
|
188
266
|
blockedReason: z.string().optional().describe('Reason why the task is blocked'),
|
|
189
267
|
customFields: z.record(z.unknown()).optional().describe('Custom field values as a JSON object (validated against the project\'s field definitions for tasks)')
|
|
190
268
|
}),
|
|
269
|
+
annotations: { idempotentHint: true },
|
|
191
270
|
execute: async (args, target) => {
|
|
192
271
|
const { id, ...updates } = args;
|
|
193
272
|
return target.updateTask(id, updates);
|
|
@@ -196,50 +275,113 @@ export const updateTaskTool = defineTool({
|
|
|
196
275
|
export const deleteTaskTool = defineTool({
|
|
197
276
|
name: 'delete_task',
|
|
198
277
|
title: 'Delete Task',
|
|
199
|
-
description: '
|
|
278
|
+
description: 'Permanently delete a task by id. This cannot be undone: confirm with the user first, and prefer `update_task` with a canceled status when the history should be kept. Returns { success }.',
|
|
279
|
+
toolset: 'core',
|
|
280
|
+
outputZodSchema: z.object({ success: z.boolean() }),
|
|
200
281
|
zodSchema: z.object({
|
|
201
282
|
id: z.string().describe('The task ID to delete')
|
|
202
283
|
}),
|
|
203
|
-
annotations: {
|
|
284
|
+
annotations: { destructiveHint: true, idempotentHint: true },
|
|
204
285
|
execute: async (args, target) => {
|
|
205
286
|
const success = await target.deleteTask(args.id);
|
|
206
287
|
return { success };
|
|
207
288
|
}
|
|
208
289
|
});
|
|
290
|
+
export const addDependencyTool = defineTool({
|
|
291
|
+
name: 'add_dependency',
|
|
292
|
+
title: 'Add Dependency',
|
|
293
|
+
description: 'Record that a task cannot start until another task finishes, so the critical path schedules them in order. Use it when planning or re-sequencing work. Adding a link that already exists returns it; a link that would make a loop is refused. Returns the dependency { id, taskId, dependsOnTaskId, type }. To undo use `remove_dependency`.',
|
|
294
|
+
toolset: 'core',
|
|
295
|
+
outputZodSchema: DependencyOutput,
|
|
296
|
+
zodSchema: z.object({
|
|
297
|
+
taskId: z.string().describe('The task that waits'),
|
|
298
|
+
dependsOnTaskId: z.string().describe('The task that must finish first'),
|
|
299
|
+
type: z
|
|
300
|
+
.enum(['blocking', 'relates_to'])
|
|
301
|
+
.optional()
|
|
302
|
+
.describe("'blocking' (default) affects the schedule; 'relates_to' only links the tasks for reference")
|
|
303
|
+
}),
|
|
304
|
+
annotations: { idempotentHint: true },
|
|
305
|
+
execute: async (args, target) => {
|
|
306
|
+
const type = args.type ?? 'blocking';
|
|
307
|
+
const { isEngine } = await resolveBackend(target);
|
|
308
|
+
if (isEngine) {
|
|
309
|
+
return target.addDependency({ taskId: args.taskId, dependsOnTaskId: args.dependsOnTaskId, type });
|
|
310
|
+
}
|
|
311
|
+
return target.addDependency(args.taskId, { dependsOnTaskId: args.dependsOnTaskId, type });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
export const removeDependencyTool = defineTool({
|
|
315
|
+
name: 'remove_dependency',
|
|
316
|
+
title: 'Remove Dependency',
|
|
317
|
+
description: 'Stop a task waiting for another task, for example when the order was wrong or the work no longer depends on it. Identify the link by its two task ids, as given to `add_dependency`. Removing a link that does not exist is not an error. Returns { removed, dependencyIds } with the number of links removed.',
|
|
318
|
+
toolset: 'core',
|
|
319
|
+
outputZodSchema: z.object({
|
|
320
|
+
removed: z.number().describe('How many dependency records were removed (0 if none matched)'),
|
|
321
|
+
dependencyIds: z.array(z.string()).describe('Ids of the removed records')
|
|
322
|
+
}),
|
|
323
|
+
zodSchema: z.object({
|
|
324
|
+
taskId: z.string().describe('The task that waits'),
|
|
325
|
+
dependsOnTaskId: z.string().describe('The task it waits for'),
|
|
326
|
+
type: z.enum(['blocking', 'relates_to']).optional().describe('Remove only links of this type (default: both)')
|
|
327
|
+
}),
|
|
328
|
+
annotations: { idempotentHint: true },
|
|
329
|
+
execute: async (args, target) => {
|
|
330
|
+
const graph = await getDependencyGraph(target, args.taskId);
|
|
331
|
+
const matches = graph.dependencies.filter((dependency) => {
|
|
332
|
+
const { waiting, waitedFor } = dependencyDirection(dependency);
|
|
333
|
+
return waiting === args.taskId && waitedFor === args.dependsOnTaskId && (!args.type || linkType(dependency) === args.type);
|
|
334
|
+
});
|
|
335
|
+
const { isEngine } = await resolveBackend(target);
|
|
336
|
+
const dependencyIds = [];
|
|
337
|
+
for (const dependency of matches) {
|
|
338
|
+
const removed = isEngine
|
|
339
|
+
? await target.removeDependency(dependency.id)
|
|
340
|
+
: await target.removeDependency(dependency.taskId, dependency.id);
|
|
341
|
+
if (removed)
|
|
342
|
+
dependencyIds.push(dependency.id);
|
|
343
|
+
}
|
|
344
|
+
return { removed: dependencyIds.length, dependencyIds };
|
|
345
|
+
}
|
|
346
|
+
});
|
|
209
347
|
export const listDeliverablesTool = defineTool({
|
|
210
348
|
name: 'list_deliverables',
|
|
211
349
|
title: 'List Deliverables',
|
|
212
|
-
description: 'List deliverables
|
|
350
|
+
description: 'List a project\'s deliverables: milestones that group tasks toward a dated outcome. Use it to see what is due and which tasks feed each milestone. Returns { deliverables }.',
|
|
351
|
+
toolset: 'collaboration',
|
|
352
|
+
outputZodSchema: z.object({ deliverables: z.array(DeliverableOutput) }),
|
|
213
353
|
zodSchema: z.object({
|
|
214
|
-
projectId: z.string().optional().describe('Project ID')
|
|
354
|
+
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)')
|
|
215
355
|
}),
|
|
216
356
|
annotations: { readOnlyHint: true },
|
|
217
357
|
execute: async (args, target, ambientContext) => {
|
|
218
358
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
219
359
|
if (!projectId)
|
|
220
|
-
throw new Error('projectId is required to list deliverables.');
|
|
360
|
+
throw new Error('projectId is required to list deliverables. Find one with `list_projects`.');
|
|
221
361
|
if ('getDeliverables' in target && typeof target.getDeliverables === 'function') {
|
|
222
|
-
return target.getDeliverables(projectId);
|
|
362
|
+
return { deliverables: await target.getDeliverables(projectId) };
|
|
223
363
|
}
|
|
224
|
-
return [];
|
|
364
|
+
return { deliverables: [] };
|
|
225
365
|
}
|
|
226
366
|
});
|
|
227
367
|
export const createDeliverableTool = defineTool({
|
|
228
368
|
name: 'create_deliverable',
|
|
229
369
|
title: 'Create Deliverable',
|
|
230
|
-
description: 'Create a milestone
|
|
370
|
+
description: 'Create a deliverable (milestone) in a project, optionally linking existing tasks by id. Use it when planning a dated outcome that several tasks build toward. Returns the created deliverable.',
|
|
371
|
+
toolset: 'collaboration',
|
|
372
|
+
outputZodSchema: DeliverableOutput,
|
|
231
373
|
zodSchema: z.object({
|
|
232
|
-
projectId: z.string().optional(),
|
|
233
|
-
title: z.string(),
|
|
234
|
-
description: z.string().optional(),
|
|
235
|
-
dueDate: z.string().optional(),
|
|
236
|
-
taskIds: z.array(z.string()).optional(),
|
|
374
|
+
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
375
|
+
title: z.string().describe('Name of the deliverable'),
|
|
376
|
+
description: z.string().optional().describe('What the deliverable is and how it is accepted'),
|
|
377
|
+
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
378
|
+
taskIds: z.array(z.string()).optional().describe('IDs of existing tasks that feed this deliverable'),
|
|
237
379
|
customFields: z.record(z.unknown()).optional().describe('Custom field values as a JSON object (validated against the project\'s field definitions for tasks)')
|
|
238
380
|
}),
|
|
239
381
|
execute: async (args, target, ambientContext) => {
|
|
240
382
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
241
383
|
if (!projectId)
|
|
242
|
-
throw new Error('projectId is required to create a deliverable.');
|
|
384
|
+
throw new Error('projectId is required to create a deliverable. Find one with `list_projects`.');
|
|
243
385
|
if ('createDeliverable' in target && typeof target.createDeliverable === 'function') {
|
|
244
386
|
return target.createDeliverable({
|
|
245
387
|
...args,
|
|
@@ -252,22 +394,26 @@ export const createDeliverableTool = defineTool({
|
|
|
252
394
|
export const listCommentsTool = defineTool({
|
|
253
395
|
name: 'list_comments',
|
|
254
396
|
title: 'List Comments',
|
|
255
|
-
description: 'List comments
|
|
397
|
+
description: 'List the comments on a task. Use it to read the discussion, decisions or blockers recorded there. Returns { comments }.',
|
|
398
|
+
toolset: 'collaboration',
|
|
399
|
+
outputZodSchema: z.object({ comments: z.array(CommentOutput) }),
|
|
256
400
|
zodSchema: z.object({
|
|
257
401
|
taskId: z.string().describe('The task ID')
|
|
258
402
|
}),
|
|
259
403
|
annotations: { readOnlyHint: true },
|
|
260
404
|
execute: async (args, target) => {
|
|
261
405
|
if ('getComments' in target && typeof target.getComments === 'function') {
|
|
262
|
-
return target.getComments(args.taskId);
|
|
406
|
+
return { comments: await target.getComments(args.taskId) };
|
|
263
407
|
}
|
|
264
|
-
return [];
|
|
408
|
+
return { comments: [] };
|
|
265
409
|
}
|
|
266
410
|
});
|
|
267
411
|
export const addCommentTool = defineTool({
|
|
268
412
|
name: 'add_comment',
|
|
269
413
|
title: 'Add Comment',
|
|
270
|
-
description: '
|
|
414
|
+
description: 'Post a comment on a task, attributed to the identity this server runs as. Use it to record a decision, status note, question or the reasoning behind a change you made. Returns the created comment.',
|
|
415
|
+
toolset: 'collaboration',
|
|
416
|
+
outputZodSchema: CommentOutput,
|
|
271
417
|
zodSchema: z.object({
|
|
272
418
|
taskId: z.string().describe('The task ID'),
|
|
273
419
|
content: z.string().describe('Comment body/markdown'),
|
|
@@ -280,7 +426,9 @@ export const addCommentTool = defineTool({
|
|
|
280
426
|
export const calculatePortfolioCriticalPathTool = defineTool({
|
|
281
427
|
name: 'calculate_portfolio_critical_path',
|
|
282
428
|
title: 'Calculate Portfolio Critical Path',
|
|
283
|
-
description: '
|
|
429
|
+
description: 'Schedule several projects together (default: every project you can read) so people and teams shared between them are not double-booked. Use it for cross-project delivery dates and resource conflicts. Returns { projects, projectEndDate, overallocations } with one analysis per project. For a single project use `calculate_critical_path`.',
|
|
430
|
+
toolset: 'scheduling',
|
|
431
|
+
outputZodSchema: PortfolioCriticalPathOutput,
|
|
284
432
|
zodSchema: z.object({
|
|
285
433
|
projectIds: z.array(z.string()).optional().describe('Projects to include (default: every project you can read)'),
|
|
286
434
|
projectOrder: z.array(z.string()).optional().describe('Project ids in priority order for levelling; unlisted projects come last'),
|
|
@@ -305,7 +453,9 @@ export const calculatePortfolioCriticalPathTool = defineTool({
|
|
|
305
453
|
export const calculateCriticalPathTool = defineTool({
|
|
306
454
|
name: 'calculate_critical_path',
|
|
307
455
|
title: 'Calculate Critical Path',
|
|
308
|
-
description: '
|
|
456
|
+
description: 'Schedule one project with the Critical Path Method: which tasks drive the end date, each task\'s early and late start and finish, and how much it can slip (slack). Use it for "when will this finish?", "what is on the critical path?" or "can this task slip?". Set calendars \'assignee\' with levelResources so each person works on one task at a time. Returns { projectEndDate, criticalTaskIds, tasks, overallocations }. For several projects that share people use `calculate_portfolio_critical_path`.',
|
|
457
|
+
toolset: 'scheduling',
|
|
458
|
+
outputZodSchema: CriticalPathOutput,
|
|
309
459
|
zodSchema: z.object({
|
|
310
460
|
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
311
461
|
calendars: z
|
|
@@ -329,7 +479,7 @@ export const calculateCriticalPathTool = defineTool({
|
|
|
329
479
|
execute: async (args, target, ambientContext) => {
|
|
330
480
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
331
481
|
if (!projectId) {
|
|
332
|
-
throw new Error('projectId is required to calculate critical path.');
|
|
482
|
+
throw new Error('projectId is required to calculate the critical path. Find one with `list_projects`.');
|
|
333
483
|
}
|
|
334
484
|
const { calendars, levelResources, levelingPriority, effort } = args;
|
|
335
485
|
return target.calculateCriticalPath(projectId, { calendars, levelResources, levelingPriority, effort });
|
|
@@ -338,7 +488,9 @@ export const calculateCriticalPathTool = defineTool({
|
|
|
338
488
|
export const getTimelineLadderTool = defineTool({
|
|
339
489
|
name: 'get_timeline_ladder',
|
|
340
490
|
title: 'Get Timeline Ladder of Abstraction',
|
|
341
|
-
description: '
|
|
491
|
+
description: 'Project status at three zoom levels: \'macro\' phases with health (on_track, at_risk, blocked, overdue) and progress, \'standard\' task timeline with the critical path and dependencies, and \'concrete\' evidence of work (time logged, attachments) per task. Use it for project status reports; level \'macro\' gives a compact summary. Returns { macro, standard, concrete }. For a single task use `get_task_ladder`.',
|
|
492
|
+
toolset: 'analytics',
|
|
493
|
+
outputZodSchema: TimelineLadderOutput,
|
|
342
494
|
zodSchema: z.object({
|
|
343
495
|
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
344
496
|
level: z.enum(['macro', 'standard', 'concrete', 'all']).optional().describe('Abstraction level (macro, standard, concrete, or all)'),
|
|
@@ -349,7 +501,7 @@ export const getTimelineLadderTool = defineTool({
|
|
|
349
501
|
execute: async (args, target, ambientContext) => {
|
|
350
502
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
351
503
|
if (!projectId) {
|
|
352
|
-
throw new Error('projectId is required to get timeline ladder.');
|
|
504
|
+
throw new Error('projectId is required to get the timeline ladder. Find one with `list_projects`.');
|
|
353
505
|
}
|
|
354
506
|
return target.getTimelineLadder(projectId, {
|
|
355
507
|
level: args.level,
|
|
@@ -361,7 +513,9 @@ export const getTimelineLadderTool = defineTool({
|
|
|
361
513
|
export const getTaskLadderTool = defineTool({
|
|
362
514
|
name: 'get_task_ladder',
|
|
363
515
|
title: 'Get Task Ladder View',
|
|
364
|
-
description: '
|
|
516
|
+
description: 'One task in context: the phase it belongs to and that phase\'s health, its place in the schedule (dates, slack, critical or not) and evidence of work done. Use it to explain where a task fits and whether it puts its phase at risk. Returns { macroPhase, standard, concrete, metrics }. For the numbers alone use `get_task_metrics`.',
|
|
517
|
+
toolset: 'analytics',
|
|
518
|
+
outputZodSchema: TaskLadderOutput,
|
|
365
519
|
zodSchema: z.object({
|
|
366
520
|
taskId: z.string().describe('The task ID')
|
|
367
521
|
}),
|
|
@@ -373,7 +527,9 @@ export const getTaskLadderTool = defineTool({
|
|
|
373
527
|
export const getTaskMetricsTool = defineTool({
|
|
374
528
|
name: 'get_task_metrics',
|
|
375
529
|
title: 'Get Task Metrics',
|
|
376
|
-
description: '
|
|
530
|
+
description: 'Is a task on track? Progress percentage, actual against estimated hours, schedule variance, overdue flags and earned value (CPI, SPI). Use it to diagnose a late or over-budget task. Returns { progress, realityDelta, evm, inferredActuals, progressHistory }.',
|
|
531
|
+
toolset: 'analytics',
|
|
532
|
+
outputZodSchema: TaskMetricsOutput,
|
|
377
533
|
zodSchema: z.object({
|
|
378
534
|
taskId: z.string().describe('The task ID')
|
|
379
535
|
}),
|
|
@@ -385,7 +541,9 @@ export const getTaskMetricsTool = defineTool({
|
|
|
385
541
|
export const getTaskProgressHistoryTool = defineTool({
|
|
386
542
|
name: 'get_task_progress_history',
|
|
387
543
|
title: 'Get Task Progress History',
|
|
388
|
-
description: '
|
|
544
|
+
description: 'How a task\'s progress has moved over time: dated progress points and a curve shape (linear, s_curve, early_surge, late_rush, stalled). Use it to tell whether work is stalling or being rushed at the end. Returns { points, curveProfile }. `get_task_metrics` includes the same history alongside variance and earned value.',
|
|
545
|
+
toolset: 'analytics',
|
|
546
|
+
outputZodSchema: TaskProgressHistoryOutput,
|
|
389
547
|
zodSchema: z.object({
|
|
390
548
|
taskId: z.string().describe('The task ID')
|
|
391
549
|
}),
|
|
@@ -397,7 +555,9 @@ export const getTaskProgressHistoryTool = defineTool({
|
|
|
397
555
|
export const getWorkloadDistributionTool = defineTool({
|
|
398
556
|
name: 'get_workload_distribution',
|
|
399
557
|
title: 'Get Workload Distribution',
|
|
400
|
-
description: '
|
|
558
|
+
description: 'Hours of work per person, team, task type, priority or status in each day, week or month, with capacity and utilization. Use it for "who is overloaded?" and capacity planning; a utilizationRatio above 1 means more work than capacity. Omit projectId for the whole workspace. Returns { buckets, seriesKeys, seriesLabels, totalHours, averageUtilization }.',
|
|
559
|
+
toolset: 'scheduling',
|
|
560
|
+
outputZodSchema: WorkloadDistributionOutput,
|
|
401
561
|
zodSchema: z.object({
|
|
402
562
|
projectId: z.string().optional().describe('Project ID (optional, defaults to workspace-wide or ambient project)'),
|
|
403
563
|
startDate: z.string().optional().describe('Start date ISO (YYYY-MM-DD)'),
|
|
@@ -420,6 +580,27 @@ export const getWorkloadDistributionTool = defineTool({
|
|
|
420
580
|
});
|
|
421
581
|
}
|
|
422
582
|
});
|
|
583
|
+
export const getSkillTool = defineTool({
|
|
584
|
+
name: 'get_skill',
|
|
585
|
+
title: 'Get Skill',
|
|
586
|
+
description: 'Load a step-by-step playbook for a multi-step project management job, such as planning a project, triaging tasks, finding schedule risk, writing a status report, checking capacity or investigating a late task. Call it before starting such a job and follow its steps. Omit name to list the skills. Returns { name, description, content }, or { skills } without a name.',
|
|
587
|
+
toolset: 'guidance',
|
|
588
|
+
outputZodSchema: SkillOutput,
|
|
589
|
+
zodSchema: z.object({
|
|
590
|
+
name: z.string().optional().describe('Skill name, e.g. "plan-project"; omit to list all skills')
|
|
591
|
+
}),
|
|
592
|
+
annotations: { readOnlyHint: true },
|
|
593
|
+
execute: async (args) => {
|
|
594
|
+
if (!args.name) {
|
|
595
|
+
return { skills: SKILLS.map(({ name, description }) => ({ name, description })) };
|
|
596
|
+
}
|
|
597
|
+
const skill = getSkill(args.name);
|
|
598
|
+
if (!skill) {
|
|
599
|
+
throw new Error(`Skill "${args.name}" not found. Available skills: ${SKILLS.map((s) => s.name).join(', ')}.`);
|
|
600
|
+
}
|
|
601
|
+
return skill;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
423
604
|
export const ALL_TOOLS = [
|
|
424
605
|
listProjectsTool,
|
|
425
606
|
getProjectTool,
|
|
@@ -429,6 +610,8 @@ export const ALL_TOOLS = [
|
|
|
429
610
|
createTaskTool,
|
|
430
611
|
updateTaskTool,
|
|
431
612
|
deleteTaskTool,
|
|
613
|
+
addDependencyTool,
|
|
614
|
+
removeDependencyTool,
|
|
432
615
|
listDeliverablesTool,
|
|
433
616
|
createDeliverableTool,
|
|
434
617
|
listCommentsTool,
|
|
@@ -439,7 +622,68 @@ export const ALL_TOOLS = [
|
|
|
439
622
|
getTaskLadderTool,
|
|
440
623
|
getTaskMetricsTool,
|
|
441
624
|
getTaskProgressHistoryTool,
|
|
442
|
-
getWorkloadDistributionTool
|
|
625
|
+
getWorkloadDistributionTool,
|
|
626
|
+
getSkillTool
|
|
443
627
|
];
|
|
444
628
|
export const TOOL_MAP = new Map(ALL_TOOLS.map((tool) => [tool.name, tool]));
|
|
629
|
+
/**
|
|
630
|
+
* Picks the tools a host exposes. Unknown tool or toolset names throw, so a typo cannot silently
|
|
631
|
+
* leave an agent without the tools it was meant to have. `get_skill` follows the same rules as
|
|
632
|
+
* other tools: include the `guidance` toolset (or name it) to keep skills reachable.
|
|
633
|
+
*/
|
|
634
|
+
export function selectTools(selection = {}) {
|
|
635
|
+
const { tools, toolsets, readOnly } = selection;
|
|
636
|
+
const unknownTools = (tools ?? []).filter((name) => !TOOL_MAP.has(name));
|
|
637
|
+
if (unknownTools.length > 0) {
|
|
638
|
+
throw new Error(`Unknown Critical Path tool(s): ${unknownTools.join(', ')}. Known tools: ${ALL_TOOLS.map((t) => t.name).join(', ')}.`);
|
|
639
|
+
}
|
|
640
|
+
const unknownSets = (toolsets ?? []).filter((name) => !(name in TOOLSETS));
|
|
641
|
+
if (unknownSets.length > 0) {
|
|
642
|
+
throw new Error(`Unknown Critical Path toolset(s): ${unknownSets.join(', ')}. Known toolsets: ${Object.keys(TOOLSETS).join(', ')}.`);
|
|
643
|
+
}
|
|
644
|
+
const filtered = !tools && !toolsets;
|
|
645
|
+
return ALL_TOOLS.filter((tool) => (filtered || tools?.includes(tool.name) || toolsets?.includes(tool.toolset)) &&
|
|
646
|
+
(!readOnly || tool.annotations.readOnlyHint));
|
|
647
|
+
}
|
|
648
|
+
export class ToolNotFoundError extends Error {
|
|
649
|
+
constructor(message) {
|
|
650
|
+
super(message);
|
|
651
|
+
this.name = 'ToolNotFoundError';
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const ID_RECOVERY = 'Check the id: find valid project ids with `list_projects` and task ids with `list_tasks`. Ids are never guessable, and items you cannot read are reported as not found.';
|
|
655
|
+
/**
|
|
656
|
+
* Validates arguments, runs a tool and returns a JSON-safe object result. A missing record (a
|
|
657
|
+
* `null` result) becomes an error, and errors carry a hint for what to do next so the agent can
|
|
658
|
+
* recover on its own.
|
|
659
|
+
*/
|
|
660
|
+
export async function runTool(tool, args, target, ambientContext) {
|
|
661
|
+
try {
|
|
662
|
+
const result = await tool.execute(parseToolArgs(tool, args), target, ambientContext);
|
|
663
|
+
if (result === null || result === undefined) {
|
|
664
|
+
throw new ToolNotFoundError(`Nothing found for ${JSON.stringify(args ?? {})}.`);
|
|
665
|
+
}
|
|
666
|
+
// Round-trip through JSON: drops undefined fields and serializes dates, as a transport would.
|
|
667
|
+
return JSON.parse(JSON.stringify(result));
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
throw new Error(withRecoveryHint(tool, err));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function withRecoveryHint(tool, err) {
|
|
674
|
+
const message = err?.message || String(err);
|
|
675
|
+
if (err instanceof ToolArgumentError) {
|
|
676
|
+
return `${message}. Fix the arguments to match the input schema of "${tool.name}" and call it again.`;
|
|
677
|
+
}
|
|
678
|
+
if (err instanceof ToolNotFoundError || err?.name === 'NotFoundError' || err?.status === 404 || /not found/i.test(message)) {
|
|
679
|
+
return `${message} ${ID_RECOVERY}`;
|
|
680
|
+
}
|
|
681
|
+
if (err?.name === 'CircularDependencyError' || /circular/i.test(message)) {
|
|
682
|
+
return `${message} The tasks are already ordered the other way round. Check with \`get_task\` and includeDependencies, and ask the user which order is right rather than retrying.`;
|
|
683
|
+
}
|
|
684
|
+
if (err?.name === 'ForbiddenError' || err?.status === 403) {
|
|
685
|
+
return `${message} You do not have permission for this; tell the user rather than retrying.`;
|
|
686
|
+
}
|
|
687
|
+
return message;
|
|
688
|
+
}
|
|
445
689
|
//# sourceMappingURL=definitions.js.map
|