@critical-path/mcp 0.18.1 → 0.20.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 +37 -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 +86 -8
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +302 -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 +2443 -0
- package/dist/tools/outputs.d.ts.map +1 -0
- package/dist/tools/outputs.js +190 -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 +13 -8
- package/skills/analyze-schedule/SKILL.md +44 -0
- package/skills/investigate-task/SKILL.md +41 -0
- package/skills/plan-capacity/SKILL.md +32 -0
- package/skills/plan-project/SKILL.md +46 -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 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'),
|
|
@@ -127,6 +203,8 @@ export const createTaskTool = defineTool({
|
|
|
127
203
|
priority: z.enum(['urgent', 'high', 'medium', 'low', 'none']).optional().default('none').describe('Task priority'),
|
|
128
204
|
assigneeId: z.string().optional().describe('User ID to assign task to'),
|
|
129
205
|
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
206
|
+
deadline: z.string().optional().describe('Latest acceptable finish (YYYY-MM-DD for the end of that working day, or an ISO date-time). Critical path analysis reports a miss as negative slack'),
|
|
207
|
+
isMilestone: z.boolean().optional().describe('Mark the task as a milestone: a zero-duration checkpoint (release, sign-off) fed by dependencies; its estimate is ignored'),
|
|
130
208
|
tags: z.array(z.string()).optional().describe('Tags for categorization'),
|
|
131
209
|
estimatedHours: z.number().optional().describe('Estimated hours to complete'),
|
|
132
210
|
allocation: z
|
|
@@ -142,7 +220,7 @@ export const createTaskTool = defineTool({
|
|
|
142
220
|
execute: async (args, target, ambientContext) => {
|
|
143
221
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
144
222
|
if (!projectId) {
|
|
145
|
-
throw new Error('projectId is required to create a task, but none was provided or active in context.');
|
|
223
|
+
throw new Error('projectId is required to create a task, but none was provided or active in context. Find one with `list_projects`.');
|
|
146
224
|
}
|
|
147
225
|
const input = {
|
|
148
226
|
projectId,
|
|
@@ -153,6 +231,8 @@ export const createTaskTool = defineTool({
|
|
|
153
231
|
priority: args.priority || 'none',
|
|
154
232
|
assigneeId: args.assigneeId,
|
|
155
233
|
dueDate: args.dueDate,
|
|
234
|
+
deadline: args.deadline,
|
|
235
|
+
isMilestone: args.isMilestone,
|
|
156
236
|
tags: args.tags || [],
|
|
157
237
|
estimatedHours: args.estimatedHours,
|
|
158
238
|
customFields: args.customFields,
|
|
@@ -166,28 +246,33 @@ export const createTaskTool = defineTool({
|
|
|
166
246
|
export const updateTaskTool = defineTool({
|
|
167
247
|
name: 'update_task',
|
|
168
248
|
title: 'Update Task',
|
|
169
|
-
description: '
|
|
249
|
+
description: 'Change an existing task: title, description, status, priority, assignee, due date, deadline, milestone flag, 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.',
|
|
250
|
+
toolset: 'core',
|
|
251
|
+
outputZodSchema: TaskOutput,
|
|
170
252
|
zodSchema: z.object({
|
|
171
253
|
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
|
-
|
|
179
|
-
|
|
254
|
+
title: z.string().optional().describe('New title'),
|
|
255
|
+
description: z.string().optional().describe('New description (markdown)'),
|
|
256
|
+
status: z.string().optional().describe("Status key from the project's workflow (see `get_project`)"),
|
|
257
|
+
priority: z.enum(['urgent', 'high', 'medium', 'low', 'none']).optional().describe('New priority'),
|
|
258
|
+
assigneeId: z.string().optional().describe('User ID to assign the task to'),
|
|
259
|
+
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
260
|
+
deadline: z.string().optional().describe('Latest acceptable finish (YYYY-MM-DD for the end of that working day, or an ISO date-time). Critical path analysis reports a miss as negative slack'),
|
|
261
|
+
isMilestone: z.boolean().optional().describe('Mark the task as a milestone: a zero-duration checkpoint (release, sign-off) fed by dependencies; its estimate is ignored'),
|
|
262
|
+
tags: z.array(z.string()).optional().describe('Replaces all tags'),
|
|
263
|
+
estimatedHours: z.number().optional().describe('Estimated effort in hours'),
|
|
180
264
|
allocation: z
|
|
181
265
|
.number()
|
|
182
266
|
.gt(0)
|
|
183
267
|
.max(1)
|
|
184
268
|
.optional()
|
|
185
269
|
.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(),
|
|
270
|
+
loggedHours: z.number().optional().describe('Total hours worked so far'),
|
|
187
271
|
isBlocked: z.boolean().optional().describe('Whether the task is blocked'),
|
|
188
272
|
blockedReason: z.string().optional().describe('Reason why the task is blocked'),
|
|
189
273
|
customFields: z.record(z.unknown()).optional().describe('Custom field values as a JSON object (validated against the project\'s field definitions for tasks)')
|
|
190
274
|
}),
|
|
275
|
+
annotations: { idempotentHint: true },
|
|
191
276
|
execute: async (args, target) => {
|
|
192
277
|
const { id, ...updates } = args;
|
|
193
278
|
return target.updateTask(id, updates);
|
|
@@ -196,50 +281,113 @@ export const updateTaskTool = defineTool({
|
|
|
196
281
|
export const deleteTaskTool = defineTool({
|
|
197
282
|
name: 'delete_task',
|
|
198
283
|
title: 'Delete Task',
|
|
199
|
-
description: '
|
|
284
|
+
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 }.',
|
|
285
|
+
toolset: 'core',
|
|
286
|
+
outputZodSchema: z.object({ success: z.boolean() }),
|
|
200
287
|
zodSchema: z.object({
|
|
201
288
|
id: z.string().describe('The task ID to delete')
|
|
202
289
|
}),
|
|
203
|
-
annotations: {
|
|
290
|
+
annotations: { destructiveHint: true, idempotentHint: true },
|
|
204
291
|
execute: async (args, target) => {
|
|
205
292
|
const success = await target.deleteTask(args.id);
|
|
206
293
|
return { success };
|
|
207
294
|
}
|
|
208
295
|
});
|
|
296
|
+
export const addDependencyTool = defineTool({
|
|
297
|
+
name: 'add_dependency',
|
|
298
|
+
title: 'Add Dependency',
|
|
299
|
+
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`.',
|
|
300
|
+
toolset: 'core',
|
|
301
|
+
outputZodSchema: DependencyOutput,
|
|
302
|
+
zodSchema: z.object({
|
|
303
|
+
taskId: z.string().describe('The task that waits'),
|
|
304
|
+
dependsOnTaskId: z.string().describe('The task that must finish first'),
|
|
305
|
+
type: z
|
|
306
|
+
.enum(['blocking', 'relates_to'])
|
|
307
|
+
.optional()
|
|
308
|
+
.describe("'blocking' (default) affects the schedule; 'relates_to' only links the tasks for reference")
|
|
309
|
+
}),
|
|
310
|
+
annotations: { idempotentHint: true },
|
|
311
|
+
execute: async (args, target) => {
|
|
312
|
+
const type = args.type ?? 'blocking';
|
|
313
|
+
const { isEngine } = await resolveBackend(target);
|
|
314
|
+
if (isEngine) {
|
|
315
|
+
return target.addDependency({ taskId: args.taskId, dependsOnTaskId: args.dependsOnTaskId, type });
|
|
316
|
+
}
|
|
317
|
+
return target.addDependency(args.taskId, { dependsOnTaskId: args.dependsOnTaskId, type });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
export const removeDependencyTool = defineTool({
|
|
321
|
+
name: 'remove_dependency',
|
|
322
|
+
title: 'Remove Dependency',
|
|
323
|
+
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.',
|
|
324
|
+
toolset: 'core',
|
|
325
|
+
outputZodSchema: z.object({
|
|
326
|
+
removed: z.number().describe('How many dependency records were removed (0 if none matched)'),
|
|
327
|
+
dependencyIds: z.array(z.string()).describe('Ids of the removed records')
|
|
328
|
+
}),
|
|
329
|
+
zodSchema: z.object({
|
|
330
|
+
taskId: z.string().describe('The task that waits'),
|
|
331
|
+
dependsOnTaskId: z.string().describe('The task it waits for'),
|
|
332
|
+
type: z.enum(['blocking', 'relates_to']).optional().describe('Remove only links of this type (default: both)')
|
|
333
|
+
}),
|
|
334
|
+
annotations: { idempotentHint: true },
|
|
335
|
+
execute: async (args, target) => {
|
|
336
|
+
const graph = await getDependencyGraph(target, args.taskId);
|
|
337
|
+
const matches = graph.dependencies.filter((dependency) => {
|
|
338
|
+
const { waiting, waitedFor } = dependencyDirection(dependency);
|
|
339
|
+
return waiting === args.taskId && waitedFor === args.dependsOnTaskId && (!args.type || linkType(dependency) === args.type);
|
|
340
|
+
});
|
|
341
|
+
const { isEngine } = await resolveBackend(target);
|
|
342
|
+
const dependencyIds = [];
|
|
343
|
+
for (const dependency of matches) {
|
|
344
|
+
const removed = isEngine
|
|
345
|
+
? await target.removeDependency(dependency.id)
|
|
346
|
+
: await target.removeDependency(dependency.taskId, dependency.id);
|
|
347
|
+
if (removed)
|
|
348
|
+
dependencyIds.push(dependency.id);
|
|
349
|
+
}
|
|
350
|
+
return { removed: dependencyIds.length, dependencyIds };
|
|
351
|
+
}
|
|
352
|
+
});
|
|
209
353
|
export const listDeliverablesTool = defineTool({
|
|
210
354
|
name: 'list_deliverables',
|
|
211
355
|
title: 'List Deliverables',
|
|
212
|
-
description: 'List deliverables
|
|
356
|
+
description: 'List a project\'s deliverables: outputs that group the tasks producing them. Use it to see what is due and which tasks feed each deliverable. For dated checkpoints, look for milestone tasks (`isMilestone`). Returns { deliverables }.',
|
|
357
|
+
toolset: 'collaboration',
|
|
358
|
+
outputZodSchema: z.object({ deliverables: z.array(DeliverableOutput) }),
|
|
213
359
|
zodSchema: z.object({
|
|
214
|
-
projectId: z.string().optional().describe('Project ID')
|
|
360
|
+
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)')
|
|
215
361
|
}),
|
|
216
362
|
annotations: { readOnlyHint: true },
|
|
217
363
|
execute: async (args, target, ambientContext) => {
|
|
218
364
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
219
365
|
if (!projectId)
|
|
220
|
-
throw new Error('projectId is required to list deliverables.');
|
|
366
|
+
throw new Error('projectId is required to list deliverables. Find one with `list_projects`.');
|
|
221
367
|
if ('getDeliverables' in target && typeof target.getDeliverables === 'function') {
|
|
222
|
-
return target.getDeliverables(projectId);
|
|
368
|
+
return { deliverables: await target.getDeliverables(projectId) };
|
|
223
369
|
}
|
|
224
|
-
return [];
|
|
370
|
+
return { deliverables: [] };
|
|
225
371
|
}
|
|
226
372
|
});
|
|
227
373
|
export const createDeliverableTool = defineTool({
|
|
228
374
|
name: 'create_deliverable',
|
|
229
375
|
title: 'Create Deliverable',
|
|
230
|
-
description: 'Create a
|
|
376
|
+
description: 'Create a deliverable in a project, optionally linking existing tasks by id. Use it for an output (a document, a build, an asset) that several tasks produce. For a dated checkpoint the schedule should enforce, create a task with `isMilestone: true` and a `deadline` instead. Returns the created deliverable.',
|
|
377
|
+
toolset: 'collaboration',
|
|
378
|
+
outputZodSchema: DeliverableOutput,
|
|
231
379
|
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(),
|
|
380
|
+
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
381
|
+
title: z.string().describe('Name of the deliverable'),
|
|
382
|
+
description: z.string().optional().describe('What the deliverable is and how it is accepted'),
|
|
383
|
+
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
384
|
+
taskIds: z.array(z.string()).optional().describe('IDs of existing tasks that feed this deliverable'),
|
|
237
385
|
customFields: z.record(z.unknown()).optional().describe('Custom field values as a JSON object (validated against the project\'s field definitions for tasks)')
|
|
238
386
|
}),
|
|
239
387
|
execute: async (args, target, ambientContext) => {
|
|
240
388
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
241
389
|
if (!projectId)
|
|
242
|
-
throw new Error('projectId is required to create a deliverable.');
|
|
390
|
+
throw new Error('projectId is required to create a deliverable. Find one with `list_projects`.');
|
|
243
391
|
if ('createDeliverable' in target && typeof target.createDeliverable === 'function') {
|
|
244
392
|
return target.createDeliverable({
|
|
245
393
|
...args,
|
|
@@ -252,22 +400,26 @@ export const createDeliverableTool = defineTool({
|
|
|
252
400
|
export const listCommentsTool = defineTool({
|
|
253
401
|
name: 'list_comments',
|
|
254
402
|
title: 'List Comments',
|
|
255
|
-
description: 'List comments
|
|
403
|
+
description: 'List the comments on a task. Use it to read the discussion, decisions or blockers recorded there. Returns { comments }.',
|
|
404
|
+
toolset: 'collaboration',
|
|
405
|
+
outputZodSchema: z.object({ comments: z.array(CommentOutput) }),
|
|
256
406
|
zodSchema: z.object({
|
|
257
407
|
taskId: z.string().describe('The task ID')
|
|
258
408
|
}),
|
|
259
409
|
annotations: { readOnlyHint: true },
|
|
260
410
|
execute: async (args, target) => {
|
|
261
411
|
if ('getComments' in target && typeof target.getComments === 'function') {
|
|
262
|
-
return target.getComments(args.taskId);
|
|
412
|
+
return { comments: await target.getComments(args.taskId) };
|
|
263
413
|
}
|
|
264
|
-
return [];
|
|
414
|
+
return { comments: [] };
|
|
265
415
|
}
|
|
266
416
|
});
|
|
267
417
|
export const addCommentTool = defineTool({
|
|
268
418
|
name: 'add_comment',
|
|
269
419
|
title: 'Add Comment',
|
|
270
|
-
description: '
|
|
420
|
+
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.',
|
|
421
|
+
toolset: 'collaboration',
|
|
422
|
+
outputZodSchema: CommentOutput,
|
|
271
423
|
zodSchema: z.object({
|
|
272
424
|
taskId: z.string().describe('The task ID'),
|
|
273
425
|
content: z.string().describe('Comment body/markdown'),
|
|
@@ -280,7 +432,9 @@ export const addCommentTool = defineTool({
|
|
|
280
432
|
export const calculatePortfolioCriticalPathTool = defineTool({
|
|
281
433
|
name: 'calculate_portfolio_critical_path',
|
|
282
434
|
title: 'Calculate Portfolio Critical Path',
|
|
283
|
-
description: '
|
|
435
|
+
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`.',
|
|
436
|
+
toolset: 'scheduling',
|
|
437
|
+
outputZodSchema: PortfolioCriticalPathOutput,
|
|
284
438
|
zodSchema: z.object({
|
|
285
439
|
projectIds: z.array(z.string()).optional().describe('Projects to include (default: every project you can read)'),
|
|
286
440
|
projectOrder: z.array(z.string()).optional().describe('Project ids in priority order for levelling; unlisted projects come last'),
|
|
@@ -305,7 +459,9 @@ export const calculatePortfolioCriticalPathTool = defineTool({
|
|
|
305
459
|
export const calculateCriticalPathTool = defineTool({
|
|
306
460
|
name: 'calculate_critical_path',
|
|
307
461
|
title: 'Calculate Critical Path',
|
|
308
|
-
description: '
|
|
462
|
+
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`.',
|
|
463
|
+
toolset: 'scheduling',
|
|
464
|
+
outputZodSchema: CriticalPathOutput,
|
|
309
465
|
zodSchema: z.object({
|
|
310
466
|
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
311
467
|
calendars: z
|
|
@@ -329,7 +485,7 @@ export const calculateCriticalPathTool = defineTool({
|
|
|
329
485
|
execute: async (args, target, ambientContext) => {
|
|
330
486
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
331
487
|
if (!projectId) {
|
|
332
|
-
throw new Error('projectId is required to calculate critical path.');
|
|
488
|
+
throw new Error('projectId is required to calculate the critical path. Find one with `list_projects`.');
|
|
333
489
|
}
|
|
334
490
|
const { calendars, levelResources, levelingPriority, effort } = args;
|
|
335
491
|
return target.calculateCriticalPath(projectId, { calendars, levelResources, levelingPriority, effort });
|
|
@@ -338,7 +494,9 @@ export const calculateCriticalPathTool = defineTool({
|
|
|
338
494
|
export const getTimelineLadderTool = defineTool({
|
|
339
495
|
name: 'get_timeline_ladder',
|
|
340
496
|
title: 'Get Timeline Ladder of Abstraction',
|
|
341
|
-
description: '
|
|
497
|
+
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`.',
|
|
498
|
+
toolset: 'analytics',
|
|
499
|
+
outputZodSchema: TimelineLadderOutput,
|
|
342
500
|
zodSchema: z.object({
|
|
343
501
|
projectId: z.string().optional().describe('Project ID (falls back to ambient context if omitted)'),
|
|
344
502
|
level: z.enum(['macro', 'standard', 'concrete', 'all']).optional().describe('Abstraction level (macro, standard, concrete, or all)'),
|
|
@@ -349,7 +507,7 @@ export const getTimelineLadderTool = defineTool({
|
|
|
349
507
|
execute: async (args, target, ambientContext) => {
|
|
350
508
|
const projectId = args.projectId || ambientContext?.projectId;
|
|
351
509
|
if (!projectId) {
|
|
352
|
-
throw new Error('projectId is required to get timeline ladder.');
|
|
510
|
+
throw new Error('projectId is required to get the timeline ladder. Find one with `list_projects`.');
|
|
353
511
|
}
|
|
354
512
|
return target.getTimelineLadder(projectId, {
|
|
355
513
|
level: args.level,
|
|
@@ -361,7 +519,9 @@ export const getTimelineLadderTool = defineTool({
|
|
|
361
519
|
export const getTaskLadderTool = defineTool({
|
|
362
520
|
name: 'get_task_ladder',
|
|
363
521
|
title: 'Get Task Ladder View',
|
|
364
|
-
description: '
|
|
522
|
+
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`.',
|
|
523
|
+
toolset: 'analytics',
|
|
524
|
+
outputZodSchema: TaskLadderOutput,
|
|
365
525
|
zodSchema: z.object({
|
|
366
526
|
taskId: z.string().describe('The task ID')
|
|
367
527
|
}),
|
|
@@ -373,7 +533,9 @@ export const getTaskLadderTool = defineTool({
|
|
|
373
533
|
export const getTaskMetricsTool = defineTool({
|
|
374
534
|
name: 'get_task_metrics',
|
|
375
535
|
title: 'Get Task Metrics',
|
|
376
|
-
description: '
|
|
536
|
+
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 }.',
|
|
537
|
+
toolset: 'analytics',
|
|
538
|
+
outputZodSchema: TaskMetricsOutput,
|
|
377
539
|
zodSchema: z.object({
|
|
378
540
|
taskId: z.string().describe('The task ID')
|
|
379
541
|
}),
|
|
@@ -385,7 +547,9 @@ export const getTaskMetricsTool = defineTool({
|
|
|
385
547
|
export const getTaskProgressHistoryTool = defineTool({
|
|
386
548
|
name: 'get_task_progress_history',
|
|
387
549
|
title: 'Get Task Progress History',
|
|
388
|
-
description: '
|
|
550
|
+
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.',
|
|
551
|
+
toolset: 'analytics',
|
|
552
|
+
outputZodSchema: TaskProgressHistoryOutput,
|
|
389
553
|
zodSchema: z.object({
|
|
390
554
|
taskId: z.string().describe('The task ID')
|
|
391
555
|
}),
|
|
@@ -397,7 +561,9 @@ export const getTaskProgressHistoryTool = defineTool({
|
|
|
397
561
|
export const getWorkloadDistributionTool = defineTool({
|
|
398
562
|
name: 'get_workload_distribution',
|
|
399
563
|
title: 'Get Workload Distribution',
|
|
400
|
-
description: '
|
|
564
|
+
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 }.',
|
|
565
|
+
toolset: 'scheduling',
|
|
566
|
+
outputZodSchema: WorkloadDistributionOutput,
|
|
401
567
|
zodSchema: z.object({
|
|
402
568
|
projectId: z.string().optional().describe('Project ID (optional, defaults to workspace-wide or ambient project)'),
|
|
403
569
|
startDate: z.string().optional().describe('Start date ISO (YYYY-MM-DD)'),
|
|
@@ -420,6 +586,27 @@ export const getWorkloadDistributionTool = defineTool({
|
|
|
420
586
|
});
|
|
421
587
|
}
|
|
422
588
|
});
|
|
589
|
+
export const getSkillTool = defineTool({
|
|
590
|
+
name: 'get_skill',
|
|
591
|
+
title: 'Get Skill',
|
|
592
|
+
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.',
|
|
593
|
+
toolset: 'guidance',
|
|
594
|
+
outputZodSchema: SkillOutput,
|
|
595
|
+
zodSchema: z.object({
|
|
596
|
+
name: z.string().optional().describe('Skill name, e.g. "plan-project"; omit to list all skills')
|
|
597
|
+
}),
|
|
598
|
+
annotations: { readOnlyHint: true },
|
|
599
|
+
execute: async (args) => {
|
|
600
|
+
if (!args.name) {
|
|
601
|
+
return { skills: SKILLS.map(({ name, description }) => ({ name, description })) };
|
|
602
|
+
}
|
|
603
|
+
const skill = getSkill(args.name);
|
|
604
|
+
if (!skill) {
|
|
605
|
+
throw new Error(`Skill "${args.name}" not found. Available skills: ${SKILLS.map((s) => s.name).join(', ')}.`);
|
|
606
|
+
}
|
|
607
|
+
return skill;
|
|
608
|
+
}
|
|
609
|
+
});
|
|
423
610
|
export const ALL_TOOLS = [
|
|
424
611
|
listProjectsTool,
|
|
425
612
|
getProjectTool,
|
|
@@ -429,6 +616,8 @@ export const ALL_TOOLS = [
|
|
|
429
616
|
createTaskTool,
|
|
430
617
|
updateTaskTool,
|
|
431
618
|
deleteTaskTool,
|
|
619
|
+
addDependencyTool,
|
|
620
|
+
removeDependencyTool,
|
|
432
621
|
listDeliverablesTool,
|
|
433
622
|
createDeliverableTool,
|
|
434
623
|
listCommentsTool,
|
|
@@ -439,7 +628,68 @@ export const ALL_TOOLS = [
|
|
|
439
628
|
getTaskLadderTool,
|
|
440
629
|
getTaskMetricsTool,
|
|
441
630
|
getTaskProgressHistoryTool,
|
|
442
|
-
getWorkloadDistributionTool
|
|
631
|
+
getWorkloadDistributionTool,
|
|
632
|
+
getSkillTool
|
|
443
633
|
];
|
|
444
634
|
export const TOOL_MAP = new Map(ALL_TOOLS.map((tool) => [tool.name, tool]));
|
|
635
|
+
/**
|
|
636
|
+
* Picks the tools a host exposes. Unknown tool or toolset names throw, so a typo cannot silently
|
|
637
|
+
* leave an agent without the tools it was meant to have. `get_skill` follows the same rules as
|
|
638
|
+
* other tools: include the `guidance` toolset (or name it) to keep skills reachable.
|
|
639
|
+
*/
|
|
640
|
+
export function selectTools(selection = {}) {
|
|
641
|
+
const { tools, toolsets, readOnly } = selection;
|
|
642
|
+
const unknownTools = (tools ?? []).filter((name) => !TOOL_MAP.has(name));
|
|
643
|
+
if (unknownTools.length > 0) {
|
|
644
|
+
throw new Error(`Unknown Critical Path tool(s): ${unknownTools.join(', ')}. Known tools: ${ALL_TOOLS.map((t) => t.name).join(', ')}.`);
|
|
645
|
+
}
|
|
646
|
+
const unknownSets = (toolsets ?? []).filter((name) => !(name in TOOLSETS));
|
|
647
|
+
if (unknownSets.length > 0) {
|
|
648
|
+
throw new Error(`Unknown Critical Path toolset(s): ${unknownSets.join(', ')}. Known toolsets: ${Object.keys(TOOLSETS).join(', ')}.`);
|
|
649
|
+
}
|
|
650
|
+
const filtered = !tools && !toolsets;
|
|
651
|
+
return ALL_TOOLS.filter((tool) => (filtered || tools?.includes(tool.name) || toolsets?.includes(tool.toolset)) &&
|
|
652
|
+
(!readOnly || tool.annotations.readOnlyHint));
|
|
653
|
+
}
|
|
654
|
+
export class ToolNotFoundError extends Error {
|
|
655
|
+
constructor(message) {
|
|
656
|
+
super(message);
|
|
657
|
+
this.name = 'ToolNotFoundError';
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
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.';
|
|
661
|
+
/**
|
|
662
|
+
* Validates arguments, runs a tool and returns a JSON-safe object result. A missing record (a
|
|
663
|
+
* `null` result) becomes an error, and errors carry a hint for what to do next so the agent can
|
|
664
|
+
* recover on its own.
|
|
665
|
+
*/
|
|
666
|
+
export async function runTool(tool, args, target, ambientContext) {
|
|
667
|
+
try {
|
|
668
|
+
const result = await tool.execute(parseToolArgs(tool, args), target, ambientContext);
|
|
669
|
+
if (result === null || result === undefined) {
|
|
670
|
+
throw new ToolNotFoundError(`Nothing found for ${JSON.stringify(args ?? {})}.`);
|
|
671
|
+
}
|
|
672
|
+
// Round-trip through JSON: drops undefined fields and serializes dates, as a transport would.
|
|
673
|
+
return JSON.parse(JSON.stringify(result));
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
throw new Error(withRecoveryHint(tool, err));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function withRecoveryHint(tool, err) {
|
|
680
|
+
const message = err?.message || String(err);
|
|
681
|
+
if (err instanceof ToolArgumentError) {
|
|
682
|
+
return `${message}. Fix the arguments to match the input schema of "${tool.name}" and call it again.`;
|
|
683
|
+
}
|
|
684
|
+
if (err instanceof ToolNotFoundError || err?.name === 'NotFoundError' || err?.status === 404 || /not found/i.test(message)) {
|
|
685
|
+
return `${message} ${ID_RECOVERY}`;
|
|
686
|
+
}
|
|
687
|
+
if (err?.name === 'CircularDependencyError' || /circular/i.test(message)) {
|
|
688
|
+
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.`;
|
|
689
|
+
}
|
|
690
|
+
if (err?.name === 'ForbiddenError' || err?.status === 403) {
|
|
691
|
+
return `${message} You do not have permission for this; tell the user rather than retrying.`;
|
|
692
|
+
}
|
|
693
|
+
return message;
|
|
694
|
+
}
|
|
445
695
|
//# sourceMappingURL=definitions.js.map
|